context-forge-cli 0.2.1__tar.gz → 0.2.3__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.2.1 → context_forge_cli-0.2.3}/PKG-INFO +1 -1
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/altimate_code.py +17 -5
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/base.py +15 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/claude_code.py +14 -30
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/claude_desktop.py +13 -4
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/codex.py +24 -2
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/gemini.py +13 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/cli.py +73 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/pyproject.toml +1 -1
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/uv.lock +2 -2
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/.contextforge.toml +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/.github/workflows/publish.yml +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/.gitignore +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/.python-version +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/AGENTS.md +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/PLAN.md +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/README.md +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/adapters/registry.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/analytics.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/compactor.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/db.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/injector.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/scanner.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/summarizer.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/core/token_analyzer.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/models/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/models/config.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/models/session.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/app.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/styles.tcss +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/session_detail.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/session_table.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/stats_panel.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/status_bar.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/tokens_panel.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/transfer_panel.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/utils/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/utils/display.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/utils/tokens.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/adapters/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/adapters/test_claude_code.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/__init__.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/test_compactor.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/test_db.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/test_injector.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/test_summarizer.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/core/test_token_analyzer.py +0 -0
- {context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/tests/fixtures/claude_session_sample.jsonl +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: context-forge-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -87,13 +87,19 @@ class AltimateCodeAdapter(ToolAdapter):
|
|
|
87
87
|
conn.row_factory = sqlite3.Row
|
|
88
88
|
cur = conn.cursor()
|
|
89
89
|
# Join message + part to get full content
|
|
90
|
+
# Note: altimate-code stores data as JSON in 'data' column
|
|
90
91
|
cur.execute(
|
|
91
92
|
"""
|
|
92
|
-
SELECT
|
|
93
|
+
SELECT
|
|
94
|
+
m.id,
|
|
95
|
+
json_extract(m.data, '$.role') as role,
|
|
96
|
+
m.time_created as ts,
|
|
97
|
+
json_extract(p.data, '$.type') as part_type,
|
|
98
|
+
json_extract(p.data, '$.text') as content
|
|
93
99
|
FROM message m
|
|
94
100
|
LEFT JOIN part p ON p.message_id = m.id
|
|
95
101
|
WHERE m.session_id = ?
|
|
96
|
-
ORDER BY m.
|
|
102
|
+
ORDER BY m.time_created ASC, p.id ASC
|
|
97
103
|
""",
|
|
98
104
|
(session_id,),
|
|
99
105
|
)
|
|
@@ -103,12 +109,13 @@ class AltimateCodeAdapter(ToolAdapter):
|
|
|
103
109
|
return []
|
|
104
110
|
|
|
105
111
|
messages: list[Message] = []
|
|
112
|
+
current_msg_id: str | None = None
|
|
106
113
|
current_role: str | None = None
|
|
107
114
|
current_parts: list[str] = []
|
|
108
115
|
current_ts: datetime | None = None
|
|
109
116
|
|
|
110
117
|
def flush():
|
|
111
|
-
nonlocal current_role, current_parts, current_ts
|
|
118
|
+
nonlocal current_msg_id, current_role, current_parts, current_ts
|
|
112
119
|
if current_role and current_parts:
|
|
113
120
|
content = "\n".join(p for p in current_parts if p)
|
|
114
121
|
if content:
|
|
@@ -117,19 +124,24 @@ class AltimateCodeAdapter(ToolAdapter):
|
|
|
117
124
|
role=current_role,
|
|
118
125
|
content=content,
|
|
119
126
|
timestamp=current_ts,
|
|
127
|
+
token_count=_count_tokens(content),
|
|
120
128
|
)
|
|
121
129
|
)
|
|
130
|
+
current_msg_id = None
|
|
122
131
|
current_role = None
|
|
123
132
|
current_parts = []
|
|
124
133
|
current_ts = None
|
|
125
134
|
|
|
126
135
|
for row in rows:
|
|
136
|
+
msg_id = row["id"]
|
|
127
137
|
role = row["role"]
|
|
128
138
|
if role not in ("user", "assistant"):
|
|
129
139
|
continue
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
# Flush when message ID changes (new message in the message table)
|
|
142
|
+
if msg_id != current_msg_id:
|
|
132
143
|
flush()
|
|
144
|
+
current_msg_id = msg_id
|
|
133
145
|
current_role = role
|
|
134
146
|
try:
|
|
135
147
|
current_ts = datetime.fromtimestamp(row["ts"] / 1000, tz=timezone.utc)
|
|
@@ -157,7 +169,7 @@ class AltimateCodeAdapter(ToolAdapter):
|
|
|
157
169
|
messages = self.load_messages(session_id)
|
|
158
170
|
total = 0
|
|
159
171
|
for msg in messages:
|
|
160
|
-
total += _count_tokens(msg.content)
|
|
172
|
+
total += msg.token_count or _count_tokens(msg.content)
|
|
161
173
|
return total
|
|
162
174
|
|
|
163
175
|
def build_inject_command(
|
|
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from contextforge.models.session import Message, Session
|
|
8
|
+
from contextforge.utils.tokens import count_tokens
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ToolAdapter(ABC):
|
|
@@ -42,3 +43,17 @@ class ToolAdapter(ABC):
|
|
|
42
43
|
def is_available(self) -> bool:
|
|
43
44
|
"""Return True if this tool is installed and its data paths exist."""
|
|
44
45
|
return any(p.exists() for p in self.default_paths)
|
|
46
|
+
|
|
47
|
+
def _count_session_tokens(self, session_id: str | Path) -> int:
|
|
48
|
+
"""Count total tokens in a session by loading and summing message tokens.
|
|
49
|
+
|
|
50
|
+
This is the default implementation that all adapters can use.
|
|
51
|
+
Adapters may override this with more efficient implementations.
|
|
52
|
+
"""
|
|
53
|
+
if isinstance(session_id, Path):
|
|
54
|
+
session_id = session_id.stem
|
|
55
|
+
messages = self.load_messages(str(session_id))
|
|
56
|
+
total = 0
|
|
57
|
+
for msg in messages:
|
|
58
|
+
total += msg.token_count or count_tokens(msg.content)
|
|
59
|
+
return total
|
|
@@ -333,42 +333,26 @@ class ClaudeCodeAdapter(ToolAdapter):
|
|
|
333
333
|
role="user" if role == "user" else "assistant",
|
|
334
334
|
content=content,
|
|
335
335
|
timestamp=ts,
|
|
336
|
+
token_count=_count_tokens(content),
|
|
336
337
|
)
|
|
337
338
|
)
|
|
338
339
|
|
|
339
340
|
return messages
|
|
340
341
|
|
|
341
|
-
def _count_session_tokens(self, jsonl_path: Path) -> int:
|
|
342
|
-
"""Count total tokens in a session JSONL file.
|
|
343
|
-
total = 0
|
|
344
|
-
try:
|
|
345
|
-
with jsonl_path.open() as f:
|
|
346
|
-
for line in f:
|
|
347
|
-
line = line.strip()
|
|
348
|
-
if not line:
|
|
349
|
-
continue
|
|
350
|
-
try:
|
|
351
|
-
entry = json.loads(line)
|
|
352
|
-
except json.JSONDecodeError:
|
|
353
|
-
continue
|
|
354
|
-
|
|
355
|
-
entry_type = entry.get("type", "")
|
|
356
|
-
if entry_type not in ("user", "assistant"):
|
|
357
|
-
continue
|
|
358
|
-
|
|
359
|
-
msg_data = entry.get("message", {})
|
|
360
|
-
content_raw = msg_data.get("content", "")
|
|
342
|
+
def _count_session_tokens(self, jsonl_path: Path | str) -> int:
|
|
343
|
+
"""Count total tokens in a session JSONL file or session ID.
|
|
361
344
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
345
|
+
Args:
|
|
346
|
+
jsonl_path: Either a Path to the JSONL file or a session ID string.
|
|
347
|
+
"""
|
|
348
|
+
if isinstance(jsonl_path, str):
|
|
349
|
+
session_id = jsonl_path
|
|
350
|
+
else:
|
|
351
|
+
session_id = jsonl_path.stem
|
|
352
|
+
messages = self.load_messages(session_id)
|
|
353
|
+
total = 0
|
|
354
|
+
for msg in messages:
|
|
355
|
+
total += msg.token_count or _count_tokens(msg.content)
|
|
372
356
|
return total
|
|
373
357
|
|
|
374
358
|
def build_inject_command(
|
|
@@ -240,8 +240,12 @@ class ClaudeDesktopAdapter(ToolAdapter):
|
|
|
240
240
|
pass
|
|
241
241
|
|
|
242
242
|
messages.append(
|
|
243
|
-
Message(
|
|
244
|
-
|
|
243
|
+
Message(
|
|
244
|
+
role="user" if role == "user" else "assistant",
|
|
245
|
+
content=content,
|
|
246
|
+
timestamp=ts,
|
|
247
|
+
token_count=_count_tokens(content),
|
|
248
|
+
)
|
|
245
249
|
)
|
|
246
250
|
return messages
|
|
247
251
|
|
|
@@ -279,7 +283,12 @@ class ClaudeDesktopAdapter(ToolAdapter):
|
|
|
279
283
|
except (ValueError, AttributeError):
|
|
280
284
|
pass
|
|
281
285
|
|
|
282
|
-
messages.append(Message(
|
|
286
|
+
messages.append(Message(
|
|
287
|
+
role=role,
|
|
288
|
+
content=content,
|
|
289
|
+
timestamp=ts,
|
|
290
|
+
token_count=_count_tokens(content),
|
|
291
|
+
))
|
|
283
292
|
return messages
|
|
284
293
|
|
|
285
294
|
def _count_session_tokens(self, session_id: str) -> int:
|
|
@@ -287,7 +296,7 @@ class ClaudeDesktopAdapter(ToolAdapter):
|
|
|
287
296
|
messages = self.load_messages(session_id)
|
|
288
297
|
total = 0
|
|
289
298
|
for msg in messages:
|
|
290
|
-
total += _count_tokens(msg.content)
|
|
299
|
+
total += msg.token_count or _count_tokens(msg.content)
|
|
291
300
|
return total
|
|
292
301
|
|
|
293
302
|
def build_inject_command(
|
|
@@ -7,6 +7,8 @@ import sqlite3
|
|
|
7
7
|
from datetime import datetime, timezone
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
import tiktoken
|
|
11
|
+
|
|
10
12
|
from contextforge.adapters.base import ToolAdapter
|
|
11
13
|
from contextforge.models.session import Message, Session
|
|
12
14
|
|
|
@@ -29,6 +31,16 @@ _CODEX_SESSIONS_DIR = Path.home() / ".codex" / "sessions"
|
|
|
29
31
|
_SESSION_INDEX = Path.home() / ".codex" / "session_index.jsonl"
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _count_tokens(text: str) -> int:
|
|
35
|
+
"""Count tokens in text using tiktoken for Claude models."""
|
|
36
|
+
try:
|
|
37
|
+
enc = tiktoken.encoding_for_model("claude-3-5-sonnet-20241022")
|
|
38
|
+
return len(enc.encode(text))
|
|
39
|
+
except Exception:
|
|
40
|
+
# Fallback: rough estimate (~4 chars per token)
|
|
41
|
+
return len(text) // 4
|
|
42
|
+
|
|
43
|
+
|
|
32
44
|
class CodexAdapter(ToolAdapter):
|
|
33
45
|
tool_name = "codex"
|
|
34
46
|
default_paths = [_CODEX_DB, _CODEX_SESSIONS_DIR]
|
|
@@ -193,11 +205,21 @@ class CodexAdapter(ToolAdapter):
|
|
|
193
205
|
if msg_type == "user_message":
|
|
194
206
|
text = payload.get("message", "")
|
|
195
207
|
if text:
|
|
196
|
-
|
|
208
|
+
text_str = str(text)
|
|
209
|
+
messages.append(Message(
|
|
210
|
+
role="user",
|
|
211
|
+
content=text_str,
|
|
212
|
+
token_count=_count_tokens(text_str),
|
|
213
|
+
))
|
|
197
214
|
elif msg_type == "agent_message":
|
|
198
215
|
text = payload.get("message", "")
|
|
199
216
|
if text:
|
|
200
|
-
|
|
217
|
+
text_str = str(text)
|
|
218
|
+
messages.append(Message(
|
|
219
|
+
role="assistant",
|
|
220
|
+
content=text_str,
|
|
221
|
+
token_count=_count_tokens(text_str),
|
|
222
|
+
))
|
|
201
223
|
|
|
202
224
|
return messages
|
|
203
225
|
|
|
@@ -6,12 +6,24 @@ import shlex
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
import tiktoken
|
|
10
|
+
|
|
9
11
|
from contextforge.adapters.base import ToolAdapter
|
|
10
12
|
from contextforge.models.session import Message, Session
|
|
11
13
|
|
|
12
14
|
_GEMINI_TMP_BASE = Path.home() / ".gemini" / "tmp"
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def _count_tokens(text: str) -> int:
|
|
18
|
+
"""Count tokens in text using tiktoken for Claude models."""
|
|
19
|
+
try:
|
|
20
|
+
enc = tiktoken.encoding_for_model("claude-3-5-sonnet-20241022")
|
|
21
|
+
return len(enc.encode(text))
|
|
22
|
+
except Exception:
|
|
23
|
+
# Fallback: rough estimate (~4 chars per token)
|
|
24
|
+
return len(text) // 4
|
|
25
|
+
|
|
26
|
+
|
|
15
27
|
class GeminiAdapter(ToolAdapter):
|
|
16
28
|
tool_name = "gemini"
|
|
17
29
|
default_paths = [_GEMINI_TMP_BASE]
|
|
@@ -163,6 +175,7 @@ class GeminiAdapter(ToolAdapter):
|
|
|
163
175
|
role="user" if msg_type == "user" else "assistant",
|
|
164
176
|
content=content,
|
|
165
177
|
timestamp=ts,
|
|
178
|
+
token_count=_count_tokens(content),
|
|
166
179
|
)
|
|
167
180
|
)
|
|
168
181
|
|
|
@@ -505,6 +505,79 @@ def config_set(key: str, value: str):
|
|
|
505
505
|
console.print(f"Would set [bold]{key}[/bold] = [bold]{value}[/bold]")
|
|
506
506
|
|
|
507
507
|
|
|
508
|
+
# ---------------------------------------------------------------------------
|
|
509
|
+
# cf refresh
|
|
510
|
+
# ---------------------------------------------------------------------------
|
|
511
|
+
|
|
512
|
+
@app.command()
|
|
513
|
+
def refresh(
|
|
514
|
+
quiet: Annotated[bool, typer.Option("--quiet", "-q")] = False,
|
|
515
|
+
):
|
|
516
|
+
"""Refresh token counts and metadata for all sessions.
|
|
517
|
+
|
|
518
|
+
Recalculates token_count for every session from source data.
|
|
519
|
+
Useful after adapter updates or schema changes.
|
|
520
|
+
"""
|
|
521
|
+
cfg = _get_config()
|
|
522
|
+
database = _get_db(cfg)
|
|
523
|
+
|
|
524
|
+
from contextforge.adapters.registry import get_adapter
|
|
525
|
+
|
|
526
|
+
# Get all sessions grouped by tool
|
|
527
|
+
sessions_by_tool = {}
|
|
528
|
+
for row in database.execute("SELECT DISTINCT tool FROM sessions ORDER BY tool").fetchall():
|
|
529
|
+
tool = row[0]
|
|
530
|
+
sessions_by_tool[tool] = []
|
|
531
|
+
for s in database.execute("SELECT id, title FROM sessions WHERE tool = ? ORDER BY id", [tool]).fetchall():
|
|
532
|
+
sessions_by_tool[tool].append((s[0], s[1]))
|
|
533
|
+
|
|
534
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
535
|
+
|
|
536
|
+
total_sessions = sum(len(sessions) for sessions in sessions_by_tool.values())
|
|
537
|
+
updated_count = 0
|
|
538
|
+
error_count = 0
|
|
539
|
+
|
|
540
|
+
with Progress(
|
|
541
|
+
SpinnerColumn(),
|
|
542
|
+
TextColumn("[progress.description]{task.description}"),
|
|
543
|
+
disable=quiet,
|
|
544
|
+
) as progress:
|
|
545
|
+
task = progress.add_task("Refreshing...", total=total_sessions)
|
|
546
|
+
|
|
547
|
+
for tool, sessions in sessions_by_tool.items():
|
|
548
|
+
if not sessions:
|
|
549
|
+
continue
|
|
550
|
+
|
|
551
|
+
try:
|
|
552
|
+
adapter = get_adapter(tool)
|
|
553
|
+
except Exception as e:
|
|
554
|
+
if not quiet:
|
|
555
|
+
err_console.print(f"[yellow]Warning:[/yellow] Skipping {tool}: {e}")
|
|
556
|
+
error_count += len(sessions)
|
|
557
|
+
progress.advance(task, len(sessions))
|
|
558
|
+
continue
|
|
559
|
+
|
|
560
|
+
for session_id, title in sessions:
|
|
561
|
+
try:
|
|
562
|
+
token_count = adapter._count_session_tokens(session_id)
|
|
563
|
+
database["sessions"].update(session_id, {"token_count": token_count})
|
|
564
|
+
updated_count += 1
|
|
565
|
+
if not quiet:
|
|
566
|
+
progress.update(task, description=f"Refreshed {session_id[:12]} ({token_count:,} tokens)")
|
|
567
|
+
except Exception as e:
|
|
568
|
+
error_count += 1
|
|
569
|
+
if not quiet:
|
|
570
|
+
err_console.print(f"[yellow]Warning:[/yellow] {session_id}: {e}")
|
|
571
|
+
finally:
|
|
572
|
+
progress.advance(task)
|
|
573
|
+
|
|
574
|
+
if not quiet:
|
|
575
|
+
console.print(
|
|
576
|
+
f"[green]Refresh complete:[/green] "
|
|
577
|
+
f"{updated_count} updated" + (f", {error_count} errors" if error_count else "")
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
508
581
|
# ---------------------------------------------------------------------------
|
|
509
582
|
# cf dashboard
|
|
510
583
|
# ---------------------------------------------------------------------------
|
|
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.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/session_detail.py
RENAMED
|
File without changes
|
{context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/session_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/contextforge/tui/widgets/tokens_panel.py
RENAMED
|
File without changes
|
{context_forge_cli-0.2.1 → context_forge_cli-0.2.3}/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.2.1 → context_forge_cli-0.2.3}/tests/fixtures/claude_session_sample.jsonl
RENAMED
|
File without changes
|