CLI-first tool with MCP support for managing SSW TimePro timesheets. Built for AI agents (Claude Code, Codex, VS Code Copilot) and human users.
This project uses the existing TimePro API with your own credentials.
SSW TimePro is a time tracking and invoicing system. This CLI makes it fast to view, create, and manage timesheets from the terminal — and exposes the same capabilities to AI agents via MCP.
- Week View — Compact or detailed view of your timesheets with totals, billable hours, and missing days
- Timesheet CRUD — Create, update, delete timesheets with rate checking and lock detection
- Suggested Timesheets — View and accept suggested timesheets to keep accuracy stats high
- CRM Bookings — See your appointments from the CRM calendar
- Leave Management — Create, list, and cancel EasyLeave requests
- Repo Mapping — Map git repos to clients/projects; auto-detects via path or remote URL, with git worktree support; optional
--issues-repofor projects whose issues live in a different GitHub repo than the code - Daily Scrum — Generate an SSW-format daily scrum email from timesheets, CRM bookings and GitHub activity, with rich-text / markdown / plain clipboard support and an interactive copy mode
- Location Defaults — Set WFH days so location is auto-applied when creating timesheets
- CSV Export — Export timesheets for tax reports or analysis
- Skills Generation — Generate agent skill files with project context and
ghcommands - MCP Server — Exposes timesheet, lookup, leave, accounting, and prepaid tools for AI agents via stdio transport
- .NET 10 SDK
- A TimePro account with API access
- (Optional) GitHub CLI for issue/PR references in timesheets
git clone https://github.com/SSWConsulting/timepro.tools.git
cd timepro.tools
dotnet pack src/SSW.TimePro.Cli/ -c Release -o artifacts/nupkg
dotnet tool install -g --add-source artifacts/nupkg SSW.TimePro.CliThis makes the tp command available system-wide.
To update after pulling new changes:
cd timepro.tools
git pull
dotnet pack src/SSW.TimePro.Cli/ -c Release -o artifacts/nupkg
dotnet tool uninstall -g SSW.TimePro.Cli
dotnet tool install -g --add-source artifacts/nupkg SSW.TimePro.CliTo uninstall:
dotnet tool uninstall -g SSW.TimePro.CliAlternative: Run from project directory without global install
git clone https://github.com/SSWConsulting/timepro.tools.git
cd timepro.tools
dotnet buildThen prefix all commands with dotnet run:
dotnet run --project src/SSW.TimePro.Cli -- login --tenant ssw
dotnet run --project src/SSW.TimePro.Cli -- ts get --weektp login --tenant sswThis sets your tenant and prompts for an API token. To get your token:
- Visit
https://{tenant}.sswtimepro.com/b/admin/api-key - Copy the API key
For non-interactive login (CI, scripts):
tp login --tenant ssw --token YOUR_API_KEY --api-url https://api.sswtimepro.comYour employee ID and name are auto-detected on login. Credentials are stored at ~/.config/timepro-cli/tenants/{tenant}.json.
tp ts get --week # This week (compact view)
tp ts get --week --detailed # With full descriptions, invoices, lock status
tp ts get --week -1 # Last week
tp ts get 2026-03-12 # Specific date| Command | Description |
|---|---|
tp login --tenant ID |
Authenticate with a TimePro tenant |
tp logout |
Remove stored credentials |
tp tenant set ID |
Switch active tenant |
tp tenant info |
Show active tenant details |
tp tenant list |
List all stored tenants |
tp ts get [DATE] |
View timesheets (supports --week, --detailed, --json) |
tp ts create |
Create a new timesheet (see options below) |
tp ts update ID |
Update a timesheet (partial — only specified fields) |
tp ts delete ID |
Delete a timesheet |
tp ts suggest [DATE] |
View suggested timesheets |
tp ts accept ID |
Accept a suggested timesheet |
tp ts export |
Export timesheets to CSV |
tp ts check |
Leave-aware week validation: gaps, overlaps, missing descriptions, under/over hours; approved leave/holidays count as covered (--week N, --json) |
tp ts copy |
Copy timesheets from one day to another |
tp bk list |
List CRM bookings/appointments |
tp leave list |
List leave entries (--filter UPCOMING|PAST) |
tp leave balance |
Show leave-usage signal (--emp-id): days since last leave + hours taken in last 12 months |
tp leave create |
Create a leave request (see options below) |
tp leave cancel ID |
Cancel a leave request (--reason) |
tp cl search QUERY |
Search for clients |
tp proj list --client ID |
List projects for a client |
tp proj recent |
Surface projects you've recently logged time against (likely picks for new entries) |
tp rate get --client ID |
Get billing rate (with expiry warnings) |
tp iter list --project ID |
List iterations/sprints for a project |
tp summary |
Project hours breakdown (--week, --month, --from/--to) |
tp report |
Monthly summary with billable %, WFH breakdown |
tp loc info [--date D] |
Show location defaults / check a specific date |
tp loc set LOC --day D |
Set WFH day defaults |
tp map set PATH |
Map a repo path to a client/project |
tp map detect |
Detect mapping for current directory |
tp map list |
List all repo mappings |
tp map remove PATH |
Remove a repo mapping |
tp query |
Query timesheets across employees/projects (--group-by, --json) |
tp scrum |
Generate a daily scrum email from timesheets + GitHub (-i, --copy --format rich|markdown|plain, --json) |
tp skills create TARGET |
Generate agent skill files (--accounting for the accountant CLI skill) |
tp user me |
Show current user info |
tp blog list |
Latest blog posts (--mine, --limit N, --all) |
tp mcp |
Start MCP server (stdio); --tenant NAME binds the session to a specific tenant config without changing the global active tenant |
| Accountant (read-only) | See docs/accounting.md |
tp invoice list / get / lines / timesheets / receipts |
Invoices |
tp receipt list / get / outstanding |
Receipts + aged debtors |
tp creditnote list --client ID |
Credit notes |
tp product list / get / discounts |
Sale products / SKUs |
tp rate list --client ID |
Configured rates across all employees |
tp client outstanding |
Clients with unbilled time |
tp unbilled list --client ID |
Unbilled timesheets for a client |
tp recurring list / get |
Recurring invoice templates |
tp prepaid summary / status INVOICE_ID |
Prepaid drawdown totals / PDF report |
All read commands support --json for machine-readable output. All write commands support --yes to skip confirmation prompts.
--json error contract: on failure, the command emits a structured envelope to stdout so stdout stays valid JSON even when an API call fails — {"error":{"code":<int|null>,"message":"...","detail":<string|null>}} (all keys always present) — and exits non-zero. Human-readable error/warning text always goes to stderr, so it never corrupts the JSON stream.
Every command group has a short alias:
| Full | Alias |
|---|---|
tp timesheet |
tp ts |
tp booking |
tp bk |
tp leave |
tp lv |
tp client |
tp cl |
tp project |
tp proj |
tp location |
tp loc |
tp iteration |
tp iter |
tp invoice |
tp inv |
tp receipt |
tp rcpt |
tp creditnote |
tp cn |
tp product |
tp prod |
tp ts create \
--client NWIND \
--project 1I776Q \
--date 2026-03-16 \
--start 09:00 \
--end 18:00 \
--less 60 \
--description "Added product search API endpoint — PR #42 · #108" \
--location Home \
--billable B \
--yesWhen creating timesheets:
- Category is auto-resolved from repo-mappings or recent timesheets (past 14 days); pass
--categoryto override - Rate is checked automatically; you'll get a warning if it's expired or expiring soon
- Location defaults to your WFH settings if not specified
- Locked timesheets (invoiced) only allow location and description changes
- Duplicate detection — if a timesheet already exists for the time slot, you'll get a clear error suggesting
tp ts updateinstead - API error details — validation errors now show the specific field and message from the API
# List upcoming leave
tp leave list --filter UPCOMING --json
# Create a full-day leave request
tp leave create --start 2026-03-30 --end 2026-03-30 --type 1 \
--note "Returning from MVP Summit" --yes
# Create with approver and CC
tp leave create --start 2026-03-30 --end 2026-03-30 --type "Annual Leave" \
--note "Returning from MVP Summit" \
--approved-by "approver@northwind.example" \
--cc "notify1@northwind.example,notify2@northwind.example" --yes
# Cancel a leave request
tp leave cancel <ID> --reason "Plans changed" --yesLeave create options:
--typeaccepts a numeric ID (e.g.,1) or name (e.g.,"Annual Leave")--approved-bysets the approver's email--cccomma-separated list of emails to notify--half-dayfor partial day requests (start and end date must be the same)--start-time/--end-timeoverride defaults (09:00/18:00)
Compact view shows one line per timesheet with totals:
Week of Mar 10 - Mar 14, 2026
─────────────────────────────────────────────────────────────────────
Mon 10 │ 8.0h │ Northwind Northwind Traders 4.0h Home B
│ │ Northwind Internal tooling 4.0h Home W
Tue 11 │ 8.0h │ Northwind Northwind Traders 8.0h Home B
Wed 12 │ 0.0h │ No timesheets
Thu 13 │ 8.0h │ Northwind Northwind Traders 8.0h Office B
Fri 14 │ 8.0h │ Northwind Planning 8.0h Office W
─────────────────────────────────────────────────────────────────────
Total: 32.0h / 40.0h │ Billable: 20.0h │ Missing: Wed
Detailed view (--detailed) shows full descriptions, invoice info, and suggested timesheets.
Map repositories to clients/projects so AI agents know what to bill:
tp map set ~/Developer/git/Northwind/traders-app \
--remote github.com/Northwind/traders-app \
--client NWIND --project 1I776Q --project-name "Northwind Traders" \
--category WEBDEV
tp map set ~/Developer/git/Northwind/traders-api \
--client NWIND --project 1I776Q --project-name "Northwind Traders API" \
--category WEBDEVThe --category is recommended — it enables tp ts create to auto-resolve the category without --category on every call. When updating an existing mapping, omitting --category, --project-name, --remote, or --issues-repo preserves their current values. ~-prefixed and absolute paths are deduped against each other, so re-running set on the same repo updates in place rather than creating a duplicate entry.
Issues repo: when a project's issues/PRs live in a different GitHub repo than the code (for example the code sits in a sandbox repo but issues are tracked in the main product repo), add --issues-repo owner/repo. This is what tp scrum uses to resolve PR and issue references:
tp map set ~/Developer/git/Northwind/traders-mobile \
--client NWIND --project 1I776Q \
--issues-repo Northwind/traders-apptp map list shows all mappings including their category and issues repo:
┌──────────────────────┬────────┬─────────┬────────────────────┬──────────┬─────────────────────────┐
│ Path / Remote │ Client │ Project │ Name │ Category │ Issues repo │
├──────────────────────┼────────┼─────────┼────────────────────┼──────────┼─────────────────────────┤
│ ~/Developer/git/Nort │ NWIND │ 1I776Q │ Northwind Traders │ WEBDEV │ — │
│ hwind/traders-app │ │ │ │ │ │
│ ~/Developer/git/Nort │ NWIND │ 1I776Q │ Northwind Traders │ WEBDEV │ Northwind/traders-app │
│ hwind/traders-mobile │ │ │ │ │ │
└──────────────────────┴────────┴─────────┴────────────────────┴──────────┴─────────────────────────┘
Detection supports:
- Exact path —
~/Developer/git/SSW.TimePRO - Glob patterns —
~/Developer/git/Northwind/* - Git remote URL —
github.com/Northwind/traders-app - Git worktrees — Codex/Claude worktrees at
~/.codex/worktrees/or~/.claude-worktrees/resolve to the main repo automatically
tp map detect # Shows matched client/project for current directorySet which days you work from home:
tp location set Home --day Mon,Tue
tp location info # Show defaults
tp location info --date 2026-03-16 # Check a specific dateWhen creating timesheets, location is auto-applied based on the day of week.
Generate agent skill files for a project:
tp skills create .agents # Local to current project
tp skills create .claude --global # Global (all projects)The generated file includes:
- Quick reference for all
tpcommands - Workflow for entering a full week of timesheets
ghcommands pre-filled with the repo slug for issue/PR lookup- Description format guide with PR and issue number examples
- Project context (client, project, GitHub repo) auto-detected from repo mapping
Quick overview of where your time goes:
tp summary --week -1 # Last week's hours by project
tp summary --month # This month to date
tp summary --from 2026-01-01 --to 2026-03-31 # Custom range
tp report # This month: billable %, WFH breakdown, projects
tp report --month -1 # Last monthCheck for gaps before submitting timesheets:
tp ts check # This week
tp ts check --week -1 # Last week
tp ts check --week --json # Machine-readable, per-day breakdownChecks for: missing days, under/over hours, overlapping entries, missing descriptions, unaccepted suggestions. Returns exit code 1 when errors are found (CI-friendly).
Leave-aware: approved leave and public holidays count toward coverage, so a full-day leave day is reported as covered (never an error) and a partial-day leave only expects the remaining hours. The --json output carries a per-day covered / coverReason (logged, leave-full, leave-partial, holiday, missing), leaveHours / leaveType, plus a top-level allCovered flag.
Duplicate a day's timesheets to another day (e.g., "Tuesday was the same client as Monday"):
tp ts copy --from 2026-03-10 --to 2026-03-11 --yes
tp ts copy --from 2026-03-10 --to 2026-03-11 --keep-description --yesSearch timesheets across employees, clients, and projects with flexible grouping:
# Who worked on Northwind this FY? (grouped by employee)
tp query --client NWIND --project 1I776Q --from 2024-07-01 --to 2025-06-30
# All Northwind projects by hours (grouped by project)
tp query --client NWIND --from 2024-07-01 --to 2025-06-30 --group-by project
# Flat view with pagination
tp query --client NWIND --group-by none --limit 20 --page 2
# Export raw JSON for analysis
tp query --client NWIND --project 1I776Q --from 2024-07-01 --to 2025-06-30 --json > northwind-fy25.jsonGrouping modes: employee (default), project, client, none (flat table with pagination).
Generate an SSW-format daily scrum email from your timesheets, CRM bookings and GitHub activity — no more hand-assembling yesterday's merged PRs every morning.
tp scrum # Styled terminal view of today's scrum
tp scrum --json # Structured output for agents / scripts
tp scrum --html # HTML body (for emailing / piping)
tp scrum -i # Interactive: r/m/p to copy rich/markdown/plain, q to quit
tp scrum --copy --format rich # Render & copy as RTF (Outlook / Apple Mail / Gmail)
tp scrum --copy --format markdown # Copy with [#1234](url) link syntax
tp scrum --copy --format plain # Copy with URLs spelled out inline
tp scrum --date 2026-04-09 # Generate for a specific date
tp scrum --project 1I776Q # Filter to one project
tp scrum --internal # Force internal daily scrum format
tp scrum --external # Force client-facing format
tp scrum --set-trello-url URL # Save a Trello URL for the internal block (one-off)How it works:
- Today = open PRs authored by you in the project's issues repo.
- Yesterday = the last working day you logged the same project, not literal calendar yesterday. If your last day on this project had no merged PRs, it bleeds back up to 7 days to surface PRs that shipped between visits.
- Internal vs external — classified from today's CRM bookings: any non-SSW client booking or timesheet → external format; all-SSW → internal format with the extra block (days until next client booking, Trello URL, "joined scrum meeting" checkbox). Override with
--internal/--external. - Issues repo — resolved via the
--issues-repofield on your repo mapping (falls back to the--remoteURL for plaingithub.com/org/repopatterns). - Timesheet notes — intentionally kept out of the rendered bullets (too noisy), but exposed in
--jsonunderyesterdayNotes/todayNotesso AI agents can use them as enrichment context via the companion/daily-scrumClaude skill.
Clipboard notes:
- Rich text on macOS uses
textutil→pbcopy -Prefer rtfwith an explicit UTF-8 declaration so emoji and em-dashes survive pasting into Outlook, Apple Mail and Gmail. - On Linux/Windows, rich text degrades to plain text automatically.
- OSC 8 hyperlinks are embedded in the styled terminal output — in modern terminals (iTerm2, Ghostty, WezTerm, Windows Terminal, VS Code) the
#1234references are clickable.
Example output (client-facing, on the day after shipping a few PRs):
Hi team,
Yesterday I worked on:
- ✅ Done – PBI – Product search: add category facets #142
- ✅ Done – PBI – Validate inventory stock-level env vars #138
- ✅ Done – PBI – Guard checkout API against missing shipping address #135
Today I'm working on:
- PBI – Order history: paginate and expose CSV export #147
<This email was sent as per https://my.sugarlearning.com/SSW/items/8291>
See what your colleagues have been writing:
tp blog list # Latest 10 posts
tp blog list --mine # Your own posts only
tp blog list --limit 5 --all # Include former employeesThe MCP server exposes TimePro data to AI agents via stdio transport. Current tool groups include:
| Group | Examples |
|---|---|
| Timesheets | Get, create, update, delete, suggested timesheets, accept suggestions, list iterations, check_week (leave-aware weekly coverage) |
| Lookup | Search clients, list projects, get client rate, CRM bookings, location and repo mapping |
| Leave | List EasyLeave entries (optionally filtered by empId), get_leave_balance (days since last leave + 12-month hours) |
| Accounting | Invoices, receipts, credit notes, products/SKUs, client rates, unbilled time, recurring invoices |
| Reporting | Timesheet queries, current user, categories, billable types, locations, project summaries, prepaid drawdown status |
Tenant resolution: the MCP server uses the active tenant (tp tenant set). Pass --tenant NAME in args to pin a session to a specific tenant config without changing the global active tenant. If no active tenant is set and exactly one tenant config exists, the server defaults to that single tenant — so a single-tenant install works out of the box.
Add to ~/.claude/settings.json:
{
"mcpServers": {
"timepro": {
"command": "tp",
"args": ["mcp"]
}
}
}Then ask Claude things like:
- "What are my timesheets for this week?"
- "Create a timesheet for today — I worked on the Northwind Traders app"
- "Accept the suggested timesheet for Monday"
- "What's my billing rate for Northwind?"
Add to .vscode/settings.json or user settings:
{
"mcp": {
"servers": {
"timepro": {
"command": "tp",
"args": ["mcp"]
}
}
}
}Add to your Codex MCP config:
{
"mcpServers": {
"timepro": {
"command": "tp",
"args": ["mcp"]
}
}
}Editable source: docs/architecture-diagram.excalidraw
- Business - Purpose, problem, goals, and statement of intent
- Instructions - Compile - Build, run, and F5 experience
- Instructions - Deployment - Package and install the CLI
- Technologies and Architecture - Technical stack, architecture overview, design patterns, and API boundary
- Testing Strategy - Unit, integration, and E2E test approach
- Alternative Solutions Considered - CLI vs MCP vs website
- Accounting Commands - Read-only accounting command reference
timepro.tools/
├── Directory.Build.props # Shared .NET project defaults
├── Directory.Packages.props # Central NuGet package versions
├── src/SSW.TimePro.Cli/
│ ├── Program.cs # Entry point, DI, command tree
│ ├── Infrastructure/
│ │ ├── ApiClient/ # HTTP client, auth headers
│ │ ├── Config/ # Global + tenant config, repo mapping
│ │ ├── Output/ # JSON + human output helpers
│ │ └── DependencyInjection/ # Spectre.Console DI bridge
│ ├── Features/
│ │ ├── Auth/ # login, logout
│ │ ├── Tenants/ # set, info, list
│ │ ├── Timesheets/ # get, create, update, delete, suggest, accept, export, check, copy
│ │ ├── Bookings/ # list
│ │ ├── Leave/ # list, create, cancel
│ │ ├── Clients/ # search
│ │ ├── Projects/ # list
│ │ ├── Iterations/ # list
│ │ ├── Rates/ # get
│ │ ├── Location/ # info, set
│ │ ├── RepoMap/ # set, list, remove, detect
│ │ ├── Summary/ # project hours breakdown
│ │ ├── Report/ # monthly report
│ │ ├── Scrum/ # daily scrum generator (tp scrum)
│ │ ├── Blogs/ # latest employee blog posts
│ │ ├── Skills/ # create
│ │ ├── Users/ # me
│ │ └── Mcp/ # MCP server + tool groups
│ └── Shared/Models/ # API DTOs
├── tests/
│ ├── SSW.TimePro.Cli.Tests/ # Unit tests (xUnit + FluentAssertions)
│ └── SSW.TimePro.Cli.Integration/ # WireMock.Net integration tests
├── scripts/e2e/ # E2E shell scripts for staging
└── docs/ # Technical and command documentation
Config is stored at ~/.config/timepro-cli/:
| File | Description |
|---|---|
config.json |
Active tenant, WFH days, default location |
tenants/{id}.json |
Per-tenant credentials (API key, employee ID, API URL) |
repo-mappings.json |
Repository-to-client/project mappings |
# Unit tests (pure logic, no network)
dotnet test tests/SSW.TimePro.Cli.Tests/
# Integration tests (WireMock.Net, no network)
dotnet test tests/SSW.TimePro.Cli.Integration/
# All tests
dotnet test
# NuGet package safety audit
scripts/security/nuget-audit.sh
# E2E (requires staging credentials)
TIMEPRO_E2E_API_KEY=... ./scripts/e2e/run-all.shMIT