bizy-ai 1.0.2__py3-none-any.whl → 1.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.
agent/cli.py CHANGED
@@ -2,6 +2,8 @@
2
2
  """Business Agent CLI Tool"""
3
3
 
4
4
  import click
5
+ import os
6
+ import sys
5
7
  from agent.tasks import TaskManager
6
8
  from agent.planner import BusinessPlanner
7
9
  from agent.research import ResearchAgent
@@ -9,6 +11,7 @@ from rich.console import Console
9
11
  from rich.table import Table
10
12
  from rich.panel import Panel
11
13
  from rich.markdown import Markdown
14
+ from rich.prompt import Prompt, Confirm
12
15
  from datetime import datetime, timedelta
13
16
  from dotenv import load_dotenv
14
17
 
@@ -32,42 +35,172 @@ def task():
32
35
  @click.option('--priority', '-p', type=int, default=3)
33
36
  @click.option('--category', '-c', help='Task category')
34
37
  @click.option('--hours', '-h', type=float, help='Estimated hours')
35
- def add(title, description, priority, category, hours):
38
+ @click.option('--goal', '-g', type=int, help='Goal ID to assign task to')
39
+ def add(title, description, priority, category, hours, goal):
36
40
  """Add a new task"""
37
41
  task_mgr = TaskManager()
42
+ planner = BusinessPlanner()
43
+
44
+ goal_id = goal
45
+
46
+ # If no goal specified, prompt user to select or create one
47
+ if goal_id is None:
48
+ goals = planner.get_active_goals()
49
+
50
+ if goals:
51
+ console.print("\n[bold]📋 Available Goals:[/bold]")
52
+ goals_table = Table(show_header=True, header_style="bold cyan")
53
+ goals_table.add_column("ID", style="dim", width=6)
54
+ goals_table.add_column("Goal")
55
+ goals_table.add_column("Progress", justify="right", width=15)
56
+
57
+ for g in goals[:10]: # Show max 10 goals
58
+ progress_bar = "█" * int(g.progress_percentage / 10) + "░" * (10 - int(g.progress_percentage / 10))
59
+ goals_table.add_row(
60
+ str(g.id),
61
+ g.title[:50],
62
+ f"{progress_bar} {g.progress_percentage:.0f}%"
63
+ )
64
+ console.print(goals_table)
65
+
66
+ # Prompt user
67
+ console.print("\n[bold]Options:[/bold]")
68
+ console.print(" • Enter [cyan]goal ID[/cyan] to assign task to that goal")
69
+ console.print(" • Press [dim]Enter[/dim] to skip (create task without goal)")
70
+ console.print(" • Type [yellow]'new'[/yellow] to create a new goal")
71
+
72
+ choice = Prompt.ask("\n[bold]Your choice[/bold]", default="")
73
+
74
+ if choice.lower() == 'new':
75
+ # Create new goal interactively
76
+ console.print("\n[bold cyan]Creating New Goal[/bold cyan]")
77
+ goal_title = Prompt.ask("Goal title")
78
+ goal_horizon = Prompt.ask(
79
+ "Horizon",
80
+ choices=["daily", "weekly", "monthly", "quarterly", "yearly"],
81
+ default="monthly"
82
+ )
83
+
84
+ try:
85
+ new_goal = planner.create_goal(
86
+ title=goal_title,
87
+ horizon=goal_horizon
88
+ )
89
+ goal_id = new_goal.id
90
+ console.print(f"[green]✓[/green] Created goal: {new_goal.title} (ID: {new_goal.id})\n")
91
+ except Exception as e:
92
+ console.print(f"[red]✗[/red] Error creating goal: {e}")
93
+ console.print("[yellow]Creating task without goal assignment[/yellow]\n")
94
+ goal_id = None
95
+ elif choice and choice.isdigit():
96
+ goal_id = int(choice)
97
+ # Validate goal exists
98
+ goal_obj = planner.get_goal(goal_id)
99
+ if not goal_obj:
100
+ console.print(f"[yellow]⚠[/yellow] Goal #{goal_id} not found. Creating task without goal.\n")
101
+ goal_id = None
102
+ else:
103
+ console.print("\n[yellow]No active goals found.[/yellow]")
104
+ if Confirm.ask("Would you like to create a new goal?", default=False):
105
+ console.print("\n[bold cyan]Creating New Goal[/bold cyan]")
106
+ goal_title = Prompt.ask("Goal title")
107
+ goal_horizon = Prompt.ask(
108
+ "Horizon",
109
+ choices=["daily", "weekly", "monthly", "quarterly", "yearly"],
110
+ default="monthly"
111
+ )
112
+
113
+ try:
114
+ new_goal = planner.create_goal(
115
+ title=goal_title,
116
+ horizon=goal_horizon
117
+ )
118
+ goal_id = new_goal.id
119
+ console.print(f"[green]✓[/green] Created goal: {new_goal.title} (ID: {new_goal.id})\n")
120
+ except Exception as e:
121
+ console.print(f"[red]✗[/red] Error creating goal: {e}\n")
122
+ goal_id = None
123
+
124
+ # Create task with goal assignment
38
125
  task = task_mgr.create_task(
39
126
  title=title,
40
127
  description=description,
41
128
  priority=priority,
42
129
  category=category,
43
- estimated_hours=hours
130
+ estimated_hours=hours,
131
+ parent_goal_id=goal_id
44
132
  )
133
+
45
134
  console.print(f"[green]✓[/green] Task created: {task.title} (ID: {task.id})")
135
+ if goal_id:
136
+ console.print(f"[dim] Assigned to goal #{goal_id}[/dim]")
137
+ # Recalculate goal progress
138
+ planner.calculate_goal_progress(goal_id)
139
+
46
140
  task_mgr.close()
141
+ planner.close()
47
142
 
48
143
  @task.command()
49
- def list():
50
- """List all pending tasks"""
144
+ @click.option('--filter', '-f', type=click.Choice(['all', 'pending', 'completed', 'today']), default='all', help='Filter tasks')
145
+ @click.option('--goal', '-g', type=int, help='Filter by goal ID')
146
+ def list(filter, goal):
147
+ """List tasks with optional filters"""
51
148
  task_mgr = TaskManager()
