CLI: Tracking Time

The Keito CLI supports running timers, manual time entries, completed agent session records, time-entry listing, and active-timer checks.

Regular timer and manual commands create entries with source=cli. Agent lifecycle hooks should use keito time session-record, which creates source=agent entries by default.

Start a Timer

keito time start --project "Acme Website" --task "Development"

This creates a running time entry for today. The CLI checks for an existing running timer first, because only one timer can be active at a time.

FlagDescriptionRequired
--project <value>Project name, code, or IDYes
--task <value>Task name or IDYes
--notes <text>Initial notesNo
`—billable <truefalse>`Override billable status

JSON example:

keito time start --project "Acme Website" --task "Development" --json
{
  "status": "started",
  "entry_id": "time_entry_id_here",
  "project": "Acme Website",
  "task": "Development",
  "spent_date": "2026-05-06",
  "billable": true,
  "source": "cli",
  "started_at": "2026-05-06T09:00:00Z"
}

If another timer is already running, the command exits with code 3.

Check the Running Timer

keito time running
keito time running --json

When no timer is running, JSON output is:

{"running": false}

When a timer is running, JSON output is an array:

[
  {
    "running": true,
    "entry_id": "time_entry_id_here",
    "project": "Acme Website",
    "task": "Development",
    "started_at": "2026-05-06T09:00:00Z",
    "spent_date": "2026-05-06",
    "billable": true,
    "source": "cli",
    "elapsed_hours": 1.5,
    "elapsed": "1:30"
  }
]

Agents and scripts should call keito time running --json before starting a timer.

Stop a Timer

keito time stop --notes "Completed code review"

This stops the running timer and saves elapsed time. The production API calculates the duration server-side. If --notes is supplied, it replaces the entry notes. Without --notes, existing notes are preserved.

FlagDescription
--notes <text>Replace notes on the entry
--discardDelete the running timer instead of saving it

JSON example:

{
  "status": "stopped",
  "entry_id": "time_entry_id_here",
  "project": "Acme Website",
  "task": "Development",
  "duration_hours": 1.5,
  "duration": "1:30",
  "spent_date": "2026-05-06",
  "billable": true,
  "source": "cli",
  "started_at": "2026-05-06T09:00:00Z",
  "stopped_at": "2026-05-06T10:30:00Z"
}

Discard a Timer

keito time stop --discard --json

Discarding deletes the running time entry and records no duration. Use this when a timer was started by mistake or an automated session failed and should not be billed.

JSON example:

{
  "status": "discarded",
  "entry_id": "time_entry_id_here",
  "project": "Acme Website",
  "task": "Development"
}

Log Completed Time

keito time log --project "Acme Website" --task "Development" \
  --duration 1:30 \
  --date 2026-05-06 \
  --notes "Implemented OAuth flow"
FlagDescriptionRequired
--project <value>Project name, code, or IDYes
--task <value>Task name or IDYes
--duration <value>Decimal hours or HH:MMYes
--date <YYYY-MM-DD>Date of work; defaults to todayNo
--notes <text>Description of workNo
`—billable <truefalse>`Override billable status

Duration formats:

InputMeaning
1.51 hour 30 minutes
1:301 hour 30 minutes
0.2515 minutes
0:1515 minutes

Record an Agent Session

keito time session-record \
  --project "project_id_here" \
  --task "task_id_here" \
  --session-id "codex-123" \
  --duration-seconds 5400 \
  --source agent \
  --metadata '{"integration":"custom_agent","agent_type":"codex"}' \
  --json

session-record is idempotent for a stable session ID on the same date and source. If the entry already exists, the CLI updates it instead of creating a duplicate.

List Time Entries

keito time list
keito time list --from 2026-05-01 --to 2026-05-31
keito time list --project "Acme Website" --limit 10 --json
keito time list --task "Development" --page 2 --json
FlagDescription
--from <YYYY-MM-DD>Start date
--to <YYYY-MM-DD>End date
--project <value>Filter by project name, code, or ID
--task <value>Filter by task name or ID
--limit <number>Entries per page; default 50
--page <number>Page number; default 1

JSON list output is an array of time-entry objects from the API.

Common Exit Codes

CodeMeaning
0Success
3Conflict, usually a timer is already running
4Project, task, or running timer not found

For the full table, see Command Reference.