| name | productive-cli |
| description | CLI tool for Productive.io - time tracking, projects, tasks, and more. Use for AI agents and automation. |
Productive CLI
CLI tool for interacting with the Productive.io API. Optimized for AI agents with structured JSON output.
Installation
npm install -g @studiometa/productive-cli
npx @studiometa/productive-cli --help
npx @studiometa/productive-cli projects list
Authentication
Three methods (in priority order):
-
CLI arguments (highest priority):
productive projects list --token "TOKEN" --org-id "ORG_ID"
-
Environment variables:
export PRODUCTIVE_API_TOKEN="your-token"
export PRODUCTIVE_ORG_ID="your-org-id"
export PRODUCTIVE_USER_ID="your-user-id"
-
Config file (persistent):
productive config set apiToken YOUR_TOKEN
productive config set organizationId YOUR_ORG_ID
productive config set userId YOUR_USER_ID
Output Formats
--format json
--format human
--format csv
--format table
Always use --format json for parsing and automation.
Smart ID Resolution
Use human-friendly identifiers instead of numeric IDs in any command:
Auto-Resolution in Filters
productive tasks list --assignee "user@example.com"
productive time list --person "john@company.com"
productive tasks list --project "PRJ-123"
productive time list --project "P-456"
productive deals list --company "Studio Meta"
productive tasks list --company "Acme Corp"
Resolve Command
Lookup resources by human-friendly identifiers:
productive resolve "user@example.com"
productive resolve "PRJ-123"
productive resolve "user@example.com" -q
productive resolve detect "user@example.com"
productive resolve "PRJ-123" --format json
Use in Scripts
PERSON_ID=$(productive resolve "user@example.com" -q)
productive time list --person "$PERSON_ID"
productive time list --person "user@example.com"
Supported Patterns
| Pattern | Example | Resolves To |
|---|
| Email | user@example.com | Person ID |
| Project number | PRJ-123, P-123 | Project ID |
| Deal number | D-456, DEAL-456 | Deal ID |
| Name (with context) | "Studio Meta" | Company/Service ID |
Commands
Projects
productive projects list
productive p ls
productive projects get <id>
productive projects list --format json
Time Entries
productive time list
productive t ls
productive time list --date today
productive time list --date yesterday
productive time list --from 2024-01-01 --to 2024-01-31
productive time list --mine
productive time list --project <id>
productive time add \
--service <service_id> \
--time 480 \
--date 2024-01-15 \
--note "Description"
productive time update <id> --time 240
productive time update <id> --note "Updated note"
productive time delete <id>
productive time rm <id>
Tasks
productive tasks list
productive tasks list --project <id>
productive tasks list --mine
productive tasks list --status open
productive tasks get <id>
People
productive people list
productive people get <id>
Discovering Filters with help
Each resource supports a help subcommand that shows all available filters, fields, and examples:
productive tasks help
productive projects help
productive time help
productive deals help
productive bookings help
Text Search with --filter query=<text>
Many resources support a query filter for text search. Use --filter query=<text> to search by name/title:
productive projects list --filter query="website redesign"
productive tasks list --filter query="bug fix"
productive people list --filter query="john"
productive companies list --filter query="acme"
productive deals list --filter query="redesign"
Resources that support query: projects, tasks, people, companies, deals
Services (Budget Line Items)
productive services list
productive svc ls
productive services list --filter deal_id=<id>
Companies
productive companies list
productive companies get <id>
Comments
productive comments list --filter task_id=<id>
productive api '/comments?filter[task_id]=12345' --format json
productive api '/comments?filter[deal_id]=12345' --format json
productive comments get <id>
productive comments add --task 12345 --body "Internal note" --hidden
productive comments update 67890 --no-hidden
Getting Task Context (Comments, Attachments)
To get full context for a task:
productive tasks get <id> --format json
productive api '/comments?filter[task_id]=<id>' --format json
productive time list --filter task_id=<id> --format json
Common mistakes to avoid:
productive api /activities
productive api /notes
productive api /task_comments
productive api /tasks/<id>/comments
productive api '/comments?filter[task_id]=<id>'
Timers
productive timers list
productive timers start --service <id>
productive timers stop <id>
Bookings
productive bookings list
productive bookings list --filter person_id=<id>
Activities (Audit Feed)
productive activities list
productive activities list --event create
productive activities list --after 2026-02-01T00:00:00Z
productive activities list --person <id>
productive activities list --project <id>
Custom Fields
productive custom-fields list
productive custom-fields list --type Task
productive custom-fields list --type Deal --format json
productive custom-fields get <id>
productive custom-fields get <id> --include options
Reports
productive reports time --from 2024-01-01 --to 2024-01-31 --group person
productive reports budget --project <id>
Run Custom Scripts
productive run executes a JavaScript or TypeScript script with a pre-configured Productive SDK client. Credentials are loaded from the usual sources (keychain, config file, env vars, CLI flags).
Use a -- separator to split run-options from script args. Everything before -- configures run (script path, credentials, --dry-run, --list); everything after -- is forwarded to the script verbatim and parsed into args (positionals) and flags (named). Without a --, all flags are treated as run-options.
productive run ./scripts/weekly-report.ts
productive script ./scripts/export-time.ts
productive run ./scripts/audit.ts -- --from 2025-01-01 --to 2025-01-31 --mine
productive run ./scripts/audit.ts --token $TOKEN --org-id $ORG_ID -- --verbose
productive run --dry-run ./scripts/bulk-update.ts
productive run --list
productive run --list ./automation
Scripts can use two patterns:
Pattern A — helpers (recommended, full type inference):
Use defineMeta and createScript — no explicit type annotations needed, the editor infers everything:
import { defineMeta, createScript } from '@studiometa/productive-cli/script';
export const meta = defineMeta({
name: 'My Report',
description: 'Short description shown by productive run --list.',
usage: '--from <date> --to <date>',
});
export default createScript(async ({ client, output, flags }) => {
const from = flags.from as string | undefined;
const projects = await client.projects.all().toArray();
output.table(projects.map((p) => ({ id: p.id, name: p.name })));
});
Pattern A (alt) — explicit type annotations:
import type { ScriptContext, ScriptMeta } from '@studiometa/productive-cli/script';
export const meta: ScriptMeta = { name: 'My Report' };
export default async function ({ client, output }: ScriptContext) {
const projects = await client.projects.all().toArray();
output.table(projects.map((p) => ({ id: p.id, name: p.name })));
}
Pattern B — globals (quick scripts, no imports):
const tasks = await productive.tasks.all().toArray();
output.json(tasks);
Output utilities available as output.*:
| Method | Description |
|---|
output.table(data) | ASCII table |
output.json(data) | Formatted JSON |
output.csv(data) | CSV output |
output.print(text) | Plain text |
output.success(msg) | Green ✓ message |
output.error(msg) | Red ✗ message (stderr) |
output.warn(msg) | Yellow ⚠ message |
output.info(msg) | Blue info message |
output.spinner(msg) | Start a spinner → { update, stop, fail } |
output.spinner(msg, asyncFn) | Wrap an async task — spinner auto-stops; returns Promise |
TypeScript files (.ts, .mts) use Node.js built-in type stripping — no extra tools needed.
Custom API Requests
Prefer named commands (tasks list, time list, etc.) over raw api calls — they handle filtering, formatting, and pagination automatically.
For endpoints not covered by built-in commands:
productive api /companies
Filtering and Includes (recommended)
Use --filter and --include flags instead of hand-crafting query strings:
productive api /tasks --filter project_id=123 --filter status=1
productive api /comments --filter task_id=12345
productive api /tasks --filter project_id=123 --include project,assignee
productive api '/tasks?sort=-created_at' --filter project_id=123 --include project
--filter and --include only work with GET requests (the default).
Request Body (POST/PATCH/DELETE)
Use --field or --raw-field for request body parameters:
productive api /comments \
--field task_id=12345 \
--raw-field body="Comment text"
productive api /tasks/12345 \
--method PATCH \
--raw-field title="Updated title"
productive api /time_entries/12345 --method DELETE
productive api /time_entries --paginate --filter person_id=12345
Cache
productive cache status
productive cache clear
productive cache clear projects
productive projects list --no-cache
productive projects list --refresh
Common Options
| Option | Alias | Description |
|---|
--format <fmt> | -f | Output format: json, human, csv, table |
--page <num> | -p | Page number |
--size <num> | -s | Items per page (default: 100) |
--sort <field> | | Sort field (prefix - for descending) |
--mine | | Filter by current user |
--no-cache | | Bypass cache |
--refresh | | Force cache refresh |
Date Formats
The CLI accepts flexible date inputs:
- ISO format:
2024-01-15
- Keywords:
today, yesterday, tomorrow
- Ranges:
"this week", "last week", "this month", "last month"
- Relative:
"2 days ago", "1 week ago", "3 months ago"
Time Values
Time is always in MINUTES:
- 60 = 1 hour
- 480 = 8 hours (full day)
- 240 = 4 hours (half day)
- 30 = 30 minutes
JSON Response Structure
{
"data": [...],
"meta": {
"page": 1,
"per_page": 100,
"total": 358
}
}
Error Response
{
"error": "ProductiveApiError",
"message": "API request failed: 401 Unauthorized",
"statusCode": 401
}
Exit codes: 0 = success, 1 = error
Best Practices for AI Agents
Data Integrity
-
Never modify text content - Use exact titles, descriptions, and notes from the API. Do not rephrase, summarize, or "improve" user content.
-
Never invent IDs - Always fetch resources first to get valid IDs. Don't guess or make up project_id, service_id, person_id, etc.
-
Preserve formatting - Task descriptions and comments may contain HTML or Markdown. Preserve it as-is.
Confirmation Before Mutations
Always ask for user confirmation before:
- Creating time entries
- Updating tasks or time entries
- Deleting any resource
- Posting comments
Example confirmation prompt:
I'll create the following time entry:
- Service: #12345 "Development"
- Duration: 2h (120 minutes)
- Date: 2024-01-15
- Note: "Feature implementation"
Confirm? (yes/no)
Fetching Strategy
- Fetch before referencing - Get the list of services/projects before creating time entries
- Use filters - Don't fetch all data, filter by project_id, date range, etc.
- Paginate large results - Use --page and --size for large datasets
- Cache awareness - Use --refresh when you need fresh data
Error Handling
- Check exit code (
$?) after commands
- Parse error JSON for details when using
--format json
- Handle 401 (auth), 404 (not found), 422 (validation) appropriately
Common Workflows
Log time for today:
productive services list --filter project_id=12345 --format json
productive time add --service <id> --time 480 --note "Work done"
Review time entries:
productive time list --date "this week" --mine --format json
Find a task:
productive tasks list --project <id> --status open --format json