context-forge-cli 0.4.0__tar.gz → 0.4.2__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 (57) hide show
  1. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/PKG-INFO +1 -1
  2. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/codex.py +24 -4
  3. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/tokens_panel.py +8 -5
  4. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/utils/tokens.py +10 -1
  5. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/pyproject.toml +1 -1
  6. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/.contextforge.toml +0 -0
  7. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/.github/workflows/publish.yml +0 -0
  8. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/.gitignore +0 -0
  9. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/.python-version +0 -0
  10. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/AGENTS.md +0 -0
  11. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/PLAN.md +0 -0
  12. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/README.md +0 -0
  13. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/__init__.py +0 -0
  14. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/__init__.py +0 -0
  15. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/altimate_code.py +0 -0
  16. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/base.py +0 -0
  17. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/claude_code.py +0 -0
  18. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/claude_desktop.py +0 -0
  19. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/gemini.py +0 -0
  20. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/adapters/registry.py +0 -0
  21. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/cli.py +0 -0
  22. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/__init__.py +0 -0
  23. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/analytics.py +0 -0
  24. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/compactor.py +0 -0
  25. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/db.py +0 -0
  26. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/injector.py +0 -0
  27. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/scanner.py +0 -0
  28. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/summarizer.py +0 -0
  29. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/core/token_analyzer.py +0 -0
  30. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/mcp_server.py +0 -0
  31. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/models/__init__.py +0 -0
  32. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/models/config.py +0 -0
  33. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/models/session.py +0 -0
  34. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/__init__.py +0 -0
  35. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/app.py +0 -0
  36. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/styles.tcss +0 -0
  37. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/__init__.py +0 -0
  38. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/session_detail.py +0 -0
  39. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/session_table.py +0 -0
  40. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/stats_panel.py +0 -0
  41. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/status_bar.py +0 -0
  42. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/tui/widgets/transfer_panel.py +0 -0
  43. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/utils/__init__.py +0 -0
  44. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/contextforge/utils/display.py +0 -0
  45. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/__init__.py +0 -0
  46. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/adapters/__init__.py +0 -0
  47. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/adapters/test_claude_code.py +0 -0
  48. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/conftest.py +0 -0
  49. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/__init__.py +0 -0
  50. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/test_compactor.py +0 -0
  51. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/test_db.py +0 -0
  52. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/test_injector.py +0 -0
  53. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/test_summarizer.py +0 -0
  54. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/core/test_token_analyzer.py +0 -0
  55. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/fixtures/claude_session_sample.jsonl +0 -0
  56. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/tests/test_mcp_server.py +0 -0
  57. {context_forge_cli-0.4.0 → context_forge_cli-0.4.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: context-forge-cli
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -50,19 +50,39 @@ def _compute_message_tokens(msg: Message) -> int:
50
50
  return total
51
51
 
52
52
 
53
- def _extract_function_output(raw_output: str) -> str:
53
+ def _extract_function_output(raw_output) -> str:
54
54
  """Extract the human-readable output from a Codex function_call_output payload.
55
55
 
56
- The `output` field is a JSON string: {"output": "...", "metadata": {...}}.
57
- Returns the inner "output" string, or the raw string on parse failure.
56
+ The `output` field may be:
57
+ - A JSON string: {"output": "...", "metadata": {...}}
58
+ - An already-parsed list of content blocks (e.g. input_text/input_image)
59
+ - An already-parsed dict
60
+ Returns a plain string suitable for token counting.
58
61
  """
62
+ if isinstance(raw_output, list):
63
+ # Content-block array — extract text parts, skip images
64
+ parts = [
65
+ block.get("text", "")
66
+ for block in raw_output
67
+ if isinstance(block, dict) and block.get("type") in ("text", "input_text")
68
+ ]
69
+ return "\n".join(p for p in parts if p)
70
+ if isinstance(raw_output, dict):
71
+ return str(raw_output.get("output", ""))
59
72
  try:
60
73
  parsed = json.loads(raw_output)
61
74
  if isinstance(parsed, dict):
62
75
  return str(parsed.get("output", raw_output))
76
+ if isinstance(parsed, list):
77
+ parts = [
78
+ block.get("text", "")
79
+ for block in parsed
80
+ if isinstance(block, dict) and block.get("type") in ("text", "input_text")
81
+ ]
82
+ return "\n".join(p for p in parts if p)
63
83
  except (json.JSONDecodeError, TypeError):
64
84
  pass
65
- return raw_output
85
+ return str(raw_output) if raw_output is not None else ""
66
86
 
67
87
 
68
88
  class CodexAdapter(ToolAdapter):
@@ -4,6 +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
8
  from textual.app import ComposeResult
8
9
  from textual.binding import Binding
9
10
  from textual.containers import ScrollableContainer, Vertical
@@ -98,7 +99,7 @@ class TurnDetailPanel(ModalScreen):
98
99
  # ── Text content ────────────────────────────────────────────────────
99
100
  if msg.content:
100
101
  parts.append("[bold dim]── Content ──[/bold dim]")
101
- parts.append(msg.content)
102
+ parts.append(escape(msg.content))
102
103
 
103
104
  # ── Tool calls ──────────────────────────────────────────────────────
104
105
  if msg.tool_calls:
@@ -109,17 +110,19 @@ class TurnDetailPanel(ModalScreen):
109
110
  try:
110
111
  formatted = json.dumps(json.loads(raw_input), indent=2)
111
112
  except (json.JSONDecodeError, TypeError):
112
- formatted = raw_input
113
- parts.append(f"[bold yellow]{i}. {name}[/bold yellow]")
114
- parts.append(f"[dim]{formatted}[/dim]")
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]")
115
116
 
116
117
  # ── Tool results ────────────────────────────────────────────────────
117
118
  if msg.tool_results:
118
119
  parts.append("\n[bold dim]── Tool Results ──[/bold dim]")
119
120
  for i, tr in enumerate(msg.tool_results, 1):
120
121
  output = tr.get("output", "")
122
+ if not isinstance(output, str):
123
+ output = str(output)
121
124
  parts.append(f"[bold green]Result {i}[/bold green]")
122
- parts.append(f"[dim]{output}[/dim]")
125
+ parts.append(f"[dim]{escape(output)}[/dim]")
123
126
 
124
127
  self.query_one("#turn-body", Static).update("\n".join(parts))
125
128
 
@@ -13,7 +13,16 @@ def _get_encoding() -> tiktoken.Encoding:
13
13
  return _ENCODING
14
14
 
15
15
 
16
- def count_tokens(text: str) -> int:
16
+ def count_tokens(text) -> int:
17
+ if not isinstance(text, str):
18
+ if isinstance(text, list):
19
+ text = " ".join(
20
+ block.get("text", "")
21
+ for block in text
22
+ if isinstance(block, dict) and block.get("type") in ("text", "input_text")
23
+ )
24
+ else:
25
+ text = str(text) if text is not None else ""
17
26
  return len(_get_encoding().encode(text))
18
27
 
19
28
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "context-forge-cli"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "Session manager and context bridge for agentic CLI tools"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }