Harvest Worklog

Harvest Worklog is a Ruby CLI and OMP plugin for personal Harvest work logs. It creates holiday-aware time-off entries, writes reviewed ordinary-work entries, and imports reviewed OMP Project Time sessions.

Credentials

  1. Open Harvest ID Developers and create a Personal Access Token for this local tool.

  2. Harvest displays the token and the available Harvest account IDs once. Copy the account ID for the account to track time in.

  3. Fill the ignored .envrc in this repository:

    export HARVEST_ACCESS_TOKEN="your-personal-access-token"
    export HARVEST_ACCOUNT_ID="your-harvest-account-id"
    
  4. Run direnv allow if you use direnv; otherwise run source .envrc in the shell that starts OMP or the CLI.

Personal Access Tokens are the intended Harvest authentication method for personal scripts. Revoke and replace a token from Harvest ID if it is exposed. Harvest authentication documentation.

CLI

harvest-worklog time-off FROM TO --project NAME --task NAME [options]
harvest-worklog time-off FROM TO --project-id ID --task-id ID [options]
harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
harvest-worklog work-entry DATE --project-id ID --task-id ID --hours HOURS --notes NOTES [options]
harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]
harvest-worklog timesheet DATE --project NAME [--task NAME]
harvest-worklog reconcile DATE --project PROJECT --harvest-project NAME [--task NAME]

Time off

The time-off command creates one 7-hour entry for each Yukon business day in an inclusive range. It skips weekends and observed Yukon holidays by default.

harvest-worklog time-off 2026-08-17 2026-08-28 \
  --project 'Time Off - Marlen' \
  --task 'Vacation / PTO' \
  --notes 'regular time off' \
  --dry-run

The default holiday region is ca_yt. Add --holiday-region REGION or set HARVEST_HOLIDAY_REGIONS to include other Holidays regions. business_time calculates business days and holidays supplies observed statutory holidays.

A time-off block never creates weekend entries. When names are supplied, it resolves them from your active personal Harvest assignments, so a Project Manager role is not required.

The harvest_record_time_off OMP tool accepts the same name pair or ID pair. Its optional holidayRegions array adds per-call regions to the configured defaults.

Reviewed ordinary work

The work-entry command checks for existing or locked entries for its project, task, and date before creating a new duration entry. Use --dry-run to complete that preflight without a write. OMP Project Time uses the same path.

Read-only aggregates

The aggregate command reads all matching Harvest time-entry pages and prints totals grouped by spent date and project/task. It lists every date in the inclusive range, including weekend or empty dates, and never writes a Harvest record.

harvest-worklog aggregate 2026-07-17 2026-07-19 \
  --project WRAP \
  --task Programming

The harvest_time_aggregates OMP tool exposes the same optional filters and is approval-gated as a read.

Timesheets

The CLI timesheet command and harvest_time_sheet OMP tool read the authenticated user's compact daily Harvest view. They show task totals, entry durations, and notes already recorded in Harvest.

harvest-worklog timesheet today --project WRAP

The /harvest-worklog timesheet today --project wrap slash command reads the requested local OMP Project Time day and fetches available Harvest project/task categories through the read-only mapping-data path. It asks the configured model to classify local activities into those exact categories and concise feature/workstream labels, then renders a Harvest draft (review only; nothing written) grouped by project and task with at most four high-level worklog/workstream bullets per aggregate, exact local duration totals, and destination/overall totals. If Harvest lookup or classification is unavailable, configured mappings are used as a fallback; generated worklog bullets are used when available, otherwise raw activity labels are bounded to four plus an other local activities remainder. The draft uses local Project Time durations and never claims to reproduce manually entered Harvest totals or ticket IDs. When a Project Time log has no narrative text, generated bullets summarize its activity labels and do not reconstruct missing Harvest notes; logs that provide narrative.text can supply richer source material. /project-time history reports all logged dates, so its totals can be larger than the selected day.

Type /harvest-worklog in OMP to discover timesheet; date aliases appear only after selecting timesheet . After --project, Tab lists local human-active Project Time project names; a prefix filters them case-insensitively. Completion preserves the case-sensitive name required by the command.

OMP settings

  • defaultHours: hours per business day when a time-off tool call omits hours; defaults to 7.
  • holidayRegions: comma-separated Holidays regions; defaults to ca_yt.
  • command: direct path to the harvest-worklog executable.
  • projectTimeMappings: JSON mapping from OMP Project Time project names to Harvest project/task names.
  • projectTimeLogPath: optional override for ~/.omp/project-time/time-log.json.

OMP Project Time integration

Configure projectTimeMappings with the recorded OMP Project Time project name as the key:

{
  "Harvest API": { "project": "Internal", "task": "Development" }
}

harvest_preview_project_time_entries reads only human_active sessions from the configured time log, splits them across local dates, reports that source policy, generates descriptions from the project and repository, and checks Harvest for existing or locked entries without writing. harvest_record_project_time_entries performs the same preflight then creates only new entries; OMP treats it as a write requiring approval. Unmapped sessions are reported and never written.

Activity transforms

harvest_preview_project_time_transforms emits deterministic JSON for local raw intervals. It accepts an inclusive date range and optional exact repositoryId, project, and sourceKind filters; each matching interval is split by local date and grouped by activity, with missing labels reported as unlabelled. It defaults to human_active and returns the effective top-level sourceKind; selecting agent_turn_elapsed requires an explicit source-kind request. Set applyMappings to include configured Harvest project/task mappings. The output reports groups, proposed mapped entries, and any unmapped or excluded rows.

harvest_record_project_time_transforms is a separate approval-gated write step. It always applies configured mappings and records the reviewed activity entries through the existing locked and duplicate preflight. Different activity labels may share a date/project/task; rerunning the same activity label is skipped, as are locked or unrelated existing entries.

Daily reconciliation

reconcile compares one local Project Time project against the manual Harvest total for one date. It reads only local human_active intervals, reports the raw duration, non-overlapping union, concurrent overlap, and both Harvest-minus-local deltas. It never creates or changes Harvest entries: manual Harvest is the benchmark.

harvest-worklog reconcile 2026-07-21 \
  --project wrap \
  --harvest-project WRAP \
  --task Programming

Empty local or Harvest data is reported as zero, so this command is suitable for daily capture checks while manual tracking remains authoritative. #51 separately ensures ordinary automatic exports use only the same human_active evidence.

The standard work-entry importer allows only one entry for a date, project, and task. If a manual WRAP / Programming entry exists for a date, importing ordinary OMP Project Time for that same mapping skips it rather than adding hours. Activity transforms are the exception: distinct activity labels can create separate entries while the same label, locked entries, and unrelated manual entries are still skipped. Reconcile ordinary entries as either OMP or manual hours, not their sum.

Migration from harvest-time-off

Version 0.5.0 is a clean identity cutover. Uninstall the prior harvest-time-off gem and OMP plugin, then install harvest-worklog; replace former top-level CLI commands with the harvest-worklog time-off and harvest-worklog work-entry subcommands. The old package, executables, and workEntryCommand setting are not retained.

Release

Before merging a release PR, self-review its complete diff, address every actionable review comment, and rerun focused QA after fixups. Record the review and QA evidence on the PR. Then merge to main, build and publish the Ruby gem, install the released OMP plugin from GitHub, and verify it:

ruby test_harvest_worklog.rb
npm run test:omp
harvest-worklog time-off 2026-08-17 2026-08-28 \
  --project 'Time Off - Marlen' \
  --task 'Vacation / PTO' \
  --holiday-region ca_yt \
  --dry-run
VERSION="$(ruby -Ilib -rharvest_worklog/version -e 'puts HarvestWorklog::VERSION')"
gem build harvest-worklog.gemspec
gem push "harvest-worklog-${VERSION}.gem"
gem uninstall harvest-time-off --all --executables --ignore-dependencies
gem install --clear-sources --source https://rubygems.org harvest-worklog --version "$VERSION" --no-document
omp plugin uninstall harvest-time-off
npm run release
npm run verify:release
harvest-worklog --help

npm run release owns the uninstall/force-install mutation. npm run verify:release never changes the installation: it checks the exact installed version and runs a deterministic slash-command smoke test against the installed plugin. It exits after verification. Existing OMP sessions retain startup-loaded extension code; restart them before optional manual autocomplete and slash-command QA.