Skip to content

michaeljauk/timr-client

Repository files navigation

timr-client

A fully typed TypeScript SDK, CLI, and MCP server for the timr time-tracking API. Unofficial, community-maintained.

"Buy Me A Coffee"

CI CodeQL timr-sdk timr-sdk downloads timr-cli timr-cli downloads timr-mcp timr-mcp downloads License: MIT


Why

The timr API ships a clean OpenAPI spec but no first-party TypeScript client. This repo fills that gap:

  • timr-sdk - fully typed client generated from the spec. One runtime dependency (openapi-fetch). Tree-shakeable, ESM, works in Node and edge runtimes.
  • timr-cli - scriptable wrapper for common workflows. Prints JSON so you can pipe through jq.
  • timr-mcp - Model Context Protocol (stdio) server for AI agents. Uses Cloudflare-style Code Mode — agents write JavaScript against the spec instead of calling one tool per endpoint.

Typical use cases:

  • Reconcile tracked hours against an issue tracker (Jira, Linear, GitHub Issues)
  • Export month-end project times for invoicing or controlling
  • Audit which team members haven't booked time in a given period
  • Feed a dashboard or BI tool with live data

Packages

Package Version Description
timr-sdk npm Typed SDK with built-in OAuth (client_credentials) or static bearer token
timr-cli npm Command-line interface built on the SDK
timr-mcp npm MCP (stdio) server using Cloudflare-style Code Mode — two tools (search, execute) instead of 80+ per-endpoint tools

All packages track timr API version 0.2.14 (pinned in openapi.json).

API reference

The timr API is documented by troii on SwaggerHub. Use it as the authoritative reference for endpoints, field semantics, and error codes:

See docs/spec.md for how the spec is sourced, pinned, and bumped in this repo.


SDK quickstart

pnpm add timr-sdk
import { createTimrClient } from "timr-sdk";

// OAuth2 client_credentials (recommended)
const timr = createTimrClient({
  clientId: process.env.TIMR_CLIENT_ID!,
  clientSecret: process.env.TIMR_CLIENT_SECRET!,
});

// ...or a static bearer token
// const timr = createTimrClient({ token: process.env.TIMR_TOKEN! });

const { data } = await timr.GET("/project-times", {
  params: {
    query: {
      start_from: "2026-04-01",
      start_to: "2026-04-30",
      limit: 500,
    },
  },
});

for (const pt of data?.data ?? []) {
  // every field is typed - hover in your editor to see the real shape
  console.log(pt.id, pt.duration);
}

Every request, response, and query parameter is typed - hover any field in your editor to see what the API returns.

See the SDK README for auth options, custom fetch, and error handling.

CLI quickstart

pnpm dlx timr-cli --help
# One-time setup: store OAuth client_id + client_secret
timr auth login
timr auth status

# Every resource in the spec has a matching subcommand
timr --help
timr project-times list --start-from 2026-04-01 --start-to 2026-04-30
timr tasks list --name "Website" --bookable

Every list endpoint returns { data: T[], next_page_token: string | null }. Field names for T are documented in the auto-generated packages/cli/skills/timr/SCHEMA.md - or inspect live with jq '.data[0] | keys'.

See the CLI README for commands and the SDK README for TypeScript usage.

AI agent integration

Two options, pick whichever your agent supports:

  • Claude Code skill — ships inside timr-cli and auto-installs to ~/.claude/skills/timr/ on npm install -g timr-cli. Use /timr (or just ask naturally) in any Claude Code session. Also usable with other runners that honor the same SKILL.md format.
  • MCP servertimr-mcp exposes the full API via two Code-Mode tools (search, execute) over stdio. Add with claude mcp add --scope user timr npx -- -y timr-mcp, or point any MCP client at npx -y timr-mcp. See the MCP README for auth and examples.

Reconciling against an issue tracker

The typical workflow the CLI was built for:

# 1. pull all tracked hours for a period
timr project-times list --start-from 2026-04-01 --start-to 2026-04-30 > timr.json

# 2. pull issues from your tracker (Jira, Linear, ...)
acli jira workitem search --jql "updated >= 2026-04-01" --output json > issues.json

# 3. diff them with your favourite tool
jq -s 'your rules here' timr.json issues.json

A ready-made reconciliation tool is not part of this repo on purpose - this SDK stays tracker-agnostic. Build your own, or watch for a companion repo.

Development

pnpm install
pnpm generate      # regenerate SDK types + CLI commands from openapi.json
pnpm build
pnpm test
pnpm typecheck

Requires Node 20+ and pnpm 10+. Turborepo orchestrates workspace tasks.

