crowdtime-cli 0.3.0__tar.gz → 0.4.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.3.0 → crowdtime_cli-0.4.0}/PKG-INFO +4 -2
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/README.md +3 -1
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/pyproject.toml +1 -1
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/__init__.py +1 -1
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/org_cmd.py +76 -2
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/formatters.py +14 -2
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/SKILL.md +13 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/references/commands.md +24 -1
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/references/workflows.md +33 -8
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/.gitignore +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/LICENSE +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/auth.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/client.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/__init__.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/ai_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/auth_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/clients_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/config_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/expense_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/favorites_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/invoice_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/log_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/projects_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/report_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/skill_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/tasks_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/timer_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/timesheet_cmd.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/config.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/main.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/models.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/oauth.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/resolvers.py +0 -0
- {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.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.4.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
|
|
@@ -118,7 +118,9 @@ ct report --week
|
|
|
118
118
|
| `ct org list` | — | List your organizations |
|
|
119
119
|
| `ct org switch <slug>` | — | Switch active organization |
|
|
120
120
|
| `ct org members` | — | List organization members |
|
|
121
|
-
| `ct org invite <email>` | — | Invite a new member |
|
|
121
|
+
| `ct org invite <email>` | — | Invite a new member (roles: viewer, member, project_manager, manager, admin) |
|
|
122
|
+
| `ct org invitations` | — | List pending invitations |
|
|
123
|
+
| `ct org resend-invite <id>` | — | Resend a pending invitation email |
|
|
122
124
|
|
|
123
125
|
### Reporting & AI
|
|
124
126
|
|
|
@@ -84,7 +84,9 @@ ct report --week
|
|
|
84
84
|
| `ct org list` | — | List your organizations |
|
|
85
85
|
| `ct org switch <slug>` | — | Switch active organization |
|
|
86
86
|
| `ct org members` | — | List organization members |
|
|
87
|
-
| `ct org invite <email>` | — | Invite a new member |
|
|
87
|
+
| `ct org invite <email>` | — | Invite a new member (roles: viewer, member, project_manager, manager, admin) |
|
|
88
|
+
| `ct org invitations` | — | List pending invitations |
|
|
89
|
+
| `ct org resend-invite <id>` | — | Resend a pending invitation email |
|
|
88
90
|
|
|
89
91
|
### Reporting & AI
|
|
90
92
|
|
|
@@ -43,7 +43,7 @@ def list_orgs(
|
|
|
43
43
|
table.add_column("", width=3)
|
|
44
44
|
table.add_column("Name", width=20)
|
|
45
45
|
table.add_column("Slug", width=15)
|
|
46
|
-
table.add_column("Role", width=
|
|
46
|
+
table.add_column("Role", width=18)
|
|
47
47
|
table.add_column("Members", justify="right", width=8)
|
|
48
48
|
|
|
49
49
|
for org in orgs:
|
|
@@ -113,7 +113,10 @@ def members(
|
|
|
113
113
|
@app.command()
|
|
114
114
|
def invite(
|
|
115
115
|
email: str = typer.Argument(..., help="Email address to invite."),
|
|
116
|
-
role: str = typer.Option(
|
|
116
|
+
role: str = typer.Option(
|
|
117
|
+
"member", "--role", "-r",
|
|
118
|
+
help="Role: admin, manager (account), project_manager, member, viewer.",
|
|
119
|
+
),
|
|
117
120
|
output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
118
121
|
) -> None:
|
|
119
122
|
"""Invite a member to the current organization."""
|
|
@@ -132,3 +135,74 @@ def invite(
|
|
|
132
135
|
except APIError as e:
|
|
133
136
|
format_error(e.message)
|
|
134
137
|
raise typer.Exit(1)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command("resend-invite")
|
|
141
|
+
def resend_invite(
|
|
142
|
+
invitation_id: str = typer.Argument(..., help="Invitation ID to resend."),
|
|
143
|
+
output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Resend a pending invitation email."""
|
|
146
|
+
client = CrowdTimeClient(require_auth=True, require_org=True)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
data = client.post(f"/invitations/{invitation_id}/resend/")
|
|
150
|
+
|
|
151
|
+
if output_json:
|
|
152
|
+
print_json(data)
|
|
153
|
+
else:
|
|
154
|
+
format_success("Invitation email resent.")
|
|
155
|
+
except APIError as e:
|
|
156
|
+
format_error(e.message)
|
|
157
|
+
raise typer.Exit(1)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@app.command()
|
|
161
|
+
def invitations(
|
|
162
|
+
output_json: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
163
|
+
) -> None:
|
|
164
|
+
"""List pending invitations for the current organization."""
|
|
165
|
+
client = CrowdTimeClient(require_auth=True, require_org=True)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
data = client.get("/invitations/")
|
|
169
|
+
inv_list = data if isinstance(data, list) else data.get("results", [])
|
|
170
|
+
|
|
171
|
+
if output_json:
|
|
172
|
+
print_json(inv_list)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
pending = [i for i in inv_list if i.get("status") == "pending"]
|
|
176
|
+
if not pending:
|
|
177
|
+
console.print("[dim]No pending invitations.[/dim]")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
table = Table(show_header=True, header_style="bold")
|
|
181
|
+
table.add_column("ID", style="dim")
|
|
182
|
+
table.add_column("Email", width=25)
|
|
183
|
+
table.add_column("Role", width=18)
|
|
184
|
+
table.add_column("Status", width=10)
|
|
185
|
+
table.add_column("Expires", width=12)
|
|
186
|
+
|
|
187
|
+
role_labels = {
|
|
188
|
+
"owner": "Owner",
|
|
189
|
+
"admin": "Admin",
|
|
190
|
+
"manager": "Account Manager",
|
|
191
|
+
"project_manager": "Project Manager",
|
|
192
|
+
"member": "Member",
|
|
193
|
+
"viewer": "Viewer",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for inv in pending:
|
|
197
|
+
table.add_row(
|
|
198
|
+
inv.get("id", ""),
|
|
199
|
+
inv.get("email", ""),
|
|
200
|
+
role_labels.get(inv.get("role", ""), inv.get("role", "")),
|
|
201
|
+
inv.get("status", ""),
|
|
202
|
+
inv.get("expires_at", "")[:10] if inv.get("expires_at") else "",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
console.print(table)
|
|
206
|
+
except APIError as e:
|
|
207
|
+
format_error(e.message)
|
|
208
|
+
raise typer.Exit(1)
|
|
@@ -362,6 +362,16 @@ def format_report(data: list[dict[str, Any]], group_by: str = "project") -> None
|
|
|
362
362
|
console.print(table)
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
ROLE_LABELS = {
|
|
366
|
+
"owner": "Owner",
|
|
367
|
+
"admin": "Admin",
|
|
368
|
+
"manager": "Account Manager",
|
|
369
|
+
"project_manager": "Project Manager",
|
|
370
|
+
"member": "Member",
|
|
371
|
+
"viewer": "Viewer",
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
365
375
|
def format_members_table(members: list[dict[str, Any]]) -> None:
|
|
366
376
|
"""Display organization members in a table."""
|
|
367
377
|
if not members:
|
|
@@ -371,15 +381,17 @@ def format_members_table(members: list[dict[str, Any]]) -> None:
|
|
|
371
381
|
table = Table(show_header=True, header_style="bold")
|
|
372
382
|
table.add_column("Name", width=20)
|
|
373
383
|
table.add_column("Email", width=25)
|
|
374
|
-
table.add_column("Role", width=
|
|
384
|
+
table.add_column("Role", width=18)
|
|
375
385
|
table.add_column("Status", width=10)
|
|
376
386
|
|
|
377
387
|
for m in members:
|
|
378
388
|
status = "[green]Active[/green]" if m.get("is_active", True) else "[dim]Inactive[/dim]"
|
|
389
|
+
role = m.get("role", "member")
|
|
390
|
+
role_label = ROLE_LABELS.get(role, role)
|
|
379
391
|
table.add_row(
|
|
380
392
|
m.get("user_name", ""),
|
|
381
393
|
m.get("user_email", ""),
|
|
382
|
-
|
|
394
|
+
role_label,
|
|
383
395
|
status,
|
|
384
396
|
)
|
|
385
397
|
|
|
@@ -213,9 +213,22 @@ ct invoice retainer reverse-withdrawal <id> -w <wd-id> -r "reason" # Reverse a
|
|
|
213
213
|
ct org list # List your organizations
|
|
214
214
|
ct org switch <slug> # Switch active organization
|
|
215
215
|
ct org members # List members
|
|
216
|
+
ct org invitations # List pending invitations
|
|
216
217
|
ct org invite user@example.com -r member
|
|
218
|
+
ct org invite client@acme.com -r viewer # External client stakeholder (read-only)
|
|
219
|
+
ct org invite pm@company.com -r project_manager # Project manager (no financial access)
|
|
220
|
+
ct org invite billing@company.com -r manager # Account manager (full financial access)
|
|
221
|
+
ct org resend-invite <invitation-id> # Resend a pending invitation
|
|
217
222
|
```
|
|
218
223
|
|
|
224
|
+
**Role hierarchy** (lowest → highest):
|
|
225
|
+
- **viewer** — external client stakeholder, read-only access scoped to assigned projects
|
|
226
|
+
- **member** — can log time, manage own entries
|
|
227
|
+
- **project_manager** — manage projects, tasks, timesheets, team time; no financial data
|
|
228
|
+
- **manager** (Account Manager) — everything project_manager does + invoices, rates, clients, billing
|
|
229
|
+
- **admin** — manage org settings, members, integrations
|
|
230
|
+
- **owner** — full control, can delete org
|
|
231
|
+
|
|
219
232
|
### Configuration
|
|
220
233
|
```bash
|
|
221
234
|
ct config list # Show all config
|
|
@@ -745,10 +745,33 @@ ct org invite EMAIL [--role/-r ROLE] [--json]
|
|
|
745
745
|
|
|
746
746
|
| Option | Type | Description |
|
|
747
747
|
|--------|------|-------------|
|
|
748
|
-
| `--role`, `-r` | string | `admin`, `manager`, `member` (default) |
|
|
748
|
+
| `--role`, `-r` | string | `admin`, `manager`, `project_manager`, `member` (default), `viewer` |
|
|
749
|
+
|
|
750
|
+
**Roles:**
|
|
751
|
+
- `viewer` — external client stakeholder, read-only access scoped to assigned projects
|
|
752
|
+
- `member` — can log time, manage own entries
|
|
753
|
+
- `project_manager` — manage projects, tasks, timesheets, team time; no financial data (rates, invoices)
|
|
754
|
+
- `manager` — Account Manager: project management + invoices, rates, clients, billing
|
|
755
|
+
- `admin` — manage org settings, members, integrations
|
|
749
756
|
|
|
750
757
|
Endpoint: `POST /members/invite/`
|
|
751
758
|
|
|
759
|
+
### ct org invitations
|
|
760
|
+
|
|
761
|
+
```
|
|
762
|
+
ct org invitations [--json]
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Lists pending invitations for the current organization. Endpoint: `GET /invitations/`
|
|
766
|
+
|
|
767
|
+
### ct org resend-invite
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
ct org resend-invite INVITATION_ID [--json]
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
Resends the invitation email for a pending invitation. Endpoint: `POST /invitations/{id}/resend/`
|
|
774
|
+
|
|
752
775
|
---
|
|
753
776
|
|
|
754
777
|
## ct config
|
|
@@ -173,18 +173,43 @@ ct l -p acme -d 2026-03-05 3h "client meeting"
|
|
|
173
173
|
### Onboard a New Team Member
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
|
-
# Invite
|
|
176
|
+
# Invite a regular team member
|
|
177
177
|
ct org invite newdev@company.com -r member
|
|
178
178
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
#
|
|
179
|
+
# Invite a project manager (can manage projects/tasks/timesheets, no financial access)
|
|
180
|
+
ct org invite pm@company.com -r project_manager
|
|
181
|
+
|
|
182
|
+
# Invite an account manager (full financial access: invoices, rates, clients)
|
|
183
|
+
ct org invite billing@company.com -r manager
|
|
184
|
+
|
|
185
|
+
# Invite an external client stakeholder (read-only, scoped to their projects)
|
|
186
|
+
ct org invite client@acme.com -r viewer
|
|
187
|
+
|
|
188
|
+
# They receive an email with an "Accept Invitation" link
|
|
189
|
+
# New users can sign up with email/password or Google OAuth
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Manage Invitations
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# List pending invitations
|
|
196
|
+
ct org invitations
|
|
197
|
+
|
|
198
|
+
# Resend an invitation email
|
|
199
|
+
ct org resend-invite <invitation-id>
|
|
186
200
|
```
|
|
187
201
|
|
|
202
|
+
### Role Reference
|
|
203
|
+
|
|
204
|
+
| Role | Description |
|
|
205
|
+
|------|-------------|
|
|
206
|
+
| `viewer` | External client: read-only, scoped to assigned projects |
|
|
207
|
+
| `member` | Logs time, manages own entries |
|
|
208
|
+
| `project_manager` | Manages projects, tasks, timesheets, team time. No financial data |
|
|
209
|
+
| `manager` | Account Manager: everything above + invoices, rates, clients, billing |
|
|
210
|
+
| `admin` | Manages org settings, members, integrations |
|
|
211
|
+
| `owner` | Full control including org deletion |
|
|
212
|
+
|
|
188
213
|
### Check Team Activity
|
|
189
214
|
|
|
190
215
|
```bash
|
|
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
|