52
- tasks = task_mgr.get_tasks_for_today()
149
+
150
+ # Get tasks based on filter
151
+ if filter == 'all':
152
+ from agent.models import Task
153
+ if goal:
154
+ tasks = task_mgr.session.query(Task).filter_by(parent_goal_id=goal).all()
155
+ else:
156
+ tasks = task_mgr.session.query(Task).all()
157
+ elif filter == 'completed':
158
+ from agent.models import Task
159
+ query = task_mgr.session.query(Task).filter_by(status='completed')
160
+ if goal:
161
+ query = query.filter_by(parent_goal_id=goal)
162
+ tasks = query.all()
163
+ elif filter == 'pending':
164
+ from agent.models import Task
165
+ query = task_mgr.session.query(Task).filter(Task.status.in_(['pending', 'in_progress']))
166
+ if goal:
167
+ query = query.filter_by(parent_goal_id=goal)
168
+ tasks = query.all()
169
+ else: # today
170
+ tasks = task_mgr.get_tasks_for_today()
53
171
 
54
172
  if not tasks:
55
- console.print("[yellow]No pending tasks[/yellow]")
173
+ console.print(f"[yellow]No {filter} tasks found[/yellow]")
174
+ task_mgr.close()
56
175
  return
57
176
 
58
- table = Table(title="📋 Your Tasks", show_header=True, header_style="bold cyan")
177
+ # Build title
178
+ title_parts = ["📋 Your Tasks"]
179
+ if filter != 'all':
180
+ title_parts.append(f"({filter.title()})")
181
+ if goal:
182
+ title_parts.append(f"for Goal #{goal}")
183
+
184
+ table = Table(title=" ".join(title_parts), show_header=True, header_style="bold cyan")
59
185
  table.add_column("ID", style="dim")
186
+ table.add_column("Status", justify="center")
60
187
  table.add_column("Priority", justify="center")
61
188
  table.add_column("Title")
62
189
  table.add_column("Category", style="cyan")
190
+ table.add_column("Due Date", style="dim")
63
191
 
64
192
  for task in tasks:
65
- priority_str = "🔴" if task.priority == 1 else "🟡" if task.priority == 3 else "🟢"
193
+ status_icon = "" if task.status == 'completed' else ""
194
+ priority_str = "🔴" if task.priority == 1 else "🟡" if task.priority == 2 else "🟢"
195
+ due_date = task.due_date.strftime('%Y-%m-%d') if task.due_date else "-"
196
+
66
197
  table.add_row(
67
198
  str(task.id),
199
+ status_icon,
68
200
  priority_str,
69
201
  task.title[:50],
70
- task.category or "-"
202
+ task.category or "-",
203
+ due_date
71
204
  )
72
205
 
73
206
  console.print(table)
@@ -81,6 +214,14 @@ def complete(task_id):
81
214
  task = task_mgr.complete_task(task_id)
82
215
  if task:
83
216
  console.print(f"[green]✓[/green] Completed: {task.title}")
217
+
218
+ # Update goal progress if task is associated with a goal
219
+ if task.parent_goal_id:
220
+ from agent.planner import BusinessPlanner
221
+ planner = BusinessPlanner()
222
+ progress = planner.calculate_goal_progress(task.parent_goal_id)
223
+ console.print(f"[dim]Goal progress updated: {progress:.1f}%[/dim]")
224
+ planner.close()
84
225
  else:
85
226
  console.print(f"[red]✗[/red] Task {task_id} not found")
86
227
  task_mgr.close()
@@ -120,8 +261,16 @@ def list():
120
261
 
121
262
  if not goals:
122
263
  console.print("[yellow]No active goals[/yellow]")
264
+ planner.close()
123
265
  return
124
266
 
267
+ # Recalculate progress for all goals
268
+ for goal in goals:
269
+ planner.calculate_goal_progress(goal.id)
270
+
271
+ # Refresh goals after recalculation
272
+ goals = planner.get_active_goals()
273
+
125
274
  table = Table(title="🎯 Your Goals", show_header=True, header_style="bold cyan")
126
275
  table.add_column("ID", style="dim")
127
276
  table.add_column("Title")
@@ -212,16 +361,28 @@ def competitors(domain, offering):
212
361
  def stats():
213
362
  """Show statistics"""
214
363
  task_mgr = TaskManager()
215
- weekly_stats = task_mgr.get_weekly_stats()
364
+ weekly_stats = task_mgr.get_weekly_task_stats()
216
365
  velocity = task_mgr.get_task_velocity()
217
366
  today_tasks = task_mgr.get_tasks_for_today()
218
367
 
219
368
  console.print("\n[bold cyan]📊 Your Statistics[/bold cyan]\n")
220
369
  console.print("[bold]This Week:[/bold]")
221
- console.print(f" • Tasks Completed: {weekly_stats['total_tasks_completed']}")
222
- console.print(f" • Completion Rate: {weekly_stats['average_completion_rate']:.0%}")
370
+ console.print(f" • Tasks Completed: {weekly_stats['tasks_completed_this_week']}")
371
+ console.print(f" • Tasks Created: {weekly_stats['tasks_created_this_week']}")
372
+ console.print(f" • Completion Rate: {weekly_stats['completion_rate']:.1f}%")
373
+ console.print(f" • Total Hours (Estimated): {weekly_stats['total_estimated_hours']:.1f}h")
374
+ if weekly_stats['total_actual_hours'] > 0:
375
+ console.print(f" • Total Hours (Actual): {weekly_stats['total_actual_hours']:.1f}h")
223
376
  console.print(f"\n[bold]Velocity:[/bold] {velocity:.1f} tasks/day")
224
377
  console.print(f"\n[bold]Today:[/bold] {len(today_tasks)} tasks scheduled\n")
378
+
379
+ # Show breakdown by category if available
380
+ if weekly_stats['categories']:
381
+ console.print("[bold]By Category:[/bold]")
382
+ for category, count in sorted(weekly_stats['categories'].items(), key=lambda x: x[1], reverse=True):
383
+ console.print(f" • {category}: {count} task(s)")
384
+ console.print()
385
+
225
386
  task_mgr.close()
226
387
 
227
388
  # DAILY/WEEKLY REVIEWS
@@ -267,5 +428,16 @@ def update():
267
428
  from agent.plan_manager import update_business_plan
268
429
  update_business_plan()
269
430
 
