crowdtime-cli 0.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.
- crowdtime_cli/__init__.py +3 -0
- crowdtime_cli/auth.py +69 -0
- crowdtime_cli/client.py +177 -0
- crowdtime_cli/commands/__init__.py +1 -0
- crowdtime_cli/commands/ai_cmd.py +211 -0
- crowdtime_cli/commands/auth_cmd.py +160 -0
- crowdtime_cli/commands/clients_cmd.py +150 -0
- crowdtime_cli/commands/config_cmd.py +91 -0
- crowdtime_cli/commands/favorites_cmd.py +128 -0
- crowdtime_cli/commands/log_cmd.py +298 -0
- crowdtime_cli/commands/org_cmd.py +134 -0
- crowdtime_cli/commands/projects_cmd.py +175 -0
- crowdtime_cli/commands/report_cmd.py +242 -0
- crowdtime_cli/commands/skill_cmd.py +266 -0
- crowdtime_cli/commands/tasks_cmd.py +101 -0
- crowdtime_cli/commands/timer_cmd.py +207 -0
- crowdtime_cli/config.py +125 -0
- crowdtime_cli/formatters.py +395 -0
- crowdtime_cli/main.py +334 -0
- crowdtime_cli/models.py +146 -0
- crowdtime_cli/oauth.py +107 -0
- crowdtime_cli/resolvers.py +80 -0
- crowdtime_cli/skills/crowdtime/SKILL.md +193 -0
- crowdtime_cli/skills/crowdtime/references/commands.md +659 -0
- crowdtime_cli/skills/crowdtime/references/workflows.md +286 -0
- crowdtime_cli/utils.py +166 -0
- crowdtime_cli-0.1.0.dist-info/METADATA +140 -0
- crowdtime_cli-0.1.0.dist-info/RECORD +31 -0
- crowdtime_cli-0.1.0.dist-info/WHEEL +4 -0
- crowdtime_cli-0.1.0.dist-info/entry_points.txt +3 -0
- crowdtime_cli-0.1.0.dist-info/licenses/LICENSE +77 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# CrowdTime CLI — Common Workflows
|
|
2
|
+
|
|
3
|
+
Multi-step workflow patterns for real-world time tracking scenarios.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Daily Routines](#daily-routines)
|
|
8
|
+
- [End of Week](#end-of-week)
|
|
9
|
+
- [Project Setup](#project-setup)
|
|
10
|
+
- [Backfilling Time](#backfilling-time)
|
|
11
|
+
- [Team Management](#team-management)
|
|
12
|
+
- [Reporting and Export](#reporting-and-export)
|
|
13
|
+
- [Troubleshooting](#troubleshooting)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Daily Routines
|
|
18
|
+
|
|
19
|
+
### Morning Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Check what's going on
|
|
23
|
+
ct s
|
|
24
|
+
|
|
25
|
+
# Start working
|
|
26
|
+
ct ts "morning standup" -p team-meetings
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Switch Between Tasks
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Atomic switch — stops current timer and starts new one
|
|
33
|
+
ct timer switch "API endpoint work" -p backend
|
|
34
|
+
|
|
35
|
+
# Or stop and start separately
|
|
36
|
+
ct tx
|
|
37
|
+
ct ts "design review" -p frontend
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### End of Day
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Stop running timer
|
|
44
|
+
ct tx
|
|
45
|
+
|
|
46
|
+
# Check today's log
|
|
47
|
+
ct ll --date today
|
|
48
|
+
|
|
49
|
+
# See total hours
|
|
50
|
+
ct s
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Quick Entry Without Timer
|
|
54
|
+
|
|
55
|
+
If you forgot to use a timer, log directly:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ct l -p acme-project 1h30m "client call with Acme"
|
|
59
|
+
ct l -p backend 45m "code review for PR #234"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## End of Week
|
|
65
|
+
|
|
66
|
+
### Weekly Review
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# See the full week at a glance
|
|
70
|
+
ct s
|
|
71
|
+
|
|
72
|
+
# Detailed report grouped by project
|
|
73
|
+
ct report --week -g project
|
|
74
|
+
|
|
75
|
+
# Report grouped by day to spot gaps
|
|
76
|
+
ct report --week -g day
|
|
77
|
+
|
|
78
|
+
# Get AI summary for standup
|
|
79
|
+
ct ai summarize --week --for standup
|
|
80
|
+
|
|
81
|
+
# Get AI summary for Slack, auto-copied
|
|
82
|
+
ct ai summarize --week --for slack --copy
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Fill In Missing Days
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Check what days are light
|
|
89
|
+
ct report --week -g day
|
|
90
|
+
|
|
91
|
+
# Backfill entries
|
|
92
|
+
ct l -p project-alpha -d monday 6h "feature development"
|
|
93
|
+
ct l -p project-alpha -d monday 2h "code review"
|
|
94
|
+
ct l -p project-beta -d tuesday 7h "API integration"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Export for Invoicing
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# CSV export for spreadsheet
|
|
101
|
+
ct report --month -p client-project -f csv > march-2026-hours.csv
|
|
102
|
+
|
|
103
|
+
# Markdown for docs/email
|
|
104
|
+
ct report --month -g project -f markdown
|
|
105
|
+
|
|
106
|
+
# JSON for programmatic use
|
|
107
|
+
ct report --month -f json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Project Setup
|
|
113
|
+
|
|
114
|
+
### Create a New Project with Tasks
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Create the project (client is auto-created if it doesn't exist)
|
|
118
|
+
ct projects create "Website Redesign" --client "Acme Corp" --budget 200 --code WR -b
|
|
119
|
+
|
|
120
|
+
# Create tasks for the project (use project slug, not display name)
|
|
121
|
+
ct tasks create "Design" -p website-redesign -b
|
|
122
|
+
ct tasks create "Development" -p website-redesign -b
|
|
123
|
+
ct tasks create "QA Testing" -p website-redesign -b
|
|
124
|
+
ct tasks create "Meetings" -p website-redesign -B # non-billable
|
|
125
|
+
|
|
126
|
+
# Set as default if it's your main project
|
|
127
|
+
ct projects switch website-redesign
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Create Favorites for Recurring Work
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Create templates for things you do every day
|
|
134
|
+
ct favorites create "Daily standup" -p team-meetings -t standup
|
|
135
|
+
ct favorites create "Code review" -p engineering -t review -b
|
|
136
|
+
ct favorites create "Sprint planning" -p team-meetings -t planning
|
|
137
|
+
|
|
138
|
+
# Start a timer from a favorite
|
|
139
|
+
ct favorites list # find the ID
|
|
140
|
+
ct favorites start <id>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Backfilling Time
|
|
146
|
+
|
|
147
|
+
### Log a Full Past Day
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Log multiple entries for yesterday (options MUST come before duration/description)
|
|
151
|
+
ct l -p team -d yesterday 1h "standup + planning"
|
|
152
|
+
ct l -p project-alpha -d yesterday -b 4h "feature implementation"
|
|
153
|
+
ct l -p project-alpha -d yesterday -b 2h "code review"
|
|
154
|
+
ct l -p project-alpha -d yesterday -B 1h "documentation"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Log for a Specific Past Date
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
ct l -p training -d "last wednesday" 8h "workshop"
|
|
161
|
+
ct l -p acme -d 2026-03-05 3h "client meeting"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Team Management
|
|
167
|
+
|
|
168
|
+
### Onboard a New Team Member
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Invite them
|
|
172
|
+
ct org invite newdev@company.com -r member
|
|
173
|
+
|
|
174
|
+
# They need to:
|
|
175
|
+
# 1. Accept the invitation (via email link)
|
|
176
|
+
# 2. Install CLI: pip install crowdtime-cli
|
|
177
|
+
# 3. Configure:
|
|
178
|
+
# ct config set server.url https://your-server.com
|
|
179
|
+
# ct auth login
|
|
180
|
+
# ct org switch your-org
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Check Team Activity
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Team utilization this week
|
|
187
|
+
ct report team --week
|
|
188
|
+
|
|
189
|
+
# Specific member
|
|
190
|
+
ct report team --month --member john@company.com
|
|
191
|
+
|
|
192
|
+
# List all members
|
|
193
|
+
ct org members
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Reporting and Export
|
|
199
|
+
|
|
200
|
+
### Client Billing Report
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Monthly billable hours for a specific project
|
|
204
|
+
ct report --month -p client-project -f csv > invoice-data.csv
|
|
205
|
+
|
|
206
|
+
# Project breakdown across all projects
|
|
207
|
+
ct report projects --month
|
|
208
|
+
|
|
209
|
+
# Detailed with day-by-day breakdown
|
|
210
|
+
ct report --from 2026-03-01 --to 2026-03-31 -g day -p client-project
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Personal Productivity
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# How did I spend my week?
|
|
217
|
+
ct report --week -g project
|
|
218
|
+
|
|
219
|
+
# Daily breakdown
|
|
220
|
+
ct report --week -g day
|
|
221
|
+
|
|
222
|
+
# Get AI insights
|
|
223
|
+
ct ai summarize --week
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Custom Date Ranges
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Last two weeks
|
|
230
|
+
ct report --from 2026-02-24 --to 2026-03-09
|
|
231
|
+
|
|
232
|
+
# Specific sprint (2 weeks)
|
|
233
|
+
ct report --from 2026-03-01 --to 2026-03-14 -g task
|
|
234
|
+
|
|
235
|
+
# Quarter to date
|
|
236
|
+
ct report --from 2026-01-01 --to 2026-03-31 -g project -f markdown
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Troubleshooting
|
|
242
|
+
|
|
243
|
+
### "Not authenticated" Error
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
ct auth whoami # Check current auth status
|
|
247
|
+
ct auth login # Re-authenticate (opens browser)
|
|
248
|
+
ct auth login --token <your-token> # Or use a token directly
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### "No organization selected" Error
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
ct org list # See available orgs
|
|
255
|
+
ct org switch <slug> # Select one
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### "Timer already running" Error
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
ct t # See what's running
|
|
262
|
+
ct timer switch "new task" -p project # Switch to new task
|
|
263
|
+
# or
|
|
264
|
+
ct tx # Stop current timer first
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Wrong Default Project
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
ct config get defaults.project # Check current default
|
|
271
|
+
ct projects switch <correct-slug> # Change it
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Server Connection Issues
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
ct config get server.url # Check configured URL
|
|
278
|
+
ct config set server.url https://api.crowdtime.lat # Fix if needed
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Check Current Configuration
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
ct config list # See all settings
|
|
285
|
+
ct config edit # Edit in your editor
|
|
286
|
+
```
|
crowdtime_cli/utils.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Utility functions for duration parsing, date parsing, and formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from datetime import date, timedelta
|
|
7
|
+
from decimal import Decimal, InvalidOperation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_duration(text: str) -> Decimal:
|
|
11
|
+
"""Parse a duration string into decimal hours.
|
|
12
|
+
|
|
13
|
+
Accepts:
|
|
14
|
+
"2h" -> 2.0
|
|
15
|
+
"2h30m" -> 2.5
|
|
16
|
+
"2.5h" -> 2.5
|
|
17
|
+
"2.5" -> 2.5 (assumed hours)
|
|
18
|
+
"2:30" -> 2.5
|
|
19
|
+
"150m" -> 2.5
|
|
20
|
+
"0.25d" -> 2.0 (1 day = 8h)
|
|
21
|
+
"30" -> 0.5 (assumed minutes if <= 480, else error)
|
|
22
|
+
"""
|
|
23
|
+
text = text.strip().lower()
|
|
24
|
+
|
|
25
|
+
# Format: 2:30
|
|
26
|
+
match = re.match(r"^(\d+):(\d{1,2})$", text)
|
|
27
|
+
if match:
|
|
28
|
+
hours = int(match.group(1))
|
|
29
|
+
minutes = int(match.group(2))
|
|
30
|
+
return Decimal(str(hours)) + Decimal(str(minutes)) / Decimal("60")
|
|
31
|
+
|
|
32
|
+
# Format: 2h30m or 2h 30m
|
|
33
|
+
match = re.match(r"^(\d+(?:\.\d+)?)\s*h\s*(?:(\d+)\s*m)?$", text)
|
|
34
|
+
if match:
|
|
35
|
+
hours = Decimal(match.group(1))
|
|
36
|
+
minutes = Decimal(match.group(2) or "0")
|
|
37
|
+
return hours + minutes / Decimal("60")
|
|
38
|
+
|
|
39
|
+
# Format: 150m
|
|
40
|
+
match = re.match(r"^(\d+(?:\.\d+)?)\s*m$", text)
|
|
41
|
+
if match:
|
|
42
|
+
return Decimal(match.group(1)) / Decimal("60")
|
|
43
|
+
|
|
44
|
+
# Format: 0.25d
|
|
45
|
+
match = re.match(r"^(\d+(?:\.\d+)?)\s*d$", text)
|
|
46
|
+
if match:
|
|
47
|
+
return Decimal(match.group(1)) * Decimal("8")
|
|
48
|
+
|
|
49
|
+
# Format: 2.5h (explicit hours)
|
|
50
|
+
match = re.match(r"^(\d+(?:\.\d+)?)\s*h$", text)
|
|
51
|
+
if match:
|
|
52
|
+
return Decimal(match.group(1))
|
|
53
|
+
|
|
54
|
+
# Format: plain number - assume hours
|
|
55
|
+
match = re.match(r"^(\d+(?:\.\d+)?)$", text)
|
|
56
|
+
if match:
|
|
57
|
+
value = Decimal(match.group(1))
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
raise ValueError(f"Cannot parse duration: '{text}'. Try formats like 2h, 2h30m, 2:30, 150m, 0.25d")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def format_duration(hours: Decimal | float | str | None) -> str:
|
|
64
|
+
"""Format decimal hours into a human-readable string like '2h 30m'."""
|
|
65
|
+
if hours is None:
|
|
66
|
+
return "0m"
|
|
67
|
+
try:
|
|
68
|
+
h = Decimal(str(hours))
|
|
69
|
+
except (InvalidOperation, ValueError):
|
|
70
|
+
return "0m"
|
|
71
|
+
|
|
72
|
+
if h <= 0:
|
|
73
|
+
return "0m"
|
|
74
|
+
|
|
75
|
+
total_minutes = int(h * 60)
|
|
76
|
+
hrs = total_minutes // 60
|
|
77
|
+
mins = total_minutes % 60
|
|
78
|
+
|
|
79
|
+
if hrs and mins:
|
|
80
|
+
return f"{hrs}h {mins}m"
|
|
81
|
+
elif hrs:
|
|
82
|
+
return f"{hrs}h"
|
|
83
|
+
else:
|
|
84
|
+
return f"{mins}m"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_date(text: str) -> date:
|
|
88
|
+
"""Parse a date string into a date object.
|
|
89
|
+
|
|
90
|
+
Accepts:
|
|
91
|
+
"today" -> today
|
|
92
|
+
"yesterday" -> yesterday
|
|
93
|
+
"monday" -> most recent Monday
|
|
94
|
+
"last friday" -> previous week's Friday
|
|
95
|
+
"2026-03-10" -> date(2026, 3, 10)
|
|
96
|
+
"3/10" -> date(current_year, 3, 10)
|
|
97
|
+
"03/10/2026" -> date(2026, 3, 10)
|
|
98
|
+
"""
|
|
99
|
+
text = text.strip().lower()
|
|
100
|
+
today = date.today()
|
|
101
|
+
|
|
102
|
+
if text == "today":
|
|
103
|
+
return today
|
|
104
|
+
|
|
105
|
+
if text == "yesterday":
|
|
106
|
+
return today - timedelta(days=1)
|
|
107
|
+
|
|
108
|
+
# Day names
|
|
109
|
+
day_names = {
|
|
110
|
+
"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3,
|
|
111
|
+
"friday": 4, "saturday": 5, "sunday": 6,
|
|
112
|
+
"mon": 0, "tue": 1, "wed": 2, "thu": 3,
|
|
113
|
+
"fri": 4, "sat": 5, "sun": 6,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# "last <day>"
|
|
117
|
+
match = re.match(r"^last\s+(\w+)$", text)
|
|
118
|
+
if match:
|
|
119
|
+
day_name = match.group(1)
|
|
120
|
+
if day_name in day_names:
|
|
121
|
+
target = day_names[day_name]
|
|
122
|
+
days_back = (today.weekday() - target) % 7
|
|
123
|
+
if days_back == 0:
|
|
124
|
+
days_back = 7
|
|
125
|
+
days_back += 7 # "last" means previous week
|
|
126
|
+
return today - timedelta(days=days_back)
|
|
127
|
+
|
|
128
|
+
# Just a day name (most recent occurrence, including today)
|
|
129
|
+
if text in day_names:
|
|
130
|
+
target = day_names[text]
|
|
131
|
+
days_back = (today.weekday() - target) % 7
|
|
132
|
+
if days_back == 0:
|
|
133
|
+
days_back = 0 # today if it matches
|
|
134
|
+
return today - timedelta(days=days_back)
|
|
135
|
+
|
|
136
|
+
# ISO format: YYYY-MM-DD
|
|
137
|
+
match = re.match(r"^(\d{4})-(\d{1,2})-(\d{1,2})$", text)
|
|
138
|
+
if match:
|
|
139
|
+
return date(int(match.group(1)), int(match.group(2)), int(match.group(3)))
|
|
140
|
+
|
|
141
|
+
# US format: M/D or M/D/YYYY
|
|
142
|
+
match = re.match(r"^(\d{1,2})/(\d{1,2})(?:/(\d{4}))?$", text)
|
|
143
|
+
if match:
|
|
144
|
+
month = int(match.group(1))
|
|
145
|
+
day = int(match.group(2))
|
|
146
|
+
year = int(match.group(3)) if match.group(3) else today.year
|
|
147
|
+
return date(year, month, day)
|
|
148
|
+
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Cannot parse date: '{text}'. Try: today, yesterday, monday, "
|
|
151
|
+
f"last friday, 2026-03-10, 3/10"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def format_date(d: date | None, fmt: str = "%Y-%m-%d") -> str:
|
|
156
|
+
"""Format a date for display."""
|
|
157
|
+
if d is None:
|
|
158
|
+
return ""
|
|
159
|
+
return d.strftime(fmt)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def truncate(text: str, max_length: int = 50) -> str:
|
|
163
|
+
"""Truncate text to a maximum length with ellipsis."""
|
|
164
|
+
if len(text) <= max_length:
|
|
165
|
+
return text
|
|
166
|
+
return text[: max_length - 1] + "\u2026"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crowdtime-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered time tracking CLI — a modern, developer-friendly alternative to Harvest
|
|
5
|
+
Project-URL: Homepage, https://crowdtime.lat
|
|
6
|
+
Project-URL: Documentation, https://crowdtime.lat/docs
|
|
7
|
+
Project-URL: Support, https://crowdtime.lat/support
|
|
8
|
+
Author-email: IT Crowd <dev@itcrowdarg.com>
|
|
9
|
+
License: Proprietary
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,cli,harvest-alternative,productivity,time-tracking
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: Other/Proprietary License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: humanize>=4.0
|
|
26
|
+
Requires-Dist: keyring>=25.0
|
|
27
|
+
Requires-Dist: platformdirs>=4.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0
|
|
29
|
+
Requires-Dist: python-dateutil>=2.9
|
|
30
|
+
Requires-Dist: rich>=13.0
|
|
31
|
+
Requires-Dist: tomlkit>=0.12.0
|
|
32
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# CrowdTime CLI
|
|
36
|
+
|
|
37
|
+
**AI-powered time tracking from your terminal.** A modern, developer-friendly alternative to Harvest.
|
|
38
|
+
|
|
39
|
+
Track time, manage projects, generate reports, and get AI-powered insights — all without leaving your terminal.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Fast time tracking** — Start/stop timers or log entries directly with natural duration formats (`2h30m`, `1:45`, `0.5d`)
|
|
44
|
+
- **AI-powered** — Natural language time entry, smart suggestions based on your patterns, and automatic standup/slack summaries
|
|
45
|
+
- **Rich terminal UI** — Beautiful tables, dashboards, and reports powered by Rich
|
|
46
|
+
- **Multi-org support** — Switch between organizations seamlessly
|
|
47
|
+
- **Secure by default** — API tokens stored in your OS keychain, never in plain text
|
|
48
|
+
- **Flexible reporting** — Daily, weekly, monthly reports in table, JSON, CSV, or Markdown formats
|
|
49
|
+
- **Favorites & templates** — Save common entries for one-command reuse
|
|
50
|
+
- **Claude Code integration** — Ships as a Claude Code skill for AI-assisted time tracking
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install crowdtime-cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Requires Python 3.11+
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Authenticate
|
|
64
|
+
ct config set server.url https://api.crowdtime.lat
|
|
65
|
+
ct auth login
|
|
66
|
+
|
|
67
|
+
# Switch to your organization
|
|
68
|
+
ct org switch my-org
|
|
69
|
+
|
|
70
|
+
# Start tracking
|
|
71
|
+
ct timer start "Building REST API" -p backend -t "Development"
|
|
72
|
+
ct timer stop
|
|
73
|
+
|
|
74
|
+
# Or log time directly
|
|
75
|
+
ct log -p backend -t "Code Review" 2h "Reviewed auth module PR"
|
|
76
|
+
|
|
77
|
+
# Check your day
|
|
78
|
+
ct status
|
|
79
|
+
|
|
80
|
+
# Weekly report
|
|
81
|
+
ct report --week
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Command Overview
|
|
85
|
+
|
|
86
|
+
| Command | Alias | Description |
|
|
87
|
+
|---------|-------|-------------|
|
|
88
|
+
| `ct status` | `ct s` | Dashboard: running timer, today's entries, weekly total |
|
|
89
|
+
| `ct timer start` | `ct ts` | Start a timer with project and task |
|
|
90
|
+
| `ct timer stop` | `ct tx` | Stop the running timer and save the entry |
|
|
91
|
+
| `ct log <duration> <desc>` | `ct l` | Log a time entry directly |
|
|
92
|
+
| `ct log list` | `ct ll` | List time entries (today, --week, --month) |
|
|
93
|
+
| `ct report` | `ct r` | Generate reports with grouping and filters |
|
|
94
|
+
| `ct projects list` | `ct p` | List projects |
|
|
95
|
+
| `ct ai suggest` | — | Get AI suggestions based on your patterns |
|
|
96
|
+
| `ct ai summarize` | — | AI-generated summaries (--for standup, --for slack) |
|
|
97
|
+
|
|
98
|
+
## Duration Formats
|
|
99
|
+
|
|
100
|
+
`2h` | `2h30m` | `2:30` | `150m` | `0.25d` (1 day = 8h) | `1.5` (hours)
|
|
101
|
+
|
|
102
|
+
## Date Formats
|
|
103
|
+
|
|
104
|
+
`today` | `yesterday` | `monday`..`sunday` | `last friday` | `2026-03-10` | `3/10`
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
ct config set server.url https://api.crowdtime.lat # API server
|
|
110
|
+
ct config set defaults.project my-project # Default project
|
|
111
|
+
ct config set defaults.daily_target 7h # Daily hour target
|
|
112
|
+
ct config list # Show all settings
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Config is stored at `~/.crowdtime/config.toml`.
|
|
116
|
+
|
|
117
|
+
## Authentication
|
|
118
|
+
|
|
119
|
+
CrowdTime supports multiple auth methods:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
ct auth login # Browser-based Google OAuth (default)
|
|
123
|
+
ct auth login --token <token> # API token (for CI/scripts)
|
|
124
|
+
ct auth login --no-browser # Token prompt (headless environments)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
API tokens are stored securely in your OS keychain via [keyring](https://pypi.org/project/keyring/).
|
|
128
|
+
|
|
129
|
+
## Requirements
|
|
130
|
+
|
|
131
|
+
- Python 3.11+
|
|
132
|
+
- A CrowdTime account at [crowdtime.lat](https://crowdtime.lat)
|
|
133
|
+
|
|
134
|
+
## Support
|
|
135
|
+
|
|
136
|
+
For issues and feature requests, contact [dev@itcrowdarg.com](mailto:dev@itcrowdarg.com).
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Proprietary. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
crowdtime_cli/__init__.py,sha256=bsIAwxE8SXqqdg_pQ3SOaMHa79IGvoS99majAe3_arM,93
|
|
2
|
+
crowdtime_cli/auth.py,sha256=6MEKCbDaS8qjOsFhkHqqpuza3l5hQ7V3BN28vRTmI64,1660
|
|
3
|
+
crowdtime_cli/client.py,sha256=fiBQ9pMSaJjObaM06fL_BhsZ0pH8mUUCoQnKA9DqP8E,5995
|
|
4
|
+
crowdtime_cli/config.py,sha256=nM32YouP7tsNsInqAYWtsNCesbfLfd7ZWmLolUFx_6U,3533
|
|
5
|
+
crowdtime_cli/formatters.py,sha256=RKYfp0fkoQzJWW7vcAo9kM9z3Ku_fOnFkAzQyjrVXvI,13936
|
|
6
|
+
crowdtime_cli/main.py,sha256=zNKjvSBQEsD17sx2pOERjKDAIYj0FfZd1Nt4g76zxAM,11257
|
|
7
|
+
crowdtime_cli/models.py,sha256=x0RQH0BMAa85pB7W1kbFs9BXITjjuCOY0rFuS3MpS3s,3681
|
|
8
|
+
crowdtime_cli/oauth.py,sha256=BQOfa9A9avck_E6lgR0dY9hNab4qDBfgsg2nhOBVnwA,3158
|
|
9
|
+
crowdtime_cli/resolvers.py,sha256=hN1IX6xRQ4Atmupvv95XIXpesOIwcDYPOutPicEouwU,2640
|
|
10
|
+
crowdtime_cli/utils.py,sha256=30q7IW95BcBhK5nNFUSL-xVf6GwU4XyiL4PUmaiuo6U,4904
|
|
11
|
+
crowdtime_cli/commands/__init__.py,sha256=GW6wnw9GOvllMHGJbGUcnYF0N2iRjeCOAibIgp4BZi8,30
|
|
12
|
+
crowdtime_cli/commands/ai_cmd.py,sha256=bM7NTLjJQ76tpjw9x6lfaN0GXGRpYNHo9KKevlISdFo,7306
|
|
13
|
+
crowdtime_cli/commands/auth_cmd.py,sha256=bFcnVpuhaylCOY6E89pRM9yYUAfa5kM7olAeNgCFNMY,5053
|
|
14
|
+
crowdtime_cli/commands/clients_cmd.py,sha256=L9h_yFxXz6jt4g9hPyu4alYiXtY9cIUUUVSIUl4dfTY,5277
|
|
15
|
+
crowdtime_cli/commands/config_cmd.py,sha256=PPuVS-o8zHspL4PtY5rUvf0IQ2TQY7LeL-PFxR6ygqw,2687
|
|
16
|
+
crowdtime_cli/commands/favorites_cmd.py,sha256=kfJJvzQwgqi7ghgrmWdOHRUlzgBhfv8EKjs3SxgIXEY,3968
|
|
17
|
+
crowdtime_cli/commands/log_cmd.py,sha256=k7hL8CWUnleiGRlrCTMJQtjxRMYMu7FRjA1UlHARMJ4,10175
|
|
18
|
+
crowdtime_cli/commands/org_cmd.py,sha256=sguQmhmBjLHGUweFkM-0ucc6yNrNn89zPr6LCGnuXGI,4179
|
|
19
|
+
crowdtime_cli/commands/projects_cmd.py,sha256=D_w65Zh2kxslLCLR9EYakRF_-seo4mDIOTQWaPYFAxU,6352
|
|
20
|
+
crowdtime_cli/commands/report_cmd.py,sha256=qGHE_12ZV8QF8cUfrXIe9ze0yVasxscoiR8REaVDwcs,8515
|
|
21
|
+
crowdtime_cli/commands/skill_cmd.py,sha256=HZwOfwwLK1KPXufaZxxomoly6qHaTCxILxNFXowpwfA,8083
|
|
22
|
+
crowdtime_cli/commands/tasks_cmd.py,sha256=3L2cGLaVCt00trHB-_Ip4nf67gJacl36kmZhBZg0J4w,3288
|
|
23
|
+
crowdtime_cli/commands/timer_cmd.py,sha256=W_5egB5q8C2M0jEGnUqsbvk5GWG4Q1u9A3hOLYws_hg,6852
|
|
24
|
+
crowdtime_cli/skills/crowdtime/SKILL.md,sha256=kE2TLTXNZA7wavong4jSyLsjr-aikbiPyKkndtIL6bI,9859
|
|
25
|
+
crowdtime_cli/skills/crowdtime/references/commands.md,sha256=yLk9tD_4DmixYhTLm9hpF5MFLPLjbfnlqVriavh8bzo,14506
|
|
26
|
+
crowdtime_cli/skills/crowdtime/references/workflows.md,sha256=q5mdeIYW5U0gbm86NgMPfRPFYVO51n8vcElIRly-xmc,5863
|
|
27
|
+
crowdtime_cli-0.1.0.dist-info/METADATA,sha256=hA3P9f2K3KPJUWGo2KiNAaubzwDhzWiYgTBf9xWq44Q,4710
|
|
28
|
+
crowdtime_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
29
|
+
crowdtime_cli-0.1.0.dist-info/entry_points.txt,sha256=YI72qLC6AMNccU8R77JZ7ukAaMDbjlYxH-WrPCtxhms,103
|
|
30
|
+
crowdtime_cli-0.1.0.dist-info/licenses/LICENSE,sha256=_cpsrHD4WgNNB2xi-jjDZ2h6QxnfIdHN7iAE9DNN370,3172
|
|
31
|
+
crowdtime_cli-0.1.0.dist-info/RECORD,,
|