Testing the CLI locally

Before publishing to npm you can run the CLI straight from the workspace.

# 1. install + build the workspace
pnpm install
pnpm build

# 2. run the CLI directly (no global install needed)
node packages/cli/dist/index.js --help

For convenience, alias it for the session:

alias timr-dev="node $(pwd)/packages/cli/dist/index.js"
timr-dev --help
timr-dev auth login         # stores ~/.config/timr-cli/credentials.json
timr-dev auth status
timr-dev users list --limit 5
timr-dev project-times list --start-from 2026-04-01 --start-to 2026-04-30 | jq '.data | length'

Or symlink it globally:

cd packages/cli && pnpm link --global     # exposes `timr` on your PATH
timr --help
pnpm unlink --global timr-cli             # undo when you're done

Hot-reload during development (rebuilds on every file change):

pnpm --filter timr-cli dev
# in another shell
node packages/cli/dist/index.js --help

Regenerate CLI commands after editing openapi.json:

pnpm --filter timr-cli generate && pnpm build

Authentication for local runs

The CLI auth flow is identical to the published version:

  1. Create an OAuth client in timr (Settings > API Access, grant client_credentials, scope timrclient openid).
  2. timr-dev auth login and paste the client_id + client_secret. The CLI does a token exchange before saving, so a wrong secret fails fast.
  3. Subsequent commands reuse the stored credentials and cache the access token in memory.

Shortcuts if you don't want to store credentials:

# one-shot static bearer
timr-dev --token "eyJ..." users list

# one-shot OAuth via env
TIMR_CLIENT_ID=... TIMR_CLIENT_SECRET=... timr-dev users list

# target staging or a self-hosted instance
timr-dev --base-url https://api.staging.timr.com/v0.2/ users list

Smoke test checklist

timr-dev auth status                                    # exits 2 until login
timr-dev auth login && timr-dev auth status             # should succeed
timr-dev --help                                         # 13 resources + auth
timr-dev users list --limit 1 | jq '.data[0] | keys'   # proves a real API call
timr-dev cars list --help                               # proves generated flags

If any of those fail, re-run pnpm build and check pnpm -r typecheck.

Updating the spec

curl -s "https://api.swaggerhub.com/apis/troii/timr/<version>" -o openapi.json
pnpm generate
pnpm build

The release is driven by the commit messages — feat(sdk): bump spec to 0.2.15 is enough; Release Please picks it up on the next merge to main.

Project layout

timr-client/
├── openapi.json              # pinned timr spec
├── packages/
│   ├── sdk/                  # timr-sdk
│   │   ├── src/
│   │   │   ├── client.ts     # openapi-fetch wrapper + auth middleware
│   │   │   ├── oauth.ts      # client_credentials token provider
│   │   │   ├── generated.ts  # types (generated, do not edit)
│   │   │   └── index.ts      # public surface
│   │   └── test/
│   ├── cli/                  # timr-cli
│   │   ├── scripts/
│   │   │   └── generate-commands.mjs   # emits commands/generated/* from spec
│   │   └── src/
│   │       ├── index.ts      # citty entry
│   │       ├── lib/credentials.ts      # ~/.config/timr-cli/credentials.json
│   │       └── commands/
│   │           ├── _shared.ts          # globalArgs, resolveClient, helpers
│   │           ├── auth.ts             # login / logout / status
│   │           └── generated/          # one file per resource (do not edit)
│   └── mcp/                  # timr-mcp
│       ├── scripts/
│       │   └── copy-spec.mjs           # bundles openapi.json for runtime search
│       └── src/
│           ├── index.ts      # stdio MCP server (openApiMcpServer)
│           ├── executor.ts   # AsyncFunction-based Code Mode executor
│           └── lib/request.ts          # bridges codemode.request → timr-sdk
└── release-please-config.json          # release automation

Contributing

Issues and pull requests are welcome. A few ground rules:

  • Conventional Commits drive releases - feat: bumps minor, fix: patches, feat!: majors (via Release Please). Enforced locally by a commit-msg husky hook running commitlint.
  • Generated code stays generated - don't hand-edit packages/sdk/src/generated.ts.
  • Spec drift should be a PR on its own - bump openapi.json, regenerate, and ship that alone so downstream consumers see the diff clearly.
  • Lowercase commit subjects.

Disclaimer

Unofficial and not affiliated with troii Software GmbH, the maintainers of timr. "timr" is their trademark.

Support

Built by Michael Jauk. If this saves you time, consider buying me a coffee.

License

MIT © Michael Jauk

About

Community TypeScript SDK, CLI, and MCP server for the timr time-tracking API

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors