crowdtime-cli 0.8.1__tar.gz → 0.9.0__tar.gz
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-0.8.1 → crowdtime_cli-0.9.0}/PKG-INFO +1 -1
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/pyproject.toml +1 -1
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/__init__.py +1 -1
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/invoice_cmd.py +55 -6
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/log_cmd.py +6 -6
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/skills/crowdtime/references/commands.md +24 -3
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/skills/crowdtime/references/workflows.md +4 -1
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/.gitignore +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/LICENSE +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/README.md +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/auth.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/client.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/__init__.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/ai_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/auth_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/billing_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/clients_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/config_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/expense_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/favorites_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/org_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/payroll_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/projects_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/report_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/skill_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/tasks_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/timer_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/commands/timesheet_cmd.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/config.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/formatters.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/main.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/models.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/oauth.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/resolvers.py +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/skills/crowdtime/SKILL.md +0 -0
- {crowdtime_cli-0.8.1 → crowdtime_cli-0.9.0}/src/crowdtime_cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crowdtime-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: AI-powered time tracking CLI — a modern, developer-friendly alternative to Harvest
|
|
5
5
|
Project-URL: Homepage, https://crowdtime.lat
|
|
6
6
|
Project-URL: Documentation, https://crowdtime.lat/docs
|
|
@@ -267,6 +267,21 @@ def list_invoices(
|
|
|
267
267
|
invoice_type: Optional[str] = typer.Option(
|
|
268
268
|
None, "--type", "-t", help="Filter by type: standard, recurring, retainer."
|
|
269
269
|
),
|
|
270
|
+
date_from: Optional[str] = typer.Option(
|
|
271
|
+
None, "--from", help="Filter by issue date from (YYYY-MM-DD)."
|
|
272
|
+
),
|
|
273
|
+
date_to: Optional[str] = typer.Option(
|
|
274
|
+
None, "--to", help="Filter by issue date to (YYYY-MM-DD)."
|
|
275
|
+
),
|
|
276
|
+
search: Optional[str] = typer.Option(
|
|
277
|
+
None, "--search", "-q", help="Search by invoice number, subject, or client name."
|
|
278
|
+
),
|
|
279
|
+
min_amount: Optional[float] = typer.Option(
|
|
280
|
+
None, "--min-amount", help="Filter by minimum total amount."
|
|
281
|
+
),
|
|
282
|
+
max_amount: Optional[float] = typer.Option(
|
|
283
|
+
None, "--max-amount", help="Filter by maximum total amount."
|
|
284
|
+
),
|
|
270
285
|
output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
271
286
|
) -> None:
|
|
272
287
|
"""List invoices in the current organization.
|
|
@@ -276,6 +291,9 @@ def list_invoices(
|
|
|
276
291
|
ct invoice list --status sent
|
|
277
292
|
ct invoice list --client <client-id> --status paid
|
|
278
293
|
ct invoice list --type recurring
|
|
294
|
+
ct invoice list --from 2026-01-01 --to 2026-03-31
|
|
295
|
+
ct invoice list --search "INV-001"
|
|
296
|
+
ct invoice list --min-amount 1000 --max-amount 5000
|
|
279
297
|
"""
|
|
280
298
|
client = CrowdTimeClient(require_auth=True, require_org=True)
|
|
281
299
|
|
|
@@ -286,6 +304,16 @@ def list_invoices(
|
|
|
286
304
|
params["client"] = client_id
|
|
287
305
|
if invoice_type:
|
|
288
306
|
params["invoice_type"] = invoice_type
|
|
307
|
+
if date_from:
|
|
308
|
+
params["issue_date_from"] = date_from
|
|
309
|
+
if date_to:
|
|
310
|
+
params["issue_date_to"] = date_to
|
|
311
|
+
if search:
|
|
312
|
+
params["search"] = search
|
|
313
|
+
if min_amount is not None:
|
|
314
|
+
params["total_min"] = str(min_amount)
|
|
315
|
+
if max_amount is not None:
|
|
316
|
+
params["total_max"] = str(max_amount)
|
|
289
317
|
|
|
290
318
|
try:
|
|
291
319
|
data = client.get("/invoices/", params=params or None)
|
|
@@ -995,17 +1023,37 @@ def retainer_default(ctx: typer.Context) -> None:
|
|
|
995
1023
|
|
|
996
1024
|
@retainer_app.command("list")
|
|
997
1025
|
def list_retainers(
|
|
1026
|
+
status: Optional[str] = typer.Option(
|
|
1027
|
+
None, "--status", "-s", help="Filter by status: pending, active, paused, completed, cancelled."
|
|
1028
|
+
),
|
|
1029
|
+
client_id: Optional[str] = typer.Option(
|
|
1030
|
+
None, "--client", "-c", help="Filter by client ID."
|
|
1031
|
+
),
|
|
1032
|
+
search: Optional[str] = typer.Option(
|
|
1033
|
+
None, "--search", "-q", help="Search by retainer name or client name."
|
|
1034
|
+
),
|
|
998
1035
|
output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
999
1036
|
) -> None:
|
|
1000
1037
|
"""List retainer agreements.
|
|
1001
1038
|
|
|
1002
1039
|
Examples:
|
|
1003
1040
|
ct invoice retainer list
|
|
1041
|
+
ct invoice retainer list --status active
|
|
1042
|
+
ct invoice retainer list --client <client-id>
|
|
1043
|
+
ct invoice retainer list --search "Acme"
|
|
1004
1044
|
"""
|
|
1005
1045
|
client = CrowdTimeClient(require_auth=True, require_org=True)
|
|
1006
1046
|
|
|
1047
|
+
params: dict = {}
|
|
1048
|
+
if status:
|
|
1049
|
+
params["status"] = status
|
|
1050
|
+
if client_id:
|
|
1051
|
+
params["client"] = client_id
|
|
1052
|
+
if search:
|
|
1053
|
+
params["search"] = search
|
|
1054
|
+
|
|
1007
1055
|
try:
|
|
1008
|
-
data = client.get("/invoices/retainers/")
|
|
1056
|
+
data = client.get("/invoices/retainers/", params=params or None)
|
|
1009
1057
|
results = data.get("results", []) if isinstance(data, dict) else data
|
|
1010
1058
|
|
|
1011
1059
|
if output_json:
|
|
@@ -1020,21 +1068,22 @@ def list_retainers(
|
|
|
1020
1068
|
table.add_column("Name", width=20)
|
|
1021
1069
|
table.add_column("Client", width=18)
|
|
1022
1070
|
table.add_column("Monthly", justify="right", width=12)
|
|
1023
|
-
table.add_column("
|
|
1071
|
+
table.add_column("Balance", justify="right", width=12)
|
|
1024
1072
|
table.add_column("Status", width=10)
|
|
1025
1073
|
|
|
1026
1074
|
for ret in results:
|
|
1027
|
-
|
|
1028
|
-
style = "green" if
|
|
1075
|
+
ret_status = ret.get("status", "active")
|
|
1076
|
+
style = "green" if ret_status == "active" else "dim"
|
|
1029
1077
|
currency = ret.get("currency", "USD")
|
|
1078
|
+
balance = Decimal(str(ret.get("balance", 0)))
|
|
1030
1079
|
|
|
1031
1080
|
table.add_row(
|
|
1032
1081
|
str(ret.get("id", "")),
|
|
1033
1082
|
ret.get("name", ret.get("title", "")),
|
|
1034
1083
|
ret.get("client_name", ""),
|
|
1035
1084
|
_format_currency(ret.get("monthly_amount", ret.get("monthly", 0)), currency),
|
|
1036
|
-
|
|
1037
|
-
f"[{style}]{
|
|
1085
|
+
f"[green]{_format_currency(balance, currency)}[/green]",
|
|
1086
|
+
f"[{style}]{ret_status.capitalize()}[/{style}]",
|
|
1038
1087
|
)
|
|
1039
1088
|
|
|
1040
1089
|
console.print(table)
|
|
@@ -246,23 +246,23 @@ def _list_entries(
|
|
|
246
246
|
elif week:
|
|
247
247
|
today = date_type.today()
|
|
248
248
|
start = today - timedelta(days=today.weekday())
|
|
249
|
-
params["
|
|
250
|
-
params["
|
|
249
|
+
params["date_from"] = format_date(start)
|
|
250
|
+
params["date_to"] = format_date(today)
|
|
251
251
|
elif month:
|
|
252
252
|
today = date_type.today()
|
|
253
253
|
start = today.replace(day=1)
|
|
254
|
-
params["
|
|
255
|
-
params["
|
|
254
|
+
params["date_from"] = format_date(start)
|
|
255
|
+
params["date_to"] = format_date(today)
|
|
256
256
|
|
|
257
257
|
if from_date:
|
|
258
258
|
try:
|
|
259
|
-
params["
|
|
259
|
+
params["date_from"] = format_date(parse_date(from_date))
|
|
260
260
|
except ValueError as e:
|
|
261
261
|
format_error(str(e))
|
|
262
262
|
raise typer.Exit(1)
|
|
263
263
|
if to_date:
|
|
264
264
|
try:
|
|
265
|
-
params["
|
|
265
|
+
params["date_to"] = format_date(parse_date(to_date))
|
|
266
266
|
except ValueError as e:
|
|
267
267
|
format_error(str(e))
|
|
268
268
|
raise typer.Exit(1)
|
|
@@ -1183,13 +1183,19 @@ Endpoint: `GET /timesheets/team-overview/`
|
|
|
1183
1183
|
### ct invoice list
|
|
1184
1184
|
|
|
1185
1185
|
```
|
|
1186
|
-
ct invoice list [--status STATUS] [--client CLIENT] [--json]
|
|
1186
|
+
ct invoice list [--status STATUS] [--client CLIENT] [--type TYPE] [--from DATE] [--to DATE] [--search QUERY] [--min-amount NUM] [--max-amount NUM] [--json]
|
|
1187
1187
|
```
|
|
1188
1188
|
|
|
1189
1189
|
| Option | Type | Description |
|
|
1190
1190
|
|--------|------|-------------|
|
|
1191
|
-
| `--status` | string | Filter: `draft`, `sent`, `viewed`, `partial`, `paid`, `overdue`, `void` |
|
|
1192
|
-
| `--client` | string | Filter by client
|
|
1191
|
+
| `--status` `-s` | string | Filter: `draft`, `sent`, `viewed`, `partial`, `paid`, `overdue`, `void` |
|
|
1192
|
+
| `--client` `-c` | string | Filter by client ID |
|
|
1193
|
+
| `--type` `-t` | string | Filter by type: `standard`, `recurring`, `retainer` |
|
|
1194
|
+
| `--from` | string | Filter by issue date from (YYYY-MM-DD) |
|
|
1195
|
+
| `--to` | string | Filter by issue date to (YYYY-MM-DD) |
|
|
1196
|
+
| `--search` `-q` | string | Search by invoice number, subject, or client name |
|
|
1197
|
+
| `--min-amount` | float | Filter by minimum total amount |
|
|
1198
|
+
| `--max-amount` | float | Filter by maximum total amount |
|
|
1193
1199
|
| `--json` | flag | JSON output |
|
|
1194
1200
|
|
|
1195
1201
|
Endpoint: `GET /invoices/`
|
|
@@ -1296,6 +1302,21 @@ Creates a copy of an existing invoice as a new draft. Endpoint: `POST /invoices/
|
|
|
1296
1302
|
|
|
1297
1303
|
---
|
|
1298
1304
|
|
|
1305
|
+
### ct invoice retainer list
|
|
1306
|
+
|
|
1307
|
+
```
|
|
1308
|
+
ct invoice retainer list [--status STATUS] [--client CLIENT] [--search QUERY] [--json]
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
| Option | Type | Description |
|
|
1312
|
+
|--------|------|-------------|
|
|
1313
|
+
| `--status` `-s` | string | Filter: `pending`, `active`, `paused`, `completed`, `cancelled` |
|
|
1314
|
+
| `--client` `-c` | string | Filter by client ID |
|
|
1315
|
+
| `--search` `-q` | string | Search by retainer name or client name |
|
|
1316
|
+
| `--json` | flag | JSON output |
|
|
1317
|
+
|
|
1318
|
+
Endpoint: `GET /invoices/retainers/`
|
|
1319
|
+
|
|
1299
1320
|
### ct invoice retainer show
|
|
1300
1321
|
|
|
1301
1322
|
```
|
|
@@ -574,8 +574,11 @@ ct invoice show <new-invoice-id>
|
|
|
574
574
|
### Set Up a Retainer and Track Balance
|
|
575
575
|
|
|
576
576
|
```bash
|
|
577
|
-
# 1. List retainers
|
|
577
|
+
# 1. List retainers (with optional filters)
|
|
578
578
|
ct invoice retainer list
|
|
579
|
+
ct invoice retainer list --status active
|
|
580
|
+
ct invoice retainer list --client <client-id>
|
|
581
|
+
ct invoice retainer list --search "Acme"
|
|
579
582
|
|
|
580
583
|
# 2. View retainer details and balance
|
|
581
584
|
ct invoice retainer show <retainer-id>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|