431
+ @plan.command()
432
+ @click.argument('file_path', required=False)
433
+ @click.option('--name', '-n', help='Business plan name')
434
+ def load(file_path, name):
435
+ """Load business plan from YAML file"""
436
+ # Import and run the standalone script
437
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
438
+ sys.path.insert(0, repo_root)
439
+ from scripts.load_business_plan import load_business_plan
440
+ load_business_plan(file_path, name)
441
+
270
442
  if __name__ == "__main__":
271
443
  cli()
agent/models.py CHANGED
@@ -158,8 +158,9 @@ class BusinessMetric(Base):
158
158
 
159
159
  class BusinessPlan(Base):
160
160
  __tablename__ = 'business_plans'
161
-
161
+
162
162
  id = Column(Integer, primary_key=True)
163
+ name = Column(String(255))
163
164
  version = Column(String(50))
164
165
  vision = Column(Text)
165
166
  mission = Column(Text)
@@ -171,6 +172,7 @@ class BusinessPlan(Base):
171
172
  key_partnerships = Column(JSON)
172
173
  cost_structure = Column(JSON)
173
174
  created_at = Column(DateTime, default=datetime.utcnow)
175
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
174
176
  is_active = Column(Boolean, default=True)
175
177
 
176
178
  def to_dict(self):
@@ -193,15 +195,28 @@ class BusinessPlan(Base):
193
195
  # Database setup
194
196
  def get_engine(db_path=None):
195
197
  if db_path is None:
196
- # Use environment variable or default to home directory
197
- db_path = os.getenv('BUSINESS_AGENT_DB', os.path.expanduser('~/.business-agent/tasks.db'))
198
+ # Determine database path based on environment
199
+ env = os.getenv('BIZY_ENV', 'production')
200
+
201
+ if env == 'test':
202
+ # Test environment uses in-memory database (overridden by conftest.py)
203
+ return create_engine('sqlite:///:memory:', echo=False)
204
+ elif env == 'development':
205
+ # Development environment uses separate database
206
+ db_path = os.getenv('BUSINESS_AGENT_DB', os.path.expanduser('~/.business-agent/dev_tasks.db'))
207
+ else: # production
208
+ # Production environment uses main database
209
+ db_path = os.getenv('BUSINESS_AGENT_DB', os.path.expanduser('~/.business-agent/tasks.db'))
198
210
 
199
- # Create directory if it doesn't exist
200
- db_dir = os.path.dirname(db_path)
201
- if db_dir:
202
- os.makedirs(db_dir, exist_ok=True)
211
+ # Create directory if it doesn't exist (not needed for in-memory)
212
+ if db_path and db_path != ':memory:':
213
+ db_dir = os.path.dirname(db_path)
214
+ if db_dir:
215
+ os.makedirs(db_dir, exist_ok=True)
216
+ return create_engine(f'sqlite:///{db_path}', echo=False)
203
217
 
204
- return create_engine(f'sqlite:///{db_path}', echo=False)
218
+ # In-memory database
219
+ return create_engine('sqlite:///:memory:', echo=False)
205
220
 
206
221
  def get_session(engine=None):
207
222
  if engine is None:
agent/plan_manager.py CHANGED
@@ -6,11 +6,13 @@ Review and update business plans, goals, and tasks
6
6
 
7
7
  import sys
8
8
  import os
9
+ import yaml
9
10
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
10
11
 
11
12
  from agent.core import BusinessAgent
12
13
  from agent.planner import BusinessPlanner
13
14
  from agent.tasks import TaskManager
15
+ from agent.models import get_session, BusinessPlan
14
16
  from rich.console import Console
15
17
  from rich.panel import Panel
16
18
  from rich.table import Table
@@ -64,6 +66,13 @@ def review_business_plan():
64
66
  console.print("\n[bold]🎯 Goals Overview[/bold]\n")
65
67
  all_goals = planner.get_active_goals()
66
68
 
69
+ # Recalculate progress for all goals
70
+ for goal in all_goals:
71
+ planner.calculate_goal_progress(goal.id)
72
+
73
+ # Refresh goals after recalculation
74
+ all_goals = planner.get_active_goals()
75
+
67
76
  if all_goals:
68
77
  goals_table = Table(show_header=True, header_style="bold cyan")
69
78
  goals_table.add_column("Horizon")
@@ -87,11 +96,11 @@ def review_business_plan():
87
96
 
88
97
  # Get task summary
89
98
  console.print("\n[bold]📋 Task Summary[/bold]\n")
90
- weekly_stats = task_mgr.get_weekly_stats()
99
+ weekly_stats = task_mgr.get_weekly_task_stats()
91
100
  today_tasks = task_mgr.get_tasks_for_today()
92
101
  overdue = task_mgr.get_overdue_tasks()
93
102
 
94
- console.print(f" • This Week: {weekly_stats['total_tasks_completed']}/{weekly_stats['total_tasks_planned']} completed ({weekly_stats['average_completion_rate']:.0%})")
103
+ console.print(f" • This Week: {weekly_stats['tasks_completed_this_week']} completed ({weekly_stats['completion_rate']:.1f}% completion rate)")
95
104
  console.print(f" • Today: {len(today_tasks)} tasks scheduled")
96
105
  console.print(f" • Overdue: {len(overdue)} tasks")
97
106
 
@@ -114,7 +123,9 @@ GOALS PROGRESS:
114
123
  {goals_summary}
115
124
 
116
125
  EXECUTION METRICS:
117
- - Weekly Completion Rate: {weekly_stats['average_completion_rate']:.0%}
126
+ - Tasks Completed This Week: {weekly_stats['tasks_completed_this_week']}
127
+ - Tasks Created This Week: {weekly_stats['tasks_created_this_week']}
128
+ - Completion Rate: {weekly_stats['completion_rate']:.1f}%
118
129
  - Tasks Overdue: {len(overdue)}
119
130
 
120
131
  Provide:
agent/tasks.py CHANGED
@@ -233,19 +233,104 @@ class TaskManager:
233
233
  }
234
234
 
235
235
  def get_task_velocity(self, days=30):
