claude-dev-cli 0.8.2__py3-none-any.whl → 0.8.3__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.

Potentially problematic release.


This version of claude-dev-cli might be problematic. Click here for more details.

@@ -9,7 +9,7 @@ Features:
9
9
  - Interactive and single-shot modes
10
10
  """
11
11
 
12
- __version__ = "0.8.2"
12
+ __version__ = "0.8.3"
13
13
  __author__ = "Julio"
14
14
  __license__ = "MIT"
15
15
 
claude_dev_cli/cli.py CHANGED
@@ -145,6 +145,7 @@ def interactive(
145
145
  config = Config()
146
146
  history_dir = config.config_dir / "history"
147
147
  conv_history = ConversationHistory(history_dir)
148
+ summ_config = config.get_summarization_config()
148
149
 
149
150
  # Load or create conversation
150
151
  if continue_conversation:
@@ -203,6 +204,42 @@ def interactive(
203
204
  full_response = ''.join(response_buffer)
204
205
  conversation.add_message("assistant", full_response)
205
206
 
207
+ # Check if auto-summarization is needed
208
+ if summ_config.auto_summarize and conversation.should_summarize(threshold_tokens=summ_config.threshold_tokens):
209
+ console.print("\n[yellow]⚠ Conversation getting long, summarizing older messages...[/yellow]")
210
+ old_messages, recent_messages = conversation.compress_messages(keep_recent=summ_config.keep_recent_messages)
211
+
212
+ if old_messages:
213
+ # Build summary prompt
214
+ conversation_text = []
215
+ if conversation.summary:
216
+ conversation_text.append(f"Previous summary:\n{conversation.summary}\n\n")
217
+
218
+ conversation_text.append("Conversation to summarize:\n")
219
+ for msg in old_messages:
220
+ role_name = "User" if msg.role == "user" else "Assistant"
221
+ conversation_text.append(f"{role_name}: {msg.content}\n")
222
+
223
+ summary_prompt = (
224
+ "Please provide a concise summary of this conversation that captures:"
225
+ "\n1. Main topics discussed"
226
+ "\n2. Key questions asked and answers provided"
227
+ "\n3. Important decisions or conclusions"
228
+ "\n4. Any action items or follow-ups mentioned"
229
+ "\n\nKeep the summary under 300 words but retain all important context."
230
+ "\n\n" + "".join(conversation_text)
231
+ )
232
+
233
+ # Get summary (use same client)
234
+ new_summary = client.call(summary_prompt)
235
+
236
+ # Update conversation
237
+ conversation.summary = new_summary
238
+ conversation.messages = recent_messages
239
+
240
+ tokens_saved = len("".join(conversation_text)) // 4
241
+ console.print(f"[dim]✓ Summarized older messages (~{tokens_saved:,} tokens saved)[/dim]")
242
+
206
243
  # Auto-save periodically
207
244
  if save and len(conversation.messages) % 10 == 0:
208
245
  conv_history.save_conversation(conversation)
@@ -341,6 +378,100 @@ def history_export(ctx: click.Context, conversation_id: str, format: str, output
341
378
  click.echo(content)
342
379
 
343
380
 
381
+ @history.command('summarize')
382
+ @click.argument('conversation_id', required=False)
383
+ @click.option('--keep-recent', type=int, default=4, help='Number of recent message pairs to keep')
384
+ @click.option('--latest', is_flag=True, help='Summarize the latest conversation')
385
+ @click.pass_context
386
+ def history_summarize(
387
+ ctx: click.Context,
388
+ conversation_id: Optional[str],
389
+ keep_recent: int,
390
+ latest: bool
391
+ ) -> None:
392
+ """Summarize a conversation to reduce token usage.
393
+
394
+ Older messages are compressed into an AI-generated summary while keeping
395
+ recent messages intact. This reduces context window usage and costs.
396
+
397
+ Examples:
398
+ cdc history summarize 20240109_143022
399
+ cdc history summarize --latest
400
+ cdc history summarize --latest --keep-recent 6
401
+ """
402
+ console = ctx.obj['console']
403
+ config = Config()
404
+ conv_history = ConversationHistory(config.config_dir / "history")
405
+
406
+ # Determine which conversation to summarize
407
+ if latest:
408
+ conv = conv_history.get_latest_conversation()
409
+ if not conv:
410
+ console.print("[red]No conversations found[/red]")
411
+ sys.exit(1)
412
+ conversation_id = conv.conversation_id
413
+ elif not conversation_id:
414
+ console.print("[red]Error: Provide conversation_id or use --latest[/red]")
415
+ console.print("\nUsage: cdc history summarize CONVERSATION_ID")
416
+ console.print(" or: cdc history summarize --latest")
417
+ sys.exit(1)
418
+
419
+ # Load conversation to show stats
420
+ conv = conv_history.load_conversation(conversation_id)
421
+ if not conv:
422
+ console.print(f"[red]Conversation {conversation_id} not found[/red]")
423
+ sys.exit(1)
424
+
425
+ # Show before stats
426
+ tokens_before = conv.estimate_tokens()
427
+ messages_before = len(conv.messages)
428
+ console.print(f"\n[cyan]Conversation:[/cyan] {conversation_id}")
429
+ console.print(f"[dim]Messages: {messages_before} | Estimated tokens: {tokens_before:,}[/dim]\n")
430
+
431
+ if messages_before <= keep_recent:
432
+ console.print(f"[yellow]Too few messages to summarize ({messages_before} <= {keep_recent})[/yellow]")
433
+ return
434
+
435
+ # Summarize
436
+ with console.status("[bold blue]Generating summary..."):
437
+ summary = conv_history.summarize_conversation(conversation_id, keep_recent)
438
+
439
+ if not summary:
440
+ console.print("[red]Failed to generate summary[/red]")
441
+ sys.exit(1)
442
+
443
+ # Reload and show after stats
444
+ conv = conv_history.load_conversation(conversation_id)
445
+ if conv:
446
+ tokens_after = conv.estimate_tokens()
447
+ messages_after = len(conv.messages)
448
+
449
+ token_savings = tokens_before - tokens_after
450
+ savings_percent = (token_savings / tokens_before * 100) if tokens_before > 0 else 0
451
+
452
+ console.print("[green]✓ Conversation summarized[/green]\n")
453
+ console.print(f"[dim]Messages:[/dim] {messages_before} → {messages_after}")
454
+ console.print(f"[dim]Tokens:[/dim] {tokens_before:,} → {tokens_after:,} ({token_savings:,} saved, {savings_percent:.1f}%)\n")
455
+ console.print("[bold]Summary:[/bold]")
456
+ console.print(Panel(summary, border_style="blue"))
457
+
458
+
459
+ @history.command('delete')
460
+ @click.argument('conversation_id')
461
+ @click.pass_context
462
+ def history_delete(ctx: click.Context, conversation_id: str) -> None:
463
+ """Delete a conversation."""
464
+ console = ctx.obj['console']
465
+ config = Config()
466
+ conv_history = ConversationHistory(config.config_dir / "history")
467
+
468
+ if conv_history.delete_conversation(conversation_id):
469
+ console.print(f"[green]✓[/green] Deleted conversation: {conversation_id}")
470
+ else:
471
+ console.print(f"[red]Conversation {conversation_id} not found[/red]")
472
+ sys.exit(1)
473
+
474
+
344
475
  @main.group()
345
476
  def config() -> None:
346
477
  """Manage configuration."""
claude_dev_cli/config.py CHANGED
@@ -21,6 +21,15 @@ class ContextConfig(BaseModel):
21
21
  include_tests: bool = True # Include test files by default
22
22
 
23
23
 
24
+ class SummarizationConfig(BaseModel):
25
+ """Conversation summarization configuration."""
26
+
27
+ auto_summarize: bool = True # Enable automatic summarization
28
+ threshold_tokens: int = 8000 # Token threshold for auto-summarization
29
+ keep_recent_messages: int = 4 # Number of recent message pairs to keep
30
+ summary_max_words: int = 300 # Maximum words in generated summary
31
+
32
+
24
33
  class APIConfig(BaseModel):
25
34
  """Configuration for a Claude API key."""
26
35
 
@@ -87,6 +96,7 @@ class Config:
87
96
  "default_model": "claude-3-5-sonnet-20241022",
88
97
  "max_tokens": 4096,
89
98
  "context": ContextConfig().model_dump(),
99
+ "summarization": SummarizationConfig().model_dump(),
90
100
  }
91
101
  self._save_config(default_config)
92
102
  return default_config
@@ -263,3 +273,8 @@ class Config:
263
273
  """Get context gathering configuration."""
264
274
  context_data = self._data.get("context", {})
265
275
  return ContextConfig(**context_data) if context_data else ContextConfig()
276
+
277
+ def get_summarization_config(self) -> SummarizationConfig:
278
+ """Get conversation summarization configuration."""
279
+ summ_data = self._data.get("summarization", {})
280
+ return SummarizationConfig(**summ_data) if summ_data else SummarizationConfig()
claude_dev_cli/history.py CHANGED
@@ -39,11 +39,13 @@ class Conversation:
39
39
  self,
40
40
  conversation_id: Optional[str] = None,
41
41
  created_at: Optional[datetime] = None,
42
- updated_at: Optional[datetime] = None
42
+ updated_at: Optional[datetime] = None,
43
+ summary: Optional[str] = None
43
44
  ):
44
- self.conversation_id = conversation_id or datetime.utcnow().strftime("%Y%m%d_%H%M%S")
45
+ self.conversation_id = conversation_id or datetime.utcnow().strftime("%Y%m%d_%H%M%S_%f")
45
46
  self.created_at = created_at or datetime.utcnow()
46
47
  self.updated_at = updated_at or datetime.utcnow()
48
+ self.summary = summary # AI-generated summary of older messages
47
49
  self.messages: List[Message] = []
48
50
 
49
51
  def add_message(self, role: str, content: str) -> None:
@@ -62,14 +64,37 @@ class Conversation:
62
64
  return summary
63
65
  return "(empty conversation)"
64
66
 
67
+ def estimate_tokens(self) -> int:
68
+ """Estimate token count for the conversation."""
69
+ # Rough estimation: ~4 characters per token
70
+ total_chars = len(self.summary or "")
71
+ for msg in self.messages:
72
+ total_chars += len(msg.content)
73
+ return total_chars // 4
74
+
75
+ def should_summarize(self, threshold_tokens: int = 8000) -> bool:
76
+ """Check if conversation should be summarized."""
77
+ return self.estimate_tokens() > threshold_tokens and len(self.messages) > 4
78
+
79
+ def compress_messages(self, keep_recent: int = 4) -> tuple[List[Message], List[Message]]:
80
+ """Split messages into old (to summarize) and recent (to keep)."""
81
+ if len(self.messages) <= keep_recent:
82
+ return [], self.messages
83
+
84
+ split_point = len(self.messages) - keep_recent
85
+ return self.messages[:split_point], self.messages[split_point:]
86
+
65
87
  def to_dict(self) -> Dict[str, Any]:
66
88
  """Convert to dictionary for storage."""
67
- return {
89
+ data = {
68
90
  "conversation_id": self.conversation_id,
69
91
  "created_at": self.created_at.isoformat(),
70
92
  "updated_at": self.updated_at.isoformat(),
71
93
  "messages": [msg.to_dict() for msg in self.messages]
72
94
  }
95
+ if self.summary:
96
+ data["summary"] = self.summary
97
+ return data
73
98
 
74
99
  @classmethod
75
100
  def from_dict(cls, data: Dict[str, Any]) -> "Conversation":
@@ -77,7 +102,8 @@ class Conversation:
77
102
  conv = cls(
78
103
  conversation_id=data["conversation_id"],
79
104
  created_at=datetime.fromisoformat(data["created_at"]),
80
- updated_at=datetime.fromisoformat(data["updated_at"])
105
+ updated_at=datetime.fromisoformat(data["updated_at"]),
106
+ summary=data.get("summary")
81
107
  )
82
108
  conv.messages = [Message.from_dict(msg) for msg in data.get("messages", [])]
83
109
  return conv
@@ -187,3 +213,63 @@ class ConversationHistory:
187
213
  return json.dumps(conv.to_dict(), indent=2)
188
214
 
189
215
  return None
216
+
217
+ def summarize_conversation(
218
+ self,
219
+ conversation_id: str,
220
+ keep_recent: int = 4
221
+ ) -> Optional[str]:
222
+ """Summarize older messages in a conversation, keeping recent ones.
223
+
224
+ Args:
225
+ conversation_id: The conversation to summarize
226
+ keep_recent: Number of recent message pairs to keep unsummarized
227
+
228
+ Returns:
229
+ The generated summary or None if conversation not found
230
+ """
231
+ from claude_dev_cli.core import ClaudeClient
232
+
233
+ conv = self.load_conversation(conversation_id)
234
+ if not conv:
235
+ return None
236
+
237
+ # Split messages
238
+ old_messages, recent_messages = conv.compress_messages(keep_recent)
239
+
240
+ if not old_messages:
241
+ return "No messages to summarize (too few messages)"
242
+
243
+ # Build summary prompt
244
+ conversation_text = []
245
+ if conv.summary:
246
+ conversation_text.append(f"Previous summary:\n{conv.summary}\n\n")
247
+
248
+ conversation_text.append("Conversation to summarize:\n")
249
+ for msg in old_messages:
250
+ role_name = "User" if msg.role == "user" else "Assistant"
251
+ conversation_text.append(f"{role_name}: {msg.content}\n")
252
+
253
+ prompt = (
254
+ "Please provide a concise summary of this conversation that captures:"
255
+ "\n1. Main topics discussed"
256
+ "\n2. Key questions asked and answers provided"
257
+ "\n3. Important decisions or conclusions"
258
+ "\n4. Any action items or follow-ups mentioned"
259
+ "\n\nKeep the summary under 300 words but retain all important context."
260
+ "\n\n" + "".join(conversation_text)
261
+ )
262
+
263
+ # Get summary from Claude
264
+ client = ClaudeClient()
265
+ new_summary = client.call(prompt)
266
+
267
+ # Update conversation
268
+ conv.summary = new_summary
269
+ conv.messages = recent_messages
270
+ conv.updated_at = datetime.utcnow()
271
+
272
+ # Save updated conversation
273
+ self.save_conversation(conv)
274
+
275
+ return new_summary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-dev-cli
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: A powerful CLI tool for developers using Claude AI with multi-API routing, test generation, code review, and usage tracking
5
5
  Author-email: Julio <thinmanj@users.noreply.github.com>
6
6
  License: MIT
@@ -44,6 +44,13 @@ Dynamic: license-file
44
44
 
45
45
  # Claude Dev CLI
46
46
 
47
+ [![PyPI version](https://badge.fury.io/py/claude-dev-cli.svg)](https://badge.fury.io/py/claude-dev-cli)
48
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
49
+ [![Tests](https://img.shields.io/badge/tests-257%20passing-brightgreen.svg)](https://github.com/thinmanj/claude-dev-cli)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
51
+ [![Homebrew](https://img.shields.io/badge/homebrew-available-orange.svg)](https://github.com/thinmanj/homebrew-tap)
52
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
53
+
47
54
  A powerful command-line tool for developers using Claude AI with multi-API routing, test generation, code review, and comprehensive usage tracking.
48
55
 
49
56
  ## Features
@@ -99,19 +106,39 @@ A powerful command-line tool for developers using Claude AI with multi-API routi
99
106
 
100
107
  ## Installation
101
108
 
102
- ### Basic Installation
109
+ ### Via Homebrew (macOS/Linux)
103
110
 
104
111
  ```bash
105
- pip install claude-dev-cli
112
+ # Add the tap
113
+ brew tap thinmanj/tap
114
+
115
+ # Install
116
+ brew install claude-dev-cli
117
+
118
+ # Or in one command
119
+ brew install thinmanj/tap/claude-dev-cli
106
120
  ```
107
121
 
108
- ### With TOON Support (Recommended for Cost Savings)
122
+ ### Via pip
109
123
 
110
124
  ```bash
111
- # Install with TOON format support for 30-60% token reduction
125
+ # Basic installation
126
+ pip install claude-dev-cli
127
+
128
+ # With TOON support (30-60% token reduction)
112
129
  pip install claude-dev-cli[toon]
113
130
  ```
114
131
 
132
+ ### Via pipx (Recommended for CLI tools)
133
+
134
+ ```bash
135
+ # Isolated installation
136
+ pipx install claude-dev-cli
137
+
138
+ # With TOON support
139
+ pipx install claude-dev-cli[toon]
140
+ ```
141
+
115
142
  ## Quick Start
116
143
 
117
144
  ### 1. Set Up API Keys
@@ -266,7 +293,33 @@ cdc template list --user
266
293
  - **explain-code**: Detailed code explanation
267
294
  - **api-design**: API design assistance
268
295
 
269
- ### 6. Usage Tracking
296
+ ### 6. Conversation History & Summarization (NEW in v0.8.3)
297
+
298
+ ```bash
299
+ # List recent conversations
300
+ cdc history list
301
+
302
+ # Search conversations
303
+ cdc history list --search "python decorators"
304
+
305
+ # Export conversation
306
+ cdc history export 20240109_143022 -o chat.md
307
+
308
+ # Summarize to reduce token usage
309
+ cdc history summarize --latest
310
+ cdc history summarize 20240109_143022 --keep-recent 6
311
+
312
+ # Delete old conversations
313
+ cdc history delete 20240109_143022
314
+ ```
315
+
316
+ **Auto-Summarization** in interactive mode:
317
+ - Automatically triggers when conversation exceeds 8,000 tokens
318
+ - Keeps recent messages (default: 4 pairs), summarizes older ones
319
+ - Reduces API costs by ~30-50% in long conversations
320
+ - Shows token savings after summarization
321
+
322
+ ### 7. Usage Tracking
270
323
 
271
324
  ```bash
272
325
  # View all usage
@@ -1,10 +1,10 @@
1
- claude_dev_cli/__init__.py,sha256=zz-bOjjR01C_VWxFEsIWbdJui0kBuDLpDfJV7yAFFRc,469
2
- claude_dev_cli/cli.py,sha256=BjTST8JSd8quldFxs09ZzlAPGt-rFIYhKUCWfj1pIn4,55100
1
+ claude_dev_cli/__init__.py,sha256=4-FONrbZl8GAHKPOWIDbvA7AmSZsTv8CRep1rwl1w5Y,469
2
+ claude_dev_cli/cli.py,sha256=hy2HjU7oEWgGwNB7apBKFQmzjhYrxhHxTqtJ18iePZE,61164
3
3
  claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
4
- claude_dev_cli/config.py,sha256=OLx0xWDf1RIK6RIxl5OKVS4aOSMZZOKxBDmzfQX-muk,9745
4
+ claude_dev_cli/config.py,sha256=hisG91dUfDwuSdBhmYovU-rrGv__nFq-6fp7S288cSw,10471
5
5
  claude_dev_cli/context.py,sha256=1TlLzpREFZDEIuU7RAtlkjxARKWZpnxHHvK283sUAZE,26714
6
6
  claude_dev_cli/core.py,sha256=yaLjEixDvPzvUy4fJ2UB7nMpPPLyKACjR-RuM-1OQBY,4780
7
- claude_dev_cli/history.py,sha256=iQlqgTnXCsyCq5q-XaDl7V5MyPKQ3bx7o_k76-xWSAA,6863
7
+ claude_dev_cli/history.py,sha256=v952xORhxZD0ayrrMaIbTLfe6kOW5fYryoeEmQLlQPg,10163
8
8
  claude_dev_cli/secure_storage.py,sha256=TK3WOaU7a0yTOtzdP_t_28fDRp2lovANNAC6MBdm4nQ,7096
9
9
  claude_dev_cli/template_manager.py,sha256=ZFXOtRIoB6hpf8kLSF9TWJfvUPJt9b-PyEv3qTBK7Zs,8600
10
10
  claude_dev_cli/templates.py,sha256=lKxH943ySfUKgyHaWa4W3LVv91SgznKgajRtSRp_4UY,2260
@@ -17,9 +17,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
17
17
  claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
18
18
  claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
19
19
  claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
20
- claude_dev_cli-0.8.2.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
21
- claude_dev_cli-0.8.2.dist-info/METADATA,sha256=4Pyai7Is7NJezxG5ypTm5rML1wEqIlOwHI5GZDlFFs0,15708
22
- claude_dev_cli-0.8.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- claude_dev_cli-0.8.2.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
24
- claude_dev_cli-0.8.2.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
25
- claude_dev_cli-0.8.2.dist-info/RECORD,,
20
+ claude_dev_cli-0.8.3.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
21
+ claude_dev_cli-0.8.3.dist-info/METADATA,sha256=zgkMO_8g4hwJ7ccfDqGqXM-d3OfghrhxsWt7JK0evgs,17356
22
+ claude_dev_cli-0.8.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ claude_dev_cli-0.8.3.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
24
+ claude_dev_cli-0.8.3.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
25
+ claude_dev_cli-0.8.3.dist-info/RECORD,,