bizy-ai 1.0.2__py3-none-any.whl → 1.1.1__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 +201 -16
- agent/core.py +4 -4
- agent/models.py +25 -10
- agent/plan_manager.py +14 -3
- agent/tasks.py +120 -22
- agent/weekly_review.py +10 -10
- {bizy_ai-1.0.2.dist-info → bizy_ai-1.1.1.dist-info}/METADATA +52 -2
- bizy_ai-1.1.1.dist-info/RECORD +21 -0
- scripts/load_business_plan.py +110 -0
- scripts/migrate_db.py +40 -0
- scripts/scheduler.py +94 -0
- bizy_ai-1.0.2.dist-info/RECORD +0 -19
- scripts/agent_cli.py +0 -232
- {bizy_ai-1.0.2.dist-info → bizy_ai-1.1.1.dist-info}/WHEEL +0 -0
- {bizy_ai-1.0.2.dist-info → bizy_ai-1.1.1.dist-info}/entry_points.txt +0 -0
- {bizy_ai-1.0.2.dist-info → bizy_ai-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {bizy_ai-1.0.2.dist-info → bizy_ai-1.1.1.dist-info}/top_level.txt +0 -0
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
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
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
|
173
|
+
console.print(f"[yellow]No {filter} tasks found[/yellow]")
|
174
|
+
task_mgr.close()
|
56
175
|
return
|
57
176
|
|
58
|
-
|
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
|
-
|
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,41 @@ def competitors(domain, offering):
|
|
212
361
|
def stats():
|
213
362
|
"""Show statistics"""
|
214
363
|
task_mgr = TaskManager()
|
215
|
-
weekly_stats = task_mgr.
|
216
|
-
|
364
|
+
weekly_stats = task_mgr.get_weekly_task_stats()
|
365
|
+
today_summary = task_mgr.get_daily_summary()
|
366
|
+
yesterday_summary = task_mgr.get_yesterday_summary()
|
367
|
+
velocity = task_mgr.get_task_velocity(days=7) # Use 7-day velocity to match weekly context
|
217
368
|
today_tasks = task_mgr.get_tasks_for_today()
|
218
369
|
|
219
370
|
console.print("\n[bold cyan]📊 Your Statistics[/bold cyan]\n")
|
220
|
-
|
221
|
-
|
222
|
-
console.print(
|
223
|
-
console.print(f"
|
224
|
-
console.print(f"
|
371
|
+
|
372
|
+
# Today's stats
|
373
|
+
console.print("[bold]Today:[/bold]")
|
374
|
+
console.print(f" • Tasks Completed: {today_summary['tasks_completed']}")
|
375
|
+
console.print(f" • Tasks Scheduled: {len(today_tasks)}")
|
376
|
+
|
377
|
+
# Yesterday's stats
|
378
|
+
console.print(f"\n[bold]Yesterday:[/bold]")
|
379
|
+
console.print(f" • Tasks Completed: {yesterday_summary['tasks_completed']}")
|
380
|
+
|
381
|
+
# Weekly stats
|
382
|
+
console.print(f"\n[bold]This Week:[/bold]")
|
383
|
+
console.print(f" • Tasks Completed: {weekly_stats['tasks_completed_this_week']}")
|
384
|
+
console.print(f" • Tasks Created: {weekly_stats['tasks_created_this_week']}")
|
385
|
+
console.print(f" • Completion Rate: {weekly_stats['completion_rate']:.1f}%")
|
386
|
+
console.print(f" • Total Hours (Estimated): {weekly_stats['total_estimated_hours']:.1f}h")
|
387
|
+
if weekly_stats['total_actual_hours'] > 0:
|
388
|
+
console.print(f" • Total Hours (Actual): {weekly_stats['total_actual_hours']:.1f}h")
|
389
|
+
|
390
|
+
console.print(f"\n[bold]Velocity:[/bold] {velocity:.1f} tasks/day\n")
|
391
|
+
|
392
|
+
# Show breakdown by category if available
|
393
|
+
if weekly_stats['categories']:
|
394
|
+
console.print("[bold]By Category (This Week):[/bold]")
|
395
|
+
for category, count in sorted(weekly_stats['categories'].items(), key=lambda x: x[1], reverse=True):
|
396
|
+
console.print(f" • {category}: {count} task(s)")
|
397
|
+
console.print()
|
398
|
+
|
225
399
|
task_mgr.close()
|
226
400
|
|
227
401
|
# DAILY/WEEKLY REVIEWS
|
@@ -267,5 +441,16 @@ def update():
|
|
267
441
|
from agent.plan_manager import update_business_plan
|
268
442
|
update_business_plan()
|
269
443
|
|
444
|
+
@plan.command()
|
445
|
+
@click.argument('file_path', required=False)
|
446
|
+
@click.option('--name', '-n', help='Business plan name')
|
447
|
+
def load(file_path, name):
|
448
|
+
"""Load business plan from YAML file"""
|
449
|
+
# Import and run the standalone script
|
450
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
451
|
+
sys.path.insert(0, repo_root)
|
452
|
+
from scripts.load_business_plan import load_business_plan
|
453
|
+
load_business_plan(file_path, name)
|
454
|
+
|
270
455
|
if __name__ == "__main__":
|
271
456
|
cli()
|
agent/core.py
CHANGED
@@ -127,10 +127,10 @@ Be supportive but honest. Keep it concise."""
|
|
127
127
|
WEEK ENDING: {datetime.now().strftime('%B %d, %Y')}
|
128
128
|
|
129
129
|
STATS:
|
130
|
-
- Total Tasks Completed: {weekly_stats.get('
|
131
|
-
- Total Tasks
|
132
|
-
-
|
133
|
-
-
|
130
|
+
- Total Tasks Completed: {weekly_stats.get('tasks_completed_this_week', 0)}
|
131
|
+
- Total Tasks Created: {weekly_stats.get('tasks_created_this_week', 0)}
|
132
|
+
- Completion Rate: {weekly_stats.get('completion_rate', 0):.1f}%
|
133
|
+
- Total Hours (Estimated): {weekly_stats.get('total_estimated_hours', 0):.1f}h
|
134
134
|
|
135
135
|
GOAL PROGRESS:
|
136
136
|
{goals_progress}
|
agent/models.py
CHANGED
@@ -18,8 +18,8 @@ class Task(Base):
|
|
18
18
|
estimated_hours = Column(Float)
|
19
19
|
actual_hours = Column(Float)
|
20
20
|
due_date = Column(DateTime)
|
21
|
-
created_at = Column(DateTime, default=datetime.
|
22
|
-
completed_at = Column(DateTime)
|
21
|
+
created_at = Column(DateTime, default=datetime.now) # LOCAL time for consistency
|
22
|
+
completed_at = Column(DateTime) # LOCAL time, set by complete_task()
|
23
23
|
parent_goal_id = Column(Integer) # Links to goals
|
24
24
|
dependencies = Column(JSON) # List of task IDs this depends on
|
25
25
|
notes = Column(Text)
|
@@ -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
|
-
#
|
197
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
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.
|
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['
|
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
|
-
-
|
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
@@ -118,16 +118,21 @@ class TaskManager:
|
|
118
118
|
).order_by(Task.due_date, Task.priority).all()
|
119
119
|
|
120
120
|
def get_daily_summary(self, date_obj=None):
|
121
|
-
"""
|
121
|
+
"""
|
122
|
+
Get summary of tasks for a specific day.
|
123
|
+
Shows tasks completed on this day regardless of due date.
|
124
|
+
All timestamps are in LOCAL time.
|
125
|
+
"""
|
122
126
|
if date_obj is None:
|
123
127
|
date_obj = datetime.now()
|
124
|
-
|
128
|
+
|
125
129
|
if isinstance(date_obj, date) and not isinstance(date_obj, datetime):
|
126
130
|
date_obj = datetime.combine(date_obj, datetime.min.time())
|
127
|
-
|
131
|
+
|
132
|
+
# Use LOCAL time boundaries (midnight to midnight)
|
128
133
|
day_start = date_obj.replace(hour=0, minute=0, second=0, microsecond=0)
|
129
134
|
day_end = day_start + timedelta(days=1)
|
130
|
-
|
135
|
+
|
131
136
|
# Get tasks due on this day
|
132
137
|
tasks_due = self.session.query(Task).filter(
|
133
138
|
and_(
|
@@ -135,20 +140,28 @@ class TaskManager:
|
|
135
140
|
Task.due_date < day_end
|
136
141
|
)
|
137
142
|
).all()
|
138
|
-
|
139
|
-
# Get tasks completed on this day
|
143
|
+
|
144
|
+
# Get tasks completed on this day (using LOCAL time)
|
140
145
|
tasks_completed = self.session.query(Task).filter(
|
141
146
|
and_(
|
142
147
|
Task.completed_at >= day_start,
|
143
148
|
Task.completed_at < day_end
|
144
149
|
)
|
145
150
|
).all()
|
146
|
-
|
151
|
+
|
152
|
+
# Calculate completion rate based on tasks that were due
|
153
|
+
# If no tasks were due, show N/A but still show completed count
|
154
|
+
if tasks_due:
|
155
|
+
completed_count = len([t for t in tasks_due if t.status == 'completed'])
|
156
|
+
completion_rate = completed_count / len(tasks_due)
|
157
|
+
else:
|
158
|
+
completion_rate = 0
|
159
|
+
|
147
160
|
return {
|
148
161
|
'date': date_obj.strftime('%Y-%m-%d'),
|
149
162
|
'tasks_due': len(tasks_due),
|
150
|
-
'tasks_completed': len(tasks_completed),
|
151
|
-
'completion_rate':
|
163
|
+
'tasks_completed': len(tasks_completed), # All tasks completed today
|
164
|
+
'completion_rate': completion_rate, # Based on tasks that were due
|
152
165
|
'completed_tasks': [t.to_dict() for t in tasks_completed],
|
153
166
|
'pending_tasks': [t.to_dict() for t in tasks_due if t.status != 'completed']
|
154
167
|
}
|
@@ -233,19 +246,104 @@ class TaskManager:
|
|
233
246
|
}
|
234
247
|
|
235
248
|
def get_task_velocity(self, days=30):
|
236
|
-
"""
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
if not
|
244
|
-
return 0
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
+
"""
|
250
|
+
Calculate average tasks completed per day over a period.
|
251
|
+
Uses actual completed_at timestamps for accurate velocity calculation.
|
252
|
+
"""
|
253
|
+
# Get tasks completed in the specified period
|
254
|
+
completed_tasks = self.get_completed_tasks_this_week(days=days)
|
255
|
+
|
256
|
+
if not completed_tasks:
|
257
|
+
return 0.0
|
258
|
+
|
259
|
+
# Calculate velocity as tasks per day
|
260
|
+
velocity = len(completed_tasks) / days
|
261
|
+
return velocity
|
262
|
+
|
263
|
+
def get_completed_tasks_this_week(self, days=7):
|
264
|
+
"""Get tasks completed in the last N days based on completed_at timestamp"""
|
265
|
+
# Use LOCAL time to match database timestamps
|
266
|
+
now = datetime.now()
|
267
|
+
start_date = now - timedelta(days=days)
|
268
|
+
|
269
|
+
completed_tasks = self.session.query(Task).filter(
|
270
|
+
and_(
|
271
|
+
Task.status == 'completed',
|
272
|
+
Task.completed_at >= start_date,
|
273
|
+
Task.completed_at <= now
|
274
|
+
)
|
275
|
+
).order_by(Task.completed_at.desc()).all()
|
276
|
+
|
277
|
+
return completed_tasks
|
278
|
+
|
279
|
+
def get_created_tasks_this_week(self, days=7):
|
280
|
+
"""Get tasks created in the last N days based on created_at timestamp"""
|
281
|
+
# Use LOCAL time to match database timestamps
|
282
|
+
now = datetime.now()
|
283
|
+
start_date = now - timedelta(days=days)
|
284
|
+
|
285
|
+
created_tasks = self.session.query(Task).filter(
|
286
|
+
and_(
|
287
|
+
Task.created_at >= start_date,
|
288
|
+
Task.created_at <= now
|
289
|
+
)
|
290
|
+
).order_by(Task.created_at.desc()).all()
|
291
|
+
|
292
|
+
return created_tasks
|
293
|
+
|
294
|
+
def get_weekly_task_stats(self, days=7):
|
295
|
+
"""
|
296
|
+
Get weekly statistics based on actual task completion dates (completed_at).
|
297
|
+
This is more accurate than DailyLog-based stats as it reflects actual work completed.
|
298
|
+
"""
|
299
|
+
completed_tasks = self.get_completed_tasks_this_week(days)
|
300
|
+
created_tasks = self.get_created_tasks_this_week(days)
|
301
|
+
|
302
|
+
# Calculate statistics
|
303
|
+
tasks_completed = len(completed_tasks)
|
304
|
+
tasks_created = len(created_tasks)
|
305
|
+
|
306
|
+
# Calculate completion rate (completed vs created this week)
|
307
|
+
completion_rate = (tasks_completed / tasks_created * 100) if tasks_created > 0 else 0
|
308
|
+
|
309
|
+
# Calculate total estimated hours
|
310
|
+
total_estimated_hours = sum(
|
311
|
+
task.estimated_hours or 0 for task in completed_tasks
|
312
|
+
)
|
313
|
+
|
314
|
+
# Calculate total actual hours
|
315
|
+
total_actual_hours = sum(
|
316
|
+
task.actual_hours or 0 for task in completed_tasks
|
317
|
+
)
|
318
|
+
|
319
|
+
# Break down by category
|
320
|
+
categories = {}
|
321
|
+
for task in completed_tasks:
|
322
|
+
category = task.category or 'uncategorized'
|
323
|
+
if category not in categories:
|
324
|
+
categories[category] = 0
|
325
|
+
categories[category] += 1
|
326
|
+
|
327
|
+
# Break down by priority
|
328
|
+
priorities = {}
|
329
|
+
for task in completed_tasks:
|
330
|
+
priority = task.priority
|
331
|
+
if priority not in priorities:
|
332
|
+
priorities[priority] = 0
|
333
|
+
priorities[priority] += 1
|
334
|
+
|
335
|
+
return {
|
336
|
+
'tasks_completed_this_week': tasks_completed,
|
337
|
+
'tasks_created_this_week': tasks_created,
|
338
|
+
'completion_rate': completion_rate,
|
339
|
+
'total_estimated_hours': total_estimated_hours,
|
340
|
+
'total_actual_hours': total_actual_hours,
|
341
|
+
'completed_tasks': [task.to_dict() for task in completed_tasks],
|
342
|
+
'categories': categories,
|
343
|
+
'priorities': priorities,
|
344
|
+
'period_days': days
|
345
|
+
}
|
346
|
+
|
249
347
|
def close(self):
|
250
348
|
"""Close the database session"""
|
251
349
|
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.
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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.
|
3
|
+
Version: 1.1.1
|
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=AmYJ1zOxupVM99zcEUdyTZ6s1sYihAmCzj-MsS_XMyg,15664
|
3
|
+
agent/core.py,sha256=SOOTfIukpf4FWJ7EESQDae71tDM33pwx6dtrMNgYb3U,6581
|
4
|
+
agent/evening_review.py,sha256=Y4d-t62zfaufM8mnSvkhBBZ0wMw1R8jtj4cN8r30t4E,4929
|
5
|
+
agent/models.py,sha256=Uqj-1ahCjcjqMN8hcwCcHVSQ8ywHjrwf9klz3I_XCEE,8792
|
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=uB-VRpkizCw4JMP0s8c4-zk8m8uJDo8ccl8OaL1sLuc,12691
|
11
|
+
agent/weekly_review.py,sha256=83fYwe3hKEhqAQfMlj7WmvvMhTNHtUnqr1PFXCHRGWo,2394
|
12
|
+
bizy_ai-1.1.1.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.1.dist-info/METADATA,sha256=BvXrex7fVli39Rq-bRjZ3yuKrKALb-un-WDN96PnucU,13726
|
18
|
+
bizy_ai-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
19
|
+
bizy_ai-1.1.1.dist-info/entry_points.txt,sha256=yDZc2xFUlCOPuHtAaNissB16AZFzOnOL8xeStkDujAg,39
|
20
|
+
bizy_ai-1.1.1.dist-info/top_level.txt,sha256=k5ce4bNe_tK9tse1lxY4b8nPSipbtgoA28GHmM2ojwk,14
|
21
|
+
bizy_ai-1.1.1.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()
|
bizy_ai-1.0.2.dist-info/RECORD
DELETED
@@ -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()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|