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.
Files changed (34) hide show
  1. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/PKG-INFO +4 -2
  2. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/README.md +3 -1
  3. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/pyproject.toml +1 -1
  4. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/__init__.py +1 -1
  5. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/org_cmd.py +76 -2
  6. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/formatters.py +14 -2
  7. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/SKILL.md +13 -0
  8. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/references/commands.md +24 -1
  9. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/skills/crowdtime/references/workflows.md +33 -8
  10. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/.gitignore +0 -0
  11. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/LICENSE +0 -0
  12. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/auth.py +0 -0
  13. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/client.py +0 -0
  14. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/__init__.py +0 -0
  15. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/ai_cmd.py +0 -0
  16. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/auth_cmd.py +0 -0
  17. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/clients_cmd.py +0 -0
  18. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/config_cmd.py +0 -0
  19. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/expense_cmd.py +0 -0
  20. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/favorites_cmd.py +0 -0
  21. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/invoice_cmd.py +0 -0
  22. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/log_cmd.py +0 -0
  23. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/projects_cmd.py +0 -0
  24. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/report_cmd.py +0 -0
  25. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/skill_cmd.py +0 -0
  26. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/tasks_cmd.py +0 -0
  27. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/timer_cmd.py +0 -0
  28. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/commands/timesheet_cmd.py +0 -0
  29. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/config.py +0 -0
  30. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/main.py +0 -0
  31. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/models.py +0 -0
  32. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/oauth.py +0 -0
  33. {crowdtime_cli-0.3.0 → crowdtime_cli-0.4.0}/src/crowdtime_cli/resolvers.py +0 -0
  34. {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.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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "crowdtime-cli"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "AI-powered time tracking CLI — a modern, developer-friendly alternative to Harvest"
5
5
  readme = "README.md"
6
6
  license = {text = "Proprietary"}
@@ -1,3 +1,3 @@
1
1
  """CrowdTime CLI - AI-powered time tracking from the command line."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.4.0"
@@ -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=10)
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("member", "--role", "-r", help="Role: admin, manager, member."),
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=10)
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
- m.get("role", "member"),
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 them
176
+ # Invite a regular team member
177
177
  ct org invite newdev@company.com -r member
178
178
 
179
- # They need to:
180
- # 1. Accept the invitation (via email link)
181
- # 2. Install CLI: pip install crowdtime-cli
182
- # 3. Configure:
183
- # ct config set server.url https://your-server.com
184
- # ct auth login
185
- # ct org switch your-org
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