236
- """Calculate average tasks completed per day over a period"""
237
- start_date = datetime.now() - timedelta(days=days)
238
-
239
- logs = self.session.query(DailyLog).filter(
240
- DailyLog.date >= start_date
241
- ).all()
242
-
243
- if not logs:
244
- return 0
245
-
246
- total_completed = sum(log.tasks_completed for log in logs)
247
- return total_completed / len(logs) if logs else 0
248
-
236
+ """
237
+ Calculate average tasks completed per day over a period.
238
+ Uses actual completed_at timestamps for accurate velocity calculation.
239
+ """
240
+ # Get tasks completed in the specified period
241
+ completed_tasks = self.get_completed_tasks_this_week(days=days)
242
+
243
+ if not completed_tasks:
244
+ return 0.0
245
+
246
+ # Calculate velocity as tasks per day
247
+ velocity = len(completed_tasks) / days
248
+ return velocity
249
+
250
+ def get_completed_tasks_this_week(self, days=7):
251
+ """Get tasks completed in the last N days based on completed_at timestamp"""
252
+ # Use UTC time to match database timestamps
253
+ now = datetime.utcnow()
254
+ start_date = now - timedelta(days=days)
255
+
256
+ completed_tasks = self.session.query(Task).filter(
257
+ and_(
258
+ Task.status == 'completed',
259
+ Task.completed_at >= start_date,
260
+ Task.completed_at <= now
261
+ )
262
+ ).order_by(Task.completed_at.desc()).all()
263
+
264
+ return completed_tasks
265
+
266
+ def get_created_tasks_this_week(self, days=7):
267
+ """Get tasks created in the last N days based on created_at timestamp"""
268
+ # Use UTC time to match database timestamps
269
+ now = datetime.utcnow()
270
+ start_date = now - timedelta(days=days)
271
+
272
+ created_tasks = self.session.query(Task).filter(
273
+ and_(
274
+ Task.created_at >= start_date,
275
+ Task.created_at <= now
276
+ )
277
+ ).order_by(Task.created_at.desc()).all()
278
+
279
+ return created_tasks
280
+
281
+ def get_weekly_task_stats(self, days=7):
282
+ """
283
+ Get weekly statistics based on actual task completion dates (completed_at).
284
+ This is more accurate than DailyLog-based stats as it reflects actual work completed.
285
+ """
286
+ completed_tasks = self.get_completed_tasks_this_week(days)
287
+ created_tasks = self.get_created_tasks_this_week(days)
288
+
289
+ # Calculate statistics
290
+ tasks_completed = len(completed_tasks)
291
+ tasks_created = len(created_tasks)
292
+
293
+ # Calculate completion rate (completed vs created this week)
294
+ completion_rate = (tasks_completed / tasks_created * 100) if tasks_created > 0 else 0
295
+
296
+ # Calculate total estimated hours
297
+ total_estimated_hours = sum(
298
+ task.estimated_hours or 0 for task in completed_tasks
299
+ )
300
+
301
+ # Calculate total actual hours
302
+ total_actual_hours = sum(
303
+ task.actual_hours or 0 for task in completed_tasks
304
+ )
305
+
306
+ # Break down by category
307
+ categories = {}
308
+ for task in completed_tasks:
309
+ category = task.category or 'uncategorized'
310
+ if category not in categories:
311
+ categories[category] = 0
312
+ categories[category] += 1
313
+
314
+ # Break down by priority
315
+ priorities = {}
316
+ for task in completed_tasks:
317
+ priority = task.priority
318
+ if priority not in priorities:
319
+ priorities[priority] = 0
320
+ priorities[priority] += 1
321
+
322
+ return {
323
+ 'tasks_completed_this_week': tasks_completed,
324
+ 'tasks_created_this_week': tasks_created,
325
+ 'completion_rate': completion_rate,
326
+ 'total_estimated_hours': total_estimated_hours,
327
+ 'total_actual_hours': total_actual_hours,
328
+ 'completed_tasks': [task.to_dict() for task in completed_tasks],
329
+ 'categories': categories,
330
+ 'priorities': priorities,
331
+ 'period_days': days
332
+ }
333
+
249
334
  def close(self):
250
335
  """Close the database session"""
251
336
  self.session.close()
agent/weekly_review.py CHANGED
@@ -25,22 +25,22 @@ def run_weekly_review():
25
25
  task_mgr = TaskManager()
26
26
  planner = BusinessPlanner()
27
27
 
28
- # Get weekly statistics
29
- weekly_stats = task_mgr.get_weekly_stats()
30
-
28
+ # Get weekly statistics based on actual task completions
29
+ weekly_stats = task_mgr.get_weekly_task_stats()
30
+
31
31
  # Get goal progress
32
32
  active_goals = planner.get_active_goals()
33
33
  goals_progress = "\n".join([
34
34
  f"- {g.title}: {g.progress_percentage:.0f}% complete"
35
35
  for g in active_goals[:5]
36
36
  ]) if active_goals else "No active goals"
37
-
38
- # Get key events
39
- key_events = []
40
- for log in weekly_stats.get('logs', []):
41
- if log.get('wins'):
42
- key_events.append(f"Win: {log['wins']}")
43
- key_events_str = "\n".join(key_events[-10:]) if key_events else "No events logged"
37
+
38
+ # Get key events from completed tasks
39
+ completed_tasks = weekly_stats.get('completed_tasks', [])
40
+ key_events_str = "\n".join([
41
+ f"✓ {task['title']}" + (f" ({task['category']})" if task['category'] else "")
42
+ for task in completed_tasks[:10]
43
+ ]) if completed_tasks else "No tasks completed this week"
44
44
 
45
45
  # Generate review
46
46
  console.print("[dim]Generating comprehensive analysis...[/dim]\n")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizy-ai
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: AI-powered business planning and execution agent
5
5
  Author: Reid Chatham
6
6
  License: MIT
@@ -37,6 +37,13 @@ Requires-Dist: rich>=13.0.0
37
37
  Requires-Dist: python-dateutil>=2.8.2
38
38
  Requires-Dist: click>=8.1.0
39
39
  Requires-Dist: jinja2>=3.1.2
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
42
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
43
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
44
+ Requires-Dist: pytest-watch>=4.2.0; extra == "dev"
45
+ Requires-Dist: black>=23.0.0; extra == "dev"
46
+ Requires-Dist: flake8>=6.1.0; extra == "dev"
40
47
  Dynamic: license-file
41
48
  Dynamic: requires-python
42
49
 
@@ -88,7 +95,50 @@ pip install -e .
88
95
  ./setup.sh
