up-cli 0.1.1__py3-none-any.whl → 0.5.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.
- up/__init__.py +1 -1
- up/ai_cli.py +229 -0
- up/cli.py +75 -4
- up/commands/agent.py +521 -0
- up/commands/bisect.py +343 -0
- up/commands/branch.py +350 -0
- up/commands/dashboard.py +248 -0
- up/commands/init.py +195 -6
- up/commands/learn.py +1741 -0
- up/commands/memory.py +545 -0
- up/commands/new.py +108 -10
- up/commands/provenance.py +267 -0
- up/commands/review.py +239 -0
- up/commands/start.py +1124 -0
- up/commands/status.py +360 -0
- up/commands/summarize.py +122 -0
- up/commands/sync.py +317 -0
- up/commands/vibe.py +304 -0
- up/context.py +421 -0
- up/core/__init__.py +69 -0
- up/core/checkpoint.py +479 -0
- up/core/provenance.py +364 -0
- up/core/state.py +678 -0
- up/events.py +512 -0
- up/git/__init__.py +37 -0
- up/git/utils.py +270 -0
- up/git/worktree.py +331 -0
- up/learn/__init__.py +155 -0
- up/learn/analyzer.py +227 -0
- up/learn/plan.py +374 -0
- up/learn/research.py +511 -0
- up/learn/utils.py +117 -0
- up/memory.py +1096 -0
- up/parallel.py +551 -0
- up/summarizer.py +407 -0
- up/templates/__init__.py +70 -2
- up/templates/config/__init__.py +502 -20
- up/templates/docs/SKILL.md +28 -0
- up/templates/docs/__init__.py +341 -0
- up/templates/docs/standards/HEADERS.md +24 -0
- up/templates/docs/standards/STRUCTURE.md +18 -0
- up/templates/docs/standards/TEMPLATES.md +19 -0
- up/templates/learn/__init__.py +567 -14
- up/templates/loop/__init__.py +546 -27
- up/templates/mcp/__init__.py +474 -0
- up/templates/projects/__init__.py +786 -0
- up/ui/__init__.py +14 -0
- up/ui/loop_display.py +650 -0
- up/ui/theme.py +137 -0
- up_cli-0.5.0.dist-info/METADATA +519 -0
- up_cli-0.5.0.dist-info/RECORD +55 -0
- up_cli-0.1.1.dist-info/METADATA +0 -186
- up_cli-0.1.1.dist-info/RECORD +0 -14
- {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
- {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/entry_points.txt +0 -0
up/commands/status.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""up status - Show system health and status."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.option(
|
|
17
|
+
"--json", "as_json",
|
|
18
|
+
is_flag=True,
|
|
19
|
+
help="Output as JSON",
|
|
20
|
+
)
|
|
21
|
+
def status_cmd(as_json: bool):
|
|
22
|
+
"""Show current status of all up systems.
|
|
23
|
+
|
|
24
|
+
Displays health information for:
|
|
25
|
+
- Context budget (token usage)
|
|
26
|
+
- Circuit breaker states
|
|
27
|
+
- Product loop progress
|
|
28
|
+
- Learning system state
|
|
29
|
+
"""
|
|
30
|
+
cwd = Path.cwd()
|
|
31
|
+
|
|
32
|
+
status = collect_status(cwd)
|
|
33
|
+
|
|
34
|
+
if as_json:
|
|
35
|
+
console.print(json.dumps(status, indent=2))
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Display rich formatted output
|
|
39
|
+
display_status(status)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def collect_status(workspace: Path) -> dict:
|
|
43
|
+
"""Collect status from all systems."""
|
|
44
|
+
status = {
|
|
45
|
+
"workspace": str(workspace),
|
|
46
|
+
"initialized": False,
|
|
47
|
+
"state_version": None,
|
|
48
|
+
"context_budget": None,
|
|
49
|
+
"loop_state": None,
|
|
50
|
+
"circuit_breaker": None,
|
|
51
|
+
"checkpoints": None,
|
|
52
|
+
"agents": None,
|
|
53
|
+
"doom_loop": None,
|
|
54
|
+
"skills": [],
|
|
55
|
+
"hooks": None,
|
|
56
|
+
"memory": None,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Check if initialized
|
|
60
|
+
claude_dir = workspace / ".claude"
|
|
61
|
+
cursor_dir = workspace / ".cursor"
|
|
62
|
+
up_dir = workspace / ".up"
|
|
63
|
+
status["initialized"] = claude_dir.exists() or cursor_dir.exists() or up_dir.exists()
|
|
64
|
+
|
|
65
|
+
if not status["initialized"]:
|
|
66
|
+
return status
|
|
67
|
+
|
|
68
|
+
# Try to load unified state first
|
|
69
|
+
try:
|
|
70
|
+
from up.core.state import get_state_manager
|
|
71
|
+
manager = get_state_manager(workspace)
|
|
72
|
+
state = manager.state
|
|
73
|
+
status["state_version"] = state.version
|
|
74
|
+
|
|
75
|
+
# Context budget from unified state
|
|
76
|
+
status["context_budget"] = {
|
|
77
|
+
"budget": state.context.budget,
|
|
78
|
+
"total_tokens": state.context.total_tokens,
|
|
79
|
+
"remaining_tokens": state.context.remaining_tokens,
|
|
80
|
+
"usage_percent": state.context.usage_percent,
|
|
81
|
+
"status": state.context.status,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Loop state from unified state
|
|
85
|
+
status["loop_state"] = {
|
|
86
|
+
"iteration": state.loop.iteration,
|
|
87
|
+
"phase": state.loop.phase,
|
|
88
|
+
"current_task": state.loop.current_task,
|
|
89
|
+
"tasks_completed": len(state.loop.tasks_completed),
|
|
90
|
+
"tasks_failed": len(state.loop.tasks_failed),
|
|
91
|
+
"success_rate": state.metrics.success_rate,
|
|
92
|
+
"last_checkpoint": state.loop.last_checkpoint,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Circuit breakers
|
|
96
|
+
status["circuit_breaker"] = {
|
|
97
|
+
name: {"state": cb.state, "failures": cb.failures}
|
|
98
|
+
for name, cb in state.circuit_breakers.items()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Checkpoints
|
|
102
|
+
status["checkpoints"] = {
|
|
103
|
+
"total": len(state.checkpoints),
|
|
104
|
+
"last": state.loop.last_checkpoint,
|
|
105
|
+
"recent": state.checkpoints[-5:] if state.checkpoints else [],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Agents
|
|
109
|
+
if state.agents:
|
|
110
|
+
status["agents"] = {
|
|
111
|
+
task_id: {
|
|
112
|
+
"status": agent.status,
|
|
113
|
+
"phase": agent.phase,
|
|
114
|
+
"worktree": agent.worktree_path,
|
|
115
|
+
}
|
|
116
|
+
for task_id, agent in state.agents.items()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Doom loop detection
|
|
120
|
+
is_doom, doom_msg = manager.check_doom_loop()
|
|
121
|
+
if is_doom or state.loop.consecutive_failures > 0:
|
|
122
|
+
status["doom_loop"] = {
|
|
123
|
+
"triggered": is_doom,
|
|
124
|
+
"consecutive_failures": state.loop.consecutive_failures,
|
|
125
|
+
"threshold": state.loop.doom_loop_threshold,
|
|
126
|
+
"message": doom_msg if is_doom else None,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
except ImportError:
|
|
130
|
+
# Fallback to old state files
|
|
131
|
+
_collect_legacy_status(workspace, status)
|
|
132
|
+
|
|
133
|
+
# Git hooks status
|
|
134
|
+
from up.commands.sync import check_hooks_installed
|
|
135
|
+
status["hooks"] = check_hooks_installed(workspace)
|
|
136
|
+
|
|
137
|
+
# Memory status
|
|
138
|
+
memory_dir = workspace / ".up" / "memory"
|
|
139
|
+
if memory_dir.exists():
|
|
140
|
+
try:
|
|
141
|
+
from up.memory import MemoryManager
|
|
142
|
+
manager = MemoryManager(workspace, use_vectors=False)
|
|
143
|
+
stats = manager.get_stats()
|
|
144
|
+
status["memory"] = {
|
|
145
|
+
"total": stats.get("total", 0),
|
|
146
|
+
"branch": stats.get("current_branch", "unknown"),
|
|
147
|
+
"commit": stats.get("current_commit", "unknown"),
|
|
148
|
+
}
|
|
149
|
+
except Exception:
|
|
150
|
+
status["memory"] = {"total": 0}
|
|
151
|
+
|
|
152
|
+
# Skills
|
|
153
|
+
skills_dirs = [
|
|
154
|
+
workspace / ".claude/skills",
|
|
155
|
+
workspace / ".cursor/skills",
|
|
156
|
+
]
|
|
157
|
+
for skills_dir in skills_dirs:
|
|
158
|
+
if skills_dir.exists():
|
|
159
|
+
for skill_dir in skills_dir.iterdir():
|
|
160
|
+
if skill_dir.is_dir() and (skill_dir / "SKILL.md").exists():
|
|
161
|
+
status["skills"].append(skill_dir.name)
|
|
162
|
+
|
|
163
|
+
return status
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _collect_legacy_status(workspace: Path, status: dict) -> None:
|
|
167
|
+
"""Collect status from legacy state files."""
|
|
168
|
+
# Context budget (old location)
|
|
169
|
+
context_file = workspace / ".claude/context_budget.json"
|
|
170
|
+
if context_file.exists():
|
|
171
|
+
try:
|
|
172
|
+
status["context_budget"] = json.loads(context_file.read_text())
|
|
173
|
+
except json.JSONDecodeError:
|
|
174
|
+
status["context_budget"] = {"error": "Invalid JSON"}
|
|
175
|
+
|
|
176
|
+
# Loop state (old location)
|
|
177
|
+
loop_file = workspace / ".loop_state.json"
|
|
178
|
+
if loop_file.exists():
|
|
179
|
+
try:
|
|
180
|
+
data = json.loads(loop_file.read_text())
|
|
181
|
+
status["loop_state"] = {
|
|
182
|
+
"iteration": data.get("iteration", 0),
|
|
183
|
+
"phase": data.get("phase", "UNKNOWN"),
|
|
184
|
+
"current_task": data.get("current_task"),
|
|
185
|
+
"tasks_completed": len(data.get("tasks_completed", [])),
|
|
186
|
+
"tasks_remaining": len(data.get("tasks_remaining", [])),
|
|
187
|
+
"success_rate": data.get("metrics", {}).get("success_rate", 1.0),
|
|
188
|
+
}
|
|
189
|
+
status["circuit_breaker"] = data.get("circuit_breaker", {})
|
|
190
|
+
except json.JSONDecodeError:
|
|
191
|
+
status["loop_state"] = {"error": "Invalid JSON"}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def display_status(status: dict) -> None:
|
|
195
|
+
"""Display status in rich format."""
|
|
196
|
+
|
|
197
|
+
# Header
|
|
198
|
+
workspace_name = Path(status["workspace"]).name
|
|
199
|
+
if status["initialized"]:
|
|
200
|
+
header = f"[bold green]✓[/] {workspace_name} - up systems active"
|
|
201
|
+
else:
|
|
202
|
+
header = f"[bold yellow]○[/] {workspace_name} - not initialized"
|
|
203
|
+
console.print(Panel(header, border_style="yellow"))
|
|
204
|
+
console.print("\nRun [cyan]up init[/] to initialize up systems.")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
console.print(Panel(header, border_style="green"))
|
|
208
|
+
|
|
209
|
+
# Context Budget
|
|
210
|
+
console.print("\n[bold]Context Budget[/]")
|
|
211
|
+
if status["context_budget"]:
|
|
212
|
+
budget = status["context_budget"]
|
|
213
|
+
if "error" in budget:
|
|
214
|
+
console.print(f" [red]Error: {budget['error']}[/]")
|
|
215
|
+
else:
|
|
216
|
+
usage = budget.get("usage_percent", 0)
|
|
217
|
+
remaining = budget.get("remaining_tokens", 0)
|
|
218
|
+
budget_status = budget.get("status", "OK")
|
|
219
|
+
|
|
220
|
+
# Color based on status
|
|
221
|
+
if budget_status == "CRITICAL":
|
|
222
|
+
color = "red"
|
|
223
|
+
icon = "🔴"
|
|
224
|
+
elif budget_status == "WARNING":
|
|
225
|
+
color = "yellow"
|
|
226
|
+
icon = "🟡"
|
|
227
|
+
else:
|
|
228
|
+
color = "green"
|
|
229
|
+
icon = "🟢"
|
|
230
|
+
|
|
231
|
+
console.print(f" {icon} Status: [{color}]{budget_status}[/]")
|
|
232
|
+
console.print(f" Usage: {usage:.1f}% ({remaining:,} tokens remaining)")
|
|
233
|
+
else:
|
|
234
|
+
console.print(" [dim]Not configured[/]")
|
|
235
|
+
|
|
236
|
+
# Circuit Breaker
|
|
237
|
+
console.print("\n[bold]Circuit Breaker[/]")
|
|
238
|
+
if status["circuit_breaker"]:
|
|
239
|
+
cb = status["circuit_breaker"]
|
|
240
|
+
for name, state in cb.items():
|
|
241
|
+
if isinstance(state, dict):
|
|
242
|
+
cb_state = state.get("state", "UNKNOWN")
|
|
243
|
+
failures = state.get("failures", 0)
|
|
244
|
+
|
|
245
|
+
if cb_state == "OPEN":
|
|
246
|
+
icon = "🔴"
|
|
247
|
+
color = "red"
|
|
248
|
+
elif cb_state == "HALF_OPEN":
|
|
249
|
+
icon = "🟡"
|
|
250
|
+
color = "yellow"
|
|
251
|
+
else:
|
|
252
|
+
icon = "🟢"
|
|
253
|
+
color = "green"
|
|
254
|
+
|
|
255
|
+
console.print(f" {icon} {name}: [{color}]{cb_state}[/] (failures: {failures})")
|
|
256
|
+
else:
|
|
257
|
+
console.print(" [dim]Not active[/]")
|
|
258
|
+
|
|
259
|
+
# Product Loop State
|
|
260
|
+
console.print("\n[bold]Product Loop[/]")
|
|
261
|
+
if status["loop_state"]:
|
|
262
|
+
loop = status["loop_state"]
|
|
263
|
+
if "error" in loop:
|
|
264
|
+
console.print(f" [red]Error: {loop['error']}[/]")
|
|
265
|
+
else:
|
|
266
|
+
console.print(f" Iteration: {loop.get('iteration', 0)}")
|
|
267
|
+
console.print(f" Phase: {loop.get('phase', 'UNKNOWN')}")
|
|
268
|
+
|
|
269
|
+
current = loop.get("current_task")
|
|
270
|
+
if current:
|
|
271
|
+
console.print(f" Current Task: [cyan]{current}[/]")
|
|
272
|
+
|
|
273
|
+
completed = loop.get("tasks_completed", 0)
|
|
274
|
+
failed = loop.get("tasks_failed", 0)
|
|
275
|
+
remaining = loop.get("tasks_remaining", 0)
|
|
276
|
+
total = completed + failed + remaining if remaining else completed + failed
|
|
277
|
+
|
|
278
|
+
if total > 0:
|
|
279
|
+
progress = completed / total * 100
|
|
280
|
+
bar_len = 20
|
|
281
|
+
filled = int(bar_len * completed / total)
|
|
282
|
+
bar = "█" * filled + "░" * (bar_len - filled)
|
|
283
|
+
console.print(f" Progress: [{bar}] {progress:.0f}% ({completed}/{total})")
|
|
284
|
+
|
|
285
|
+
success_rate = loop.get("success_rate", 1.0)
|
|
286
|
+
console.print(f" Success Rate: {success_rate * 100:.0f}%")
|
|
287
|
+
|
|
288
|
+
last_cp = loop.get("last_checkpoint")
|
|
289
|
+
if last_cp:
|
|
290
|
+
console.print(f" Last Checkpoint: [cyan]{last_cp}[/]")
|
|
291
|
+
else:
|
|
292
|
+
console.print(" [dim]Not active[/]")
|
|
293
|
+
|
|
294
|
+
# Doom Loop Detection
|
|
295
|
+
if status.get("doom_loop"):
|
|
296
|
+
doom = status["doom_loop"]
|
|
297
|
+
console.print("\n[bold]Doom Loop Detection[/]")
|
|
298
|
+
if doom["triggered"]:
|
|
299
|
+
console.print(f" [red]⚠ TRIGGERED[/] - {doom['consecutive_failures']}/{doom['threshold']} failures")
|
|
300
|
+
console.print(f" [dim]{doom.get('message', '')}[/]")
|
|
301
|
+
else:
|
|
302
|
+
console.print(f" Consecutive Failures: {doom['consecutive_failures']}/{doom['threshold']}")
|
|
303
|
+
|
|
304
|
+
# Checkpoints
|
|
305
|
+
if status.get("checkpoints"):
|
|
306
|
+
cp = status["checkpoints"]
|
|
307
|
+
console.print("\n[bold]Checkpoints[/]")
|
|
308
|
+
console.print(f" Total: {cp['total']}")
|
|
309
|
+
if cp.get("recent"):
|
|
310
|
+
console.print(f" Recent: {', '.join(cp['recent'][-3:])}")
|
|
311
|
+
|
|
312
|
+
# Active Agents
|
|
313
|
+
if status.get("agents"):
|
|
314
|
+
console.print("\n[bold]Active Agents[/]")
|
|
315
|
+
for task_id, agent in status["agents"].items():
|
|
316
|
+
status_icon = "🟢" if agent["status"] == "passed" else "🟡" if agent["status"] in ["executing", "verifying"] else "🔴"
|
|
317
|
+
console.print(f" {status_icon} {task_id}: {agent['status']} ({agent['phase']})")
|
|
318
|
+
|
|
319
|
+
# Memory
|
|
320
|
+
console.print("\n[bold]Memory[/]")
|
|
321
|
+
if status["memory"]:
|
|
322
|
+
mem = status["memory"]
|
|
323
|
+
total = mem.get("total", 0)
|
|
324
|
+
branch = mem.get("branch", "unknown")
|
|
325
|
+
commit = mem.get("commit", "unknown")
|
|
326
|
+
console.print(f" 📚 {total} entries | Branch: [cyan]{branch}[/] @ {commit}")
|
|
327
|
+
else:
|
|
328
|
+
console.print(" [dim]Not initialized - run [cyan]up memory sync[/][/]")
|
|
329
|
+
|
|
330
|
+
# Git Hooks
|
|
331
|
+
console.print("\n[bold]Auto-Sync (Git Hooks)[/]")
|
|
332
|
+
hooks = status.get("hooks", {})
|
|
333
|
+
if hooks.get("git"):
|
|
334
|
+
post_commit = hooks.get("post_commit", False)
|
|
335
|
+
post_checkout = hooks.get("post_checkout", False)
|
|
336
|
+
|
|
337
|
+
if post_commit and post_checkout:
|
|
338
|
+
console.print(" [green]✓ Enabled[/] - commits auto-indexed to memory")
|
|
339
|
+
else:
|
|
340
|
+
console.print(" [yellow]⚠ Partially installed[/]")
|
|
341
|
+
if not post_commit:
|
|
342
|
+
console.print(" • Missing: post-commit hook")
|
|
343
|
+
if not post_checkout:
|
|
344
|
+
console.print(" • Missing: post-checkout hook")
|
|
345
|
+
console.print("\n [dim]Run [cyan]up hooks[/] to install missing hooks[/]")
|
|
346
|
+
else:
|
|
347
|
+
console.print(" [yellow]✗ Not installed[/]")
|
|
348
|
+
console.print(" [dim]Run [cyan]up hooks[/] to enable auto-sync on commits[/]")
|
|
349
|
+
|
|
350
|
+
# Skills
|
|
351
|
+
console.print("\n[bold]Skills[/]")
|
|
352
|
+
if status["skills"]:
|
|
353
|
+
for skill in status["skills"]:
|
|
354
|
+
console.print(f" • {skill}")
|
|
355
|
+
else:
|
|
356
|
+
console.print(" [dim]No skills installed[/]")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
if __name__ == "__main__":
|
|
360
|
+
status_cmd()
|
up/commands/summarize.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""up summarize - Summarize AI conversation history."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option(
|
|
15
|
+
"--format", "-f",
|
|
16
|
+
type=click.Choice(["markdown", "json"]),
|
|
17
|
+
default="markdown",
|
|
18
|
+
help="Output format",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--output", "-o",
|
|
22
|
+
type=click.Path(),
|
|
23
|
+
help="Output file path",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--project", "-p",
|
|
27
|
+
help="Filter by project path",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--source",
|
|
31
|
+
type=click.Choice(["cursor", "claude", "all"]),
|
|
32
|
+
default="cursor",
|
|
33
|
+
help="Conversation source to analyze",
|
|
34
|
+
)
|
|
35
|
+
def summarize_cmd(format: str, output: str, project: str, source: str):
|
|
36
|
+
"""Summarize AI conversation history.
|
|
37
|
+
|
|
38
|
+
Analyzes your Cursor or Claude chat history to extract:
|
|
39
|
+
- Common topics and patterns
|
|
40
|
+
- Frequent errors encountered
|
|
41
|
+
- Actionable insights
|
|
42
|
+
- Code snippets
|
|
43
|
+
|
|
44
|
+
\b
|
|
45
|
+
Examples:
|
|
46
|
+
up summarize # Markdown to stdout
|
|
47
|
+
up summarize -f json -o out.json # JSON to file
|
|
48
|
+
up summarize -p myproject # Filter by project
|
|
49
|
+
"""
|
|
50
|
+
console.print(Panel.fit(
|
|
51
|
+
"[bold blue]Conversation Summarizer[/]",
|
|
52
|
+
border_style="blue"
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
if source in ("cursor", "all"):
|
|
57
|
+
result = _summarize_cursor(format, project)
|
|
58
|
+
_output_result(result, output, format)
|
|
59
|
+
|
|
60
|
+
if source == "claude":
|
|
61
|
+
console.print("[yellow]Claude history summarization not yet implemented.[/]")
|
|
62
|
+
console.print("Use [cyan]--source cursor[/] for Cursor history.")
|
|
63
|
+
|
|
64
|
+
except FileNotFoundError as e:
|
|
65
|
+
console.print(f"[red]Error:[/] {e}")
|
|
66
|
+
console.print("\nMake sure Cursor is installed and has chat history.")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
console.print(f"[red]Error:[/] {e}")
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _summarize_cursor(format: str, project_filter: str = None) -> str:
|
|
74
|
+
"""Summarize Cursor conversation history."""
|
|
75
|
+
# Add scripts to path
|
|
76
|
+
scripts_path = Path(__file__).parent.parent.parent.parent / "scripts"
|
|
77
|
+
if scripts_path.exists():
|
|
78
|
+
sys.path.insert(0, str(scripts_path))
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
from export_cursor_history import load_all_data
|
|
82
|
+
except ImportError:
|
|
83
|
+
raise FileNotFoundError(
|
|
84
|
+
"Could not import export_cursor_history. "
|
|
85
|
+
"Make sure scripts/export_cursor_history.py exists."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
from up.summarizer import ConversationSummarizer
|
|
89
|
+
|
|
90
|
+
console.print("Loading Cursor history...", style="dim")
|
|
91
|
+
conversations = load_all_data(project_filter=project_filter)
|
|
92
|
+
|
|
93
|
+
if not conversations:
|
|
94
|
+
raise ValueError("No conversations found in Cursor history.")
|
|
95
|
+
|
|
96
|
+
console.print(f"Found [cyan]{len(conversations)}[/] conversations")
|
|
97
|
+
console.print("Analyzing...", style="dim")
|
|
98
|
+
|
|
99
|
+
summarizer = ConversationSummarizer(conversations)
|
|
100
|
+
|
|
101
|
+
if format == "json":
|
|
102
|
+
return summarizer.to_json()
|
|
103
|
+
return summarizer.to_markdown()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _output_result(result: str, output_path: str, format: str) -> None:
|
|
107
|
+
"""Output result to file or stdout."""
|
|
108
|
+
if output_path:
|
|
109
|
+
Path(output_path).write_text(result)
|
|
110
|
+
console.print(f"\n[green]✓[/] Summary saved to [cyan]{output_path}[/]")
|
|
111
|
+
else:
|
|
112
|
+
console.print("\n")
|
|
113
|
+
if format == "markdown":
|
|
114
|
+
# Use rich markdown rendering
|
|
115
|
+
from rich.markdown import Markdown
|
|
116
|
+
console.print(Markdown(result))
|
|
117
|
+
else:
|
|
118
|
+
console.print(result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
summarize_cmd()
|