89
96
  ```
90
97
 
91
- See **[INSTALL.md](INSTALL.md)** for detailed installation options.
98
+ See **[INSTALL.md](docs/INSTALL.md)** for detailed installation options.
99
+
100
+ ---
101
+
102
+ ## Development
103
+
104
+ ### Test-Driven Development (TDD)
105
+
106
+ This project follows TDD principles. All new features must:
107
+
108
+ 1. **Write tests first** - Define expected behavior
109
+ 2. **Run tests (they should fail)** - Red phase
110
+ 3. **Implement feature** - Green phase
111
+ 4. **Refactor** - Clean up code
112
+
113
+ ### Running Tests
114
+
115
+ ```bash
116
+ # Run all tests with coverage
117
+ make test
118
+
119
+ # Run tests in watch mode
120
+ make test-watch
121
+
122
+ # Run specific test file
123
+ pytest tests/test_tasks.py -v
124
+ ```
125
+
126
+ ### Database Environments
127
+
128
+ - **Production**: `~/.business-agent/tasks.db` - Your actual business data 🔒
129
+ - **Development**: `~/.business-agent/dev_tasks.db` - Safe for experimentation ⚙️
130
+ - **Test**: In-memory - Isolated, clean for each test 🧪
131
+
132
+ ```bash
133
+ # Use development database
134
+ export BIZY_ENV=development
135
+ make dev
136
+
137
+ # Use production database (default)
138
+ export BIZY_ENV=production
139
+ ```
140
+
141
+ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for detailed development guidelines.
92
142
 
93
143
  ---
94
144
 
@@ -0,0 +1,21 @@
1
+ agent/__init__.py,sha256=pHeIDi-ibuwZqaNqxdIDRvzedWtPywssbaYIDawMcvQ,88
2
+ agent/cli.py,sha256=J-TlTArbSD2eHkn93Kt7Y7YXZb84NJPGj3XDRgUPsZw,15186
3
+ agent/core.py,sha256=UILN_DjsSr91KN3YyQaD_cZgF9vW8m827-W0pZMTUnM,6561
4
+ agent/evening_review.py,sha256=Y4d-t62zfaufM8mnSvkhBBZ0wMw1R8jtj4cN8r30t4E,4929
5
+ agent/models.py,sha256=Et7GDIW8E-S444j0AbqoFrvNXL4hJ9iWPM989CS34E0,8727
6
+ agent/morning_brief.py,sha256=zcu4nUOnIQtoZTiK1XlEjuudiUAABrps3PvAwt2Avmk,4732
7
+ agent/plan_manager.py,sha256=7ipJBW-QS308dSpULjJGcDPjsXKA-yVh1mljFejCv_U,12225
8
+ agent/planner.py,sha256=-KlyZ-2jMQTUy0QVZCH_NuubENU-UrPXVhat3ju54ew,13114
9
+ agent/research.py,sha256=HvqGHENQ0v1lJadGYNuf3eb6fZoR6qzzIPzqcsnhFSE,6429
10
+ agent/tasks.py,sha256=PzisO_2a7Hgplxf3CxnGYLG-Fg8O9ylHYC5193cHyZo,12155
11
+ agent/weekly_review.py,sha256=83fYwe3hKEhqAQfMlj7WmvvMhTNHtUnqr1PFXCHRGWo,2394
12
+ bizy_ai-1.1.0.dist-info/licenses/LICENSE,sha256=__BSNgmbeWQ1IA57XKyGhQajUNcF-pZjvBAY268fCWM,1069
13
+ scripts/init_db.py,sha256=lF1rJAuaeOX19dYQKURzePYWmjkjLPH_4L0D2OiRgQA,3376
14
+ scripts/load_business_plan.py,sha256=gb4_NPL39PX5yie7zENYgViPBoTqHaLYbTGXPdZU9wM,3560
15
+ scripts/migrate_db.py,sha256=b51nlLn62nThDGRNqPbxT3jNM2Zdg56hKyymYFTcrUk,1220
16
+ scripts/scheduler.py,sha256=4LDL7Mb8ZWeOFpy6i8LuMff4HWjFwm10XrcMpLkZmUY,3459
17
+ bizy_ai-1.1.0.dist-info/METADATA,sha256=9zDRI0w3sQuFbGFPFbZC97Gq0kY-vbeyFMjk99OWjxo,13726
18
+ bizy_ai-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ bizy_ai-1.1.0.dist-info/entry_points.txt,sha256=yDZc2xFUlCOPuHtAaNissB16AZFzOnOL8xeStkDujAg,39
20
+ bizy_ai-1.1.0.dist-info/top_level.txt,sha256=k5ce4bNe_tK9tse1lxY4b8nPSipbtgoA28GHmM2ojwk,14
21
+ bizy_ai-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """Script to load business plan from YAML into the database"""
3
+
4
+ import yaml
5
+ import sys
6
+ import os
7
+ from datetime import datetime
8
+ from agent.models import get_session, BusinessPlan
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.prompt import Prompt
12
+
13
+ console = Console()
14
+
15
+ def load_business_plan(file_path=None, plan_name=None):
16
+ """Load a business plan from YAML into database"""
17
+
18
+ console.print()
19
+
20
+ # If no file path provided, request one
21
+ if not file_path:
22
+ file_path = Prompt.ask("📁 [bold]Enter path to business plan YAML file[/bold]")
23
+
24
+ # Expand user path if needed
25
+ file_path = os.path.expanduser(file_path)
26
+
27
+ # Check if file exists
28
+ if not os.path.exists(file_path):
29
+ console.print(f"[bold red]❌ Error: File not found: {file_path}[/bold red]")
30
+ return False
31
+
32
+ # Read the YAML file
33
+ try:
34
+ with open(file_path, 'r') as f:
35
+ file_content = f.read()
36
+ plan_data = yaml.safe_load(file_content)
37
+ except Exception as e:
38
+ console.print(f"[bold red]❌ Error reading YAML file: {e}[/bold red]")
39
+ return False
40
+
41
+ session = get_session()
42
+
43
+ try:
44
+ # Get plan name: CLI option > YAML 'name' field > first # header > prompt user
45
+ if not plan_name:
46
+ # Try to get name from YAML 'name' field
47
+ plan_name = plan_data.get('name')
48
+
49
+ if not plan_name:
50
+ # Try to extract from first # header in file
51
+ for line in file_content.split('\n'):
52
+ line = line.strip()
53
+ if line.startswith('# ') and not line.startswith('##'):
54
+ plan_name = line[2:].strip()
55
+ break
56
+
57
+ if not plan_name:
58
+ # Prompt user for name
59
+ plan_name = Prompt.ask(f"📝 [bold]Enter business plan name[/bold]")
60
+
61
+ # Deactivate any existing active plans
62
+ active_plans = session.query(BusinessPlan).filter_by(is_active=True).all()
63
+ for plan in active_plans:
64
+ plan.is_active = False
65
+ console.print(f"[yellow]⚠ Deactivated existing plan: {plan.name}[/yellow]")
66
+
67
+ # Create new business plan
68
+ new_plan = BusinessPlan(
69
+ name=plan_name,
70
+ version=plan_data.get('version', '1.0'),
71
+ vision=plan_data.get('vision', ''),
72
+ mission=plan_data.get('mission', ''),
73
+ value_proposition=plan_data.get('value_proposition', ''),
74
+ target_market=plan_data.get('target_market', ''),
75
+ revenue_model=plan_data.get('revenue_model', ''),
76
+ is_active=True
77
+ )
78
+
79
+ session.add(new_plan)
80
+ session.commit()
81
+
82
+ console.print(Panel(
83
+ f"[green]✅ Business Plan Loaded Successfully[/green]\n\n"
84
+ f"Name: {new_plan.name}\n"
85
+ f"Version: {new_plan.version}\n"
86
+ f"ID: {new_plan.id}\n"
87
+ f"Created: {new_plan.created_at.strftime('%Y-%m-%d %H:%M:%S')}\n"
88
+ f"Active: {'Yes' if new_plan.is_active else 'No'}",
89
+ title="Success",
90
+ border_style="green"
91
+ ))
92
+
93
+ console.print()
94
+ return True
95
+
96
+ except Exception as e:
97
+ console.print(f"[bold red]❌ Error creating business plan: {e}[/bold red]")
98
+ session.rollback()
99
+ return False
100
+ finally:
101
+ session.close()
102
+
103
+ if __name__ == "__main__":
104
+ # Check for command line argument
105
+ if len(sys.argv) > 1:
106
+ file_path = sys.argv[1]
107
+ else:
108
+ file_path = None
109
+
110
+ load_business_plan(file_path)
scripts/migrate_db.py ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ """Migrate database to add name and updated_at columns to business_plans table"""
3
+
4
+ import sqlite3
5
+ import os
6
+
7
+ db_path = os.path.expanduser('~/.business-agent/tasks.db')
8
+
9
+ print(f"Migrating database at: {db_path}")
10
+
11
+ conn = sqlite3.connect(db_path)
12
+ cursor = conn.cursor()
13
+
14
+ try:
15
+ # Check if name column exists
16
+ cursor.execute("PRAGMA table_info(business_plans)")
17
+ columns = [row[1] for row in cursor.fetchall()]
18
+
19
+ if 'name' not in columns:
20
+ print("Adding 'name' column to business_plans table...")
21
+ cursor.execute("ALTER TABLE business_plans ADD COLUMN name VARCHAR(255)")
22
+ print("✅ Added 'name' column")
23
+ else:
24
+ print("✓ 'name' column already exists")
25
+
26
+ if 'updated_at' not in columns:
27
+ print("Adding 'updated_at' column to business_plans table...")
28
+ cursor.execute("ALTER TABLE business_plans ADD COLUMN updated_at DATETIME")
29
+ print("✅ Added 'updated_at' column")
30
+ else:
31
+ print("✓ 'updated_at' column already exists")
32
+
33
+ conn.commit()
34
+ print("\n✅ Database migration completed successfully!")
35
+
36
+ except Exception as e:
37
+ print(f"❌ Error during migration: {e}")
38
+ conn.rollback()
39
+ finally:
40
+ conn.close()
scripts/scheduler.py ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Business Agent Main Scheduler
4
+ Runs automated daily tasks on schedule
5
+ """
6
+
7
+ import schedule
8
+ import time
9
+ import subprocess
10
+ import sys
11
+ from datetime import datetime
12
+ from rich.console import Console
13
+ from dotenv import load_dotenv
14
+
15
+ load_dotenv()
16
+
17
+ console = Console()
18
+
19
+ def run_script(script_name, description):
20
+ """Run a Python script and handle errors"""
21
+ console.print(f"\n[bold cyan]Running {description}...[/bold cyan]")
22
+ console.print(f"[dim]{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}[/dim]\n")
23
+
24
+ try:
25
+ result = subprocess.run(
26
+ [sys.executable, f"scripts/{script_name}"],
27
+ capture_output=False,
28
+ text=True
29
+ )
30
+
31
+ if result.returncode == 0:
32
+ console.print(f"[green]✓ {description} completed successfully[/green]\n")
33
+ else:
34
+ console.print(f"[yellow]⚠ {description} finished with warnings[/yellow]\n")
35
+
36
+ except Exception as e:
37
+ console.print(f"[red]✗ Error running {description}: {e}[/red]\n")
38
+
39
+ def run_morning_brief():
40
+ """Run morning briefing"""
41
+ run_script("morning_brief.py", "Morning Briefing")
42
+
43
+ def run_evening_review():
44
+ """Run evening review"""
45
+ run_script("evening_review.py", "Evening Review")
46
+
47
+ def run_weekly_review():
48
+ """Run weekly review"""
49
+ run_script("weekly_review.py", "Weekly Review")
50
+
51
+ def main():
52
+ console.print("\n[bold cyan]═══════════════════════════════════════════[/bold cyan]")
53
+ console.print("[bold cyan] 🤖 Business Agent Scheduler Started[/bold cyan]")
54
+ console.print("[bold cyan]═══════════════════════════════════════════[/bold cyan]\n")
55
+
56
+ # Schedule jobs
57
+ # Morning briefing: Monday-Friday at 8:00 AM
58
+ schedule.every().monday.at("08:00").do(run_morning_brief)
59
+ schedule.every().tuesday.at("08:00").do(run_morning_brief)
60
+ schedule.every().wednesday.at("08:00").do(run_morning_brief)
61
+ schedule.every().thursday.at("08:00").do(run_morning_brief)
62
+ schedule.every().friday.at("08:00").do(run_morning_brief)
63
+
64
+ # Evening review: Monday-Friday at 6:00 PM
65
+ schedule.every().monday.at("18:00").do(run_evening_review)
66
+ schedule.every().tuesday.at("18:00").do(run_evening_review)
67
+ schedule.every().wednesday.at("18:00").do(run_evening_review)
68
+ schedule.every().thursday.at("18:00").do(run_evening_review)
69
+ schedule.every().friday.at("18:00").do(run_evening_review)
70
+
71
+ # Weekly review: Sunday at 7:00 PM
72
+ schedule.every().sunday.at("19:00").do(run_weekly_review)
73
+
74
+ console.print("[bold]📅 Scheduled Tasks:[/bold]")
75
+ console.print(" • Morning Briefings: Mon-Fri at 8:00 AM")
76
+ console.print(" • Evening Reviews: Mon-Fri at 6:00 PM")
77
+ console.print(" • Weekly Review: Sunday at 7:00 PM")
78
+ console.print()
79
+ console.print("[dim]Press Ctrl+C to stop the scheduler[/dim]")
80
+ console.print()
81
+
82
+ # Keep the scheduler running
83
+ try:
84
+ while True:
85
+ schedule.run_pending()
86
+ time.sleep(60) # Check every minute
87
+
88
+ except KeyboardInterrupt:
89
+ console.print("\n\n[yellow]Scheduler stopped by user[/yellow]")
90
+ console.print("[dim]All scheduled tasks have been cancelled[/dim]\n")
91
+ sys.exit(0)
92
+
93
+ if __name__ == "__main__":
94
+ main()
@@ -1,19 +0,0 @@
1
- agent/__init__.py,sha256=pHeIDi-ibuwZqaNqxdIDRvzedWtPywssbaYIDawMcvQ,88
2
- agent/cli.py,sha256=HZ9u3j2NU4lSBS-buP7xGZs_8dwiVLhSV8zJwMrf8bw,7821
3
- agent/core.py,sha256=UILN_DjsSr91KN3YyQaD_cZgF9vW8m827-W0pZMTUnM,6561
4
- agent/evening_review.py,sha256=Y4d-t62zfaufM8mnSvkhBBZ0wMw1R8jtj4cN8r30t4E,4929
5
- agent/models.py,sha256=6bDSLXRxztF50J0AoTi5PX3ckf74oPhyQXfOccfVGdk,7937
6
- agent/morning_brief.py,sha256=zcu4nUOnIQtoZTiK1XlEjuudiUAABrps3PvAwt2Avmk,4732
7
- agent/plan_manager.py,sha256=x216QjRe9wd9tdBcqBdXX3idU77SqtYeUGN5nxZjrXQ,11832
8
- agent/planner.py,sha256=-KlyZ-2jMQTUy0QVZCH_NuubENU-UrPXVhat3ju54ew,13114
9
- agent/research.py,sha256=HvqGHENQ0v1lJadGYNuf3eb6fZoR6qzzIPzqcsnhFSE,6429
10
- agent/tasks.py,sha256=PaQzRcnzp5HPWVlQKG3KLwdNBNqHTsqCuoGmRwRh8dA,9041
11
- agent/weekly_review.py,sha256=ljwy0Aq6yFE_gs8TQjJKCO9Ob59fXsu8L_gDPiRQdmc,2298
12
- bizy_ai-1.0.2.dist-info/licenses/LICENSE,sha256=__BSNgmbeWQ1IA57XKyGhQajUNcF-pZjvBAY268fCWM,1069
13
- scripts/agent_cli.py,sha256=sG-iRmFZCzm5SkqDtVV1KzZ293SEtvFpY8A1_b69dJU,6971
14
- scripts/init_db.py,sha256=lF1rJAuaeOX19dYQKURzePYWmjkjLPH_4L0D2OiRgQA,3376
15
- bizy_ai-1.0.2.dist-info/METADATA,sha256=qlo6EBdzrXVemHs64kMvD_YKRs6WJG1hzGI4QfeTM8s,12480
16
- bizy_ai-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- bizy_ai-1.0.2.dist-info/entry_points.txt,sha256=yDZc2xFUlCOPuHtAaNissB16AZFzOnOL8xeStkDujAg,39
18
- bizy_ai-1.0.2.dist-info/top_level.txt,sha256=k5ce4bNe_tK9tse1lxY4b8nPSipbtgoA28GHmM2ojwk,14
19
- bizy_ai-1.0.2.dist-info/RECORD,,
scripts/agent_cli.py DELETED
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Business Agent CLI Tool"""
3
-
4
- import sys
5
- import os
6
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
7
-
8
- import click
9
- from agent.tasks import TaskManager
10
- from agent.planner import BusinessPlanner
11
- from agent.research import ResearchAgent
12
- from rich.console import Console
13
- from rich.table import Table
14
- from rich.panel import Panel
15
- from rich.markdown import Markdown
16
- from datetime import datetime, timedelta
17
- from dotenv import load_dotenv
18
-
19
- load_dotenv()
20
- console = Console()
21
-
22
- @click.group()
23
- def cli():
24
- """Business Agent CLI - Manage your business from the command line"""
25
- pass
26
-
27
- # TASK COMMANDS
28
- @cli.group()
29
- def task():
30
- """Manage tasks"""
31
- pass
32
-
33
- @task.command()
34
- @click.argument('title')
35
- @click.option('--description', '-d', help='Task description')
36
- @click.option('--priority', '-p', type=int, default=3)
37
- @click.option('--category', '-c', help='Task category')
38
- @click.option('--hours', '-h', type=float, help='Estimated hours')
39
- def add(title, description, priority, category, hours):
40
- """Add a new task"""
41
- task_mgr = TaskManager()
42
- task = task_mgr.create_task(
43
- title=title,
44
- description=description,
45
- priority=priority,
46
- category=category,
47
- estimated_hours=hours
48
- )
49
- console.print(f"[green]✓[/green] Task created: {task.title} (ID: {task.id})")
50
- task_mgr.close()
51
-
52
- @task.command()
53
- def list():
54
- """List all pending tasks"""
55
- task_mgr = TaskManager()
56
- tasks = task_mgr.get_tasks_for_today()
57
-
58
- if not tasks:
59
- console.print("[yellow]No pending tasks[/yellow]")
60
- return
61
-
62
- table = Table(title="📋 Your Tasks", show_header=True, header_style="bold cyan")
63
- table.add_column("ID", style="dim")
64
- table.add_column("Priority", justify="center")
65
- table.add_column("Title")
66
- table.add_column("Category", style="cyan")
67
-
68
- for task in tasks:
69
- priority_str = "🔴" if task.priority == 1 else "🟡" if task.priority == 3 else "🟢"
70
- table.add_row(
71
- str(task.id),
72
- priority_str,
73
- task.title[:50],
74
- task.category or "-"
75
- )
76
-
77
- console.print(table)
78
- task_mgr.close()
79
-
80
- @task.command()
81
- @click.argument('task_id', type=int)
82
- def complete(task_id):
83
- """Mark a task as complete"""
84
- task_mgr = TaskManager()
85
- task = task_mgr.complete_task(task_id)
86
- if task:
87
- console.print(f"[green]✓[/green] Completed: {task.title}")
88
- else:
89
- console.print(f"[red]✗[/red] Task {task_id} not found")
90
- task_mgr.close()
91
-
92
- # GOAL COMMANDS
93
- @cli.group()
94
- def goal():
95
- """Manage goals"""
96
- pass
97
-
98
- @goal.command()
99
- @click.argument('title')
100
- @click.option('--description', '-d', help='Goal description')
101
- @click.option('--horizon', '-h', type=click.Choice(['weekly', 'monthly', 'quarterly', 'yearly']), default='monthly')
102
- @click.option('--target', '-t', help='Target date (YYYY-MM-DD)')
103
- def add(title, description, horizon, target):
104
- """Add a new goal"""
105
- planner = BusinessPlanner()
106
- target_date = None
107
- if target:
108
- target_date = datetime.strptime(target, '%Y-%m-%d')
109
-
110
- goal = planner.create_goal(
111
- title=title,
112
- description=description,
113
- horizon=horizon,
114
- target_date=target_date
115
- )
116
- console.print(f"[green]✓[/green] Goal created: {goal.title} (ID: {goal.id})")
117
- planner.close()
118
-
119
- @goal.command()
120
- def list():
121
- """List all active goals"""
122
- planner = BusinessPlanner()
123
- goals = planner.get_active_goals()
124
-
125
- if not goals:
126
- console.print("[yellow]No active goals[/yellow]")
127
- return
128
-
129
- table = Table(title="🎯 Your Goals", show_header=True, header_style="bold cyan")
130
- table.add_column("ID", style="dim")
131
- table.add_column("Title")
132
- table.add_column("Horizon", style="cyan")
133
- table.add_column("Progress", justify="right")
134
-
135
- for goal in goals:
136
- progress_bar = "█" * int(goal.progress_percentage / 10) + "░" * (10 - int(goal.progress_percentage / 10))
137
- table.add_row(
138
- str(goal.id),
139
- goal.title[:40],
140
- goal.horizon,
141
- f"{progress_bar} {goal.progress_percentage:.0f}%"
142
- )
143
-
144
- console.print(table)
145
- planner.close()
146
-
147
- @goal.command()
148
- @click.argument('goal_id', type=int)
149
- def breakdown(goal_id):
150
- """Break down a goal into tasks using AI"""
151
- planner = BusinessPlanner()
152
- console.print(f"[cyan]Breaking down goal {goal_id}...[/cyan]")
153
- tasks = planner.break_down_goal(goal_id)
154
-
155
- if tasks:
156
- console.print(f"[green]✓[/green] Created {len(tasks)} tasks")
157
- for task in tasks:
158
- console.print(f" • {task.title}")
159
- else:
160
- console.print("[red]✗[/red] Failed to break down goal")
161
- planner.close()
162
-
163
- # RESEARCH COMMANDS
164
- @cli.group()
165
- def research():
166
- """Conduct research"""
167
- pass
168
-
169
- @research.command()
170
- @click.argument('topic')
171
- @click.option('--goal', '-g', help='Business goal')
172
- def topic(topic, goal):
173
- """Research a topic"""
174
- researcher = ResearchAgent()
175
- console.print(f"[cyan]Researching: {topic}...[/cyan]\n")
176
-
177
- result = researcher.research_topic(
178
- topic=topic,
179
- business_goal=goal or "General research",
180
- depth="standard"
181
- )
182
-
183
- if 'error' in result:
184
- console.print(f"[red]✗ Error:[/red] {result['error']}")
185
- else:
186
- console.print(Panel(
187
- Markdown(result['findings']),
188
- title=f"🔍 Research: {topic}",
189
- border_style="blue"
190
- ))
191
- console.print(f"\n[dim]Saved as research ID: {result['research_id']}[/dim]")
192
- researcher.close()
193
-
194
- @research.command()
195
- @click.argument('domain')
196
- @click.argument('offering')
197
- def competitors(domain, offering):
198
- """Research competitors"""
199
- researcher = ResearchAgent()
200
- console.print(f"[cyan]Analyzing competitive landscape...[/cyan]\n")
201
-
202
- result = researcher.research_competitors(domain, offering)
203
-
204
- if 'error' in result:
205
- console.print(f"[red]✗ Error:[/red] {result['error']}")
206
- else:
207
- console.print(Panel(
208
- Markdown(result['findings']),
209
- title="🏆 Competitive Analysis",
210
- border_style="blue"
211
- ))
212
- researcher.close()
213
-
214
- # STATS COMMAND
215
- @cli.command()
216
- def stats():
217
- """Show statistics"""
218
- task_mgr = TaskManager()
219
- weekly_stats = task_mgr.get_weekly_stats()
220
- velocity = task_mgr.get_task_velocity()
221
- today_tasks = task_mgr.get_tasks_for_today()
222
-
223
- console.print("\n[bold cyan]📊 Your Statistics[/bold cyan]\n")
224
- console.print("[bold]This Week:[/bold]")
225
- console.print(f" • Tasks Completed: {weekly_stats['total_tasks_completed']}")
226
- console.print(f" • Completion Rate: {weekly_stats['average_completion_rate']:.0%}")
227
- console.print(f"\n[bold]Velocity:[/bold] {velocity:.1f} tasks/day")
228
- console.print(f"\n[bold]Today:[/bold] {len(today_tasks)} tasks scheduled\n")
229
- task_mgr.close()
230
-
231
- if __name__ == "__main__":
232
- cli()