Skip to content

Using Workfold

Terminal window
workfold [PATH ...]

With no path, Workfold uses .. For Git collection, a path selects its entire containing repository, not a history pathspec. Multiple paths in the same repository are deduplicated. If the selected path is not in a repository, use -m fs or pass a repository path.

The default view selects:

  • the current ISO week in the resolved local timezone;
  • the standard evidence profile;
  • Git commit records reachable through local branches plus a detached HEAD;
  • author timestamps from every recorded Git identity;
  • Mo-Fr 08:00-16:30 as the working schedule;
  • a 1h sparse cluster window.

-m/--mode selects git, fs, or all and defaults to git. It is independent from -t/--time: changing one never changes the other.

Git collection invokes the local git executable and never fetches or contacts a remote. Use --git-commits-from head to limit history to the currently checked-out branch or detached HEAD.

Workfold can load personal and project defaults without changing the meaning of its CLI flags. Resolution is deterministic:

built-in → global config → nearest project config → explicit CLI

Each layer overrides only the keys it contains. Arrays replace earlier arrays; they are not concatenated. Unknown keys, wrong TOML types, malformed values, and invalid final option combinations are usage errors rather than silently ignored settings.

The automatic global file is platform-specific:

| Platform | Location | | --- | --- | | Linux and other Unix | ${XDG_CONFIG_HOME:-~/.config}/workfold/workfold.toml | | macOS | ~/Library/Application Support/workfold/workfold.toml | | Windows | %APPDATA%\workfold\workfold.toml |

Starting at each selected PATH, Workfold searches upward for the nearest workfold.toml or a pyproject.toml containing [tool.workfold]. A standalone workfold.toml wins if both exist in the same directory. Multiple input paths must resolve to the same project configuration because one chart has one timezone, schedule, and rendering policy; otherwise Workfold asks for an explicit choice.

A standalone file stores keys at its root:

workfold.toml
time = "this-week"
mode = "git"
profile = "standard"
timezone = "Europe/Berlin"
hours = "Mo-Thu 08:00-16:30; Fr 08:00-14:00"
cluster-window = "1h"
marker-style = "identity"
grid = "vertical"
hide-empty-days = ["weekend"]

The same settings fit inside a Python project file:

pyproject.toml
[tool.workfold]
timezone = "Europe/Berlin"
hours = "Mo-Fr 08:00-16:30"
grid = "vertical"

Repeatable and comma-separated CLI selections use TOML arrays. time also accepts one string for its common single-selector form:

mode = "all"
time = ["2026-W30", "2026-W31"]
git-records = ["commit", "tag"]
git-commit-times = ["author", "committer"]
git-identity = ["jan@example.com", "Jan Müller"]
fs-times = ["birth", "modified"]
fs-entries = ["file"]
exclude = ["node_modules", "*.log"]
hide-days = []
hide-empty-days = ["weekend"]

Git-only and filesystem-only scope keys must agree with the effective mode; use mode = "all" when configuring both families in one default view. A higher-precedence mode selection can replace lower-layer settings from the collector it disables.

Other configurable keys are git-commits-from, include-ignored, display-hours, no-color, list-outside, limit, coverage, strict, and verbose. Boolean values use TOML true/false. Positional paths and the configuration-control flags themselves are deliberately not configurable. Use timezone = "local" for the operating-system local zone and display-hours = "auto" for an automatic chart range; these values can also reset a global setting from a nearer layer. Omit git-commits-from for the selected profile’s default.

The locked portable and full profiles keep their CLI semantics. A higher-precedence profile replaces granular scope defaults inherited from a lower layer; granular Git/filesystem scope settings from the same or a higher layer are rejected while that locked profile remains selected.

Inspect the fully validated result without collecting Git or filesystem data:

Terminal window
workfold . --show-config
workfold . --grid both --show-config # includes this CLI override and its origin

--show-config prints the loaded files followed by every effective value and whether it came from a built-in, global file, local file, exact config file, or the CLI. --help remains stable and describes built-in defaults; it does not pretend configured values are universal defaults.

Use one exact file and disable automatic global/project discovery with --config FILE. An explicitly named pyproject.toml still reads [tool.workfold]; any other filename uses the standalone root schema. Use --no-config for built-ins plus CLI only:

Terminal window
workfold . --config ~/workfold/client.toml
workfold . --no-config

-t/--time defaults to this-week and accepts one selector kind at a time:

Terminal window
# One ISO week, or a union of repeated weeks
workfold . -t 2026-W31
workfold . -t 2026-W30 -t 2026-W31
# Inclusive local calendar dates; either endpoint may be open
workfold . -t 2026-07-01..2026-07-31
workfold . -t 2026-01-01..
workfold . -t ..2026-07-31
# A rolling elapsed window ending when this run starts
workfold . -t 2w
workfold . -t 2w3d
workfold . -t 6h30m
# Every available date for the otherwise selected mode/evidence scope
workfold . -t all

this-week, ranges, and all occur once. Repetition is reserved for ISO weeks; mixing selector kinds is invalid. All selected dates still fold onto one Monday-to-Sunday chart. Internally, Workfold converts inclusive calendar choices to half-open instant ranges so a boundary event is not counted twice.

Rolling selectors are also single-use and form the half-open instant range [captured-now - duration, captured-now). Workfold captures now once for the run. Components use ordered, nonrepeating w, d, h, and m units; w is seven elapsed 24-hour days, d is 24 elapsed hours, and m means minutes. This fixed-duration behavior remains exact across daylight-saving changes. Calendar months are intentionally not accepted because their length and boundary semantics differ from elapsed durations.

Events are converted into the selected IANA timezone before weekday, clock time, and schedule classification are derived. This preserves daylight-saving rules.

Terminal window
workfold . --timezone Europe/Berlin
workfold . --hours 'Mo-Fr 08:00-16:30'
workfold . --hours 'Mo-Thu 08:00-16:30; Fr 08:00-14:00'
workfold . --hours 'Mo-Fr 08:00-12:00,13:00-16:30'

Schedule intervals are half-open: the start is inside and the end is outside. A weekday accepts either Workfold’s two-letter token (Mo, Th) or its common three-letter English alias (Mon, Thu), case-insensitively. Output is normalized to the two-letter form. A weekday with no interval is entirely outside. Overnight intervals are not in the MVP; express the two sides as clauses on adjacent days.

The default chart uses anchored time clustering and renders only occupied bands in implicit 00:00-toward-24:00 order; it does not print empty boundary rows. Workfold sorts visible events by localized wall-clock time, uses the earliest unassigned event as an anchor, assigns every weekday’s events in the half-open interval [anchor, anchor + cluster-window), and repeats. The anchor never moves when a later event joins the band, and an event exactly on the end starts or joins the next band. All visible weekday columns share these bands, so rows remain comparable. A row label shows its first through last visible event, not the entire assignment window. Its endpoint labels use HH:MM. Because the minimum window is one minute, distinct clusters cannot receive the same minute-only label. Exact seconds and nanoseconds remain in normalized provenance and exact outside-event rows.

--cluster-window accepts a duration from 1m up to, but not including, 24 hours. Components use h, m, and s in that order without repeating a unit. Whitespace is optional between complete components, but a spaced value must be quoted as one CLI argument. The default is 1h; smaller values such as 1m30s and 10m are custom views:

Terminal window
workfold . --cluster-window 1m30s
workfold . --cluster-window 10m
workfold . --cluster-window 1h
workfold . --cluster-window 1h5m
workfold . --cluster-window '1h 5m'

Internal grid lines are optional:

Terminal window
workfold . --grid none # default
workfold . --grid vertical
workfold . --grid horizontal
workfold . --grid both

Vertical lines separate the time band from each visible weekday column. Horizontal lines appear below the header and between logical time clusters; they never split a wrapped continuation row. When clusters have a compressed gap, its duration is embedded into their shared boundary instead of becoming a separate row. Grid styles use dim internal lines without an outer box.

Weekday columns are independently controllable without filtering the event scope:

Terminal window
workfold . -E all
workfold . -H weekend
workfold . --hide-empty-days weekend
workfold . --hide-empty-days weekdays
workfold . --hide-empty-days mon,wed,sunday
workfold . --hide-days weekend --hide-empty-days weekdays

-H is short for --hide-days; -E is short for --hide-empty-days. Both options are repeatable and accept comma-separated, case-insensitive two-letter, three-letter, or full weekday names plus the weekdays and weekend groups. --hide-empty-days also accepts all. Empty means no marker remains in that column after selection and --display-hours cropping. An occupied day is therefore always preserved by --hide-empty-days.

--hide-days is unconditional. Its events disappear from the matrix but remain in Events, Schedule, Calendar, coverage, and --list-outside; a compact Hidden line accounts for occupied columns. Empty-column hiding needs no such notice because it omits no visible event. Column projection happens before clustering, so hidden weekdays cannot anchor or stretch a visible time band.

Events are ordered by local wall time, actual instant, canonical source (Git before filesystem for simultaneous ties), and stable marker ID; weekdays only group those events into columns. Each ordinary event is a symbol: Git uses circles, filesystem uses squares, inside-hours markers are filled, and outside-hours markers are hollow. Color adds green for inside-hours Git, blue for inside-hours filesystem, and red for outside-hours events. If a cell is too busy for individual symbols at the available width, exact tokens such as ●×12 ■×4 ○×2 replace only that cell; they are counts, not density estimates. Empty bands are always omitted, and a gap of at least one hour between consecutive observed cluster endpoints gets one dim duration cue.

Use --marker-style identity when contributor recognition matters more than a single Git source symbol:

Terminal window
workfold . -t 2w --marker-style identity

Each visible Git marker then uses a deterministic code derived from the exact recorded name/email pair. The key maps every visible code back to that full, unverified Git identity exactly once, using its canonical uppercase code in green. It never adds a lowercase entry for each identity; the single red a–z outside-hours cue explains every lowercase occurrence compactly. An identity uses the first ASCII letter in its normalized name, then its email as a fallback, and I if neither contains one. A unique initial remains one letter; every member of a shared initial group is numbered from 1, such as J1, J2, and J3. This remains unbounded and adjacent markers are self-delimiting without spaces: J1J2j3 is two inside-hours identities followed by the outside-hours form of J3. When one marker coalesces identical author and committer instants belonging to different identities, it receives (then ◆2, ◆3, if several such groups are visible) rather than arbitrarily choosing one person. Filesystem markers remain squares. In every color mode, uppercase/lowercase identity codes mean inside/outside; composite codes use / for the same distinction.

Terminal output starts directly with the matrix; there is no title or context subtitle above it. A content-aware symbol key follows without a heading. It names only categories actually visible after display cropping and explains ×N only when exact-count tokens occur. One conditional outside-hours cue uses the hollow shapes or lowercase classes actually present; the entire cue is red when color is available. It never repeats one contributor’s marker as a global example. The configured schedule follows on an independent left-aligned Working hours: line. Three statistic rows then appear without a Summary heading: Events is the common total, Schedule is its inside/outside split, and Calendar is its independent weekday/weekend split. Both split rows show counts and percentages of Events; a weekend event can also be outside the schedule. Use --verbose for scope, period, the complete successful coverage wording, source/record breakdowns, cluster/compression policy, exact collector selection, Git identity scope, collection extents, ignore/exclusion policy, and the detailed coverage ledger. Use --coverage for the ledger without those verbose details. Partial collection, explicit narrowing, unsupported capabilities, and nonzero cropping remain visible by default as exception notices.

--display-hours crops chart rows only. It does not change classification:

Terminal window
workfold . --hours 'Mo-Fr 08:00-16:30' --display-hours '06:00-22:00'

A compact notice after the summary reports nonzero event counts hidden before or at/after an explicit half-open crop; zero-valued crop notices are omitted. Cropping happens before clustering and the requested bounds are not rounded, so a marker at the start is visible and one exactly at the end is hidden.

The Git source has independent record, granularity, timestamp-role, reachability, and identity choices:

Terminal window
workfold . -m git --git-records commit
workfold . -m git --git-records file-change
workfold . -m git --git-records commit,file-change
workfold . --git-commit-times author
workfold . --git-commit-times committer
workfold . --git-commit-times author,committer
workfold . --git-records tag
workfold . --git-records reflog
workfold . --git-records commit,tag,reflog
workfold . --git-commits-from head
workfold . --git-commits-from local-branches
workfold . --git-commits-from all-refs
workfold . --git-identity 'Ada' --git-identity 'ada@example.com'

Commit reachability has three explicit scopes:

  • head follows the ancestry of the currently checked-out branch or detached HEAD;
  • local-branches follows every refs/heads/* branch and also includes a detached HEAD; this is the standard default;
  • all-refs follows every locally stored ref, including remote-tracking refs, tags, stash, and custom refs; it never fetches and is the portable/full default.

A commit is captured once by object ID even when several selected refs reach it. Date selection happens after reachability discovery. Consequently, a reachable commit can contribute an author event, a committer event, both, or neither in the selected period. Commits known only to an unfetched remote or no longer reachable from the selected refs are outside the commit scope. A full profile can still expose a local reflog timestamp referring to an otherwise unreachable commit, but that reflog observation is not converted into a commit observation.

--git-identity is an inclusive filter over the identity attached to each Git timestamp: an author observation uses its author, a committer observation its committer, an annotated-tag observation its tagger, and a reflog observation its reflog actor. File-change observations inherit the corresponding author or committer identity from their commit. Matching is a case-insensitive literal substring of the recorded name or email. Repeated values use OR semantics; with no flag, all recorded Git identities are included. Git identities are self-declared metadata, not verified people or accounts. In combined mode the filter does not affect filesystem observations.

Filtering happens before plotting. Therefore, when identical author and committer instants could share one marker, only the matching role remains if their identities differ; both roles remain when both match. Lightweight tags have no independent tagger timestamp or tagger identity and therefore produce no tag observation, as reported by coverage.

Annotated tags carry tagger timestamps; lightweight tags have no independent tagger time. Reflogs are local and may be disabled or expired. Git stores tree snapshots, not human file-change actions: Workfold derives each change from the commit-versus-first-parent tree difference (or root-versus-empty tree), uses Git’s reported change kind, and preserves renames when Git reports one.

Filesystem mode scans current metadata rather than reconstructing editing history:

Terminal window
workfold . -m fs --fs-times birth,modified
workfold . -m fs --fs-times metadata-changed,accessed
workfold . -m fs --fs-entries file,directory
workfold . -m fs -p full
workfold . -m all

Timestamp labels are deliberately precise:

  • birth uses a real birth/creation time only where available; on Linux, Workfold requests STATX_BTIME through statx;
  • modified is mtime;
  • metadata-changed is POSIX metadata-change ctime, never creation time;
  • accessed is atime and is labeled potentially unreliable.

Regular files are scanned by default. --fs-entries accepts a comma-separated combination of file, directory, and symlink. Symlink targets are not followed, including by Linux birth-time reads, and raw .git/ internals are always excluded. A filesystem that does not return birth time is reported as unavailable rather than receiving a fabricated ctime value. In a Git worktree, standard Git ignore semantics are respected by default:

Terminal window
workfold . -m fs --respect-gitignore
workfold . -m fs --include-ignored
workfold . -m fs --exclude node_modules --exclude '*.log'

Tracked files remain included even if a current ignore pattern matches them. Explicit exclusions are applied after discovery and always win. When directory entries are requested, the default ignore-respecting scan reports each ignored subtree it prunes and states that descendant directories were not enumerated. Use --include-ignored or the full profile when those entries and their timestamps should be included.

Terminal window
workfold . -p standard
workfold . -t all -m git -p portable
workfold . -t all -m all -p full
workfold . -t 2026-W31 -m fs -p full

-p/--profile defaults to standard, which uses the ordinary granular evidence selectors and their defaults. Each profile answers a different question about the available evidence.

Standard: What does the ordinary activity pattern look like?

Section titled “Standard: What does the ordinary activity pattern look like?”

The standard profile is the customizable everyday view:

  • Git mode uses commit records and their author dates from local branches plus a detached HEAD.
  • Filesystem mode uses birth and modification dates for regular files while respecting Git ignore rules.
  • Combined mode applies both sets of defaults.

Granular Git and filesystem options can replace these defaults. Standard keeps the initial chart focused, but it is not a completeness claim.

Portable: What dated evidence is stored inside Git objects?

Section titled “Portable: What dated evidence is stored inside Git objects?”

A commit stores two independently meaningful clocks:

  • the author date, describing when the change was originally authored;
  • the committer date, describing when that particular commit object was created or rewritten.

An annotated tag is a separate Git object with a tagger date, describing when that tag object was created. For example:

Monday 10:00 Alice originally authors a commit → author date
Thursday 15:00 Bob rebases or cherry-picks it → committer date
Friday 09:00 Bob creates annotated tag v1.0 → tagger date

Standard shows the Monday author event. Portable retains all three object-backed observations. If one commit’s author and committer timestamps are identical, Workfold may plot one marker carrying both roles; provenance and coverage still retain both timestamp roles.

Portable is a locked, Git-only profile. It reads commit author and committer dates from all locally stored refs plus annotated-tag tagger dates. Lightweight tags have no independent tag object or tagger date and remain explicitly unavailable. Derived per-file changes are excluded because Git does not store a separate timestamp for each changed file.

These timestamps travel when their Git objects are transferred, but they do not say when an object was pushed or published. Portable therefore excludes local reflogs and filesystem metadata, and Workfold never invents push, branch-creation, or lightweight-tag creation times.

Full: What dated evidence can this local machine still discover?

Section titled “Full: What dated evidence can this local machine still discover?”

Full selects every supported evidence kind available to the selected mode. In Git mode it enables commit, file-change, tag, and reflog records, both commit times, and all locally stored refs. In filesystem mode it enables every supported timestamp and entry kind and includes ignored entries.

Full includes machine-local and mutable evidence, so it is broader than portable rather than more transferable. It changes collection scope only and does not enable the detailed coverage ledger automatically.

Profiles never change the selected time or mode: full does not imply -t all or -m all. Portable and full reject granular selectors that would redefine them. --git-identity and, when filesystem mode is enabled, repeatable --exclude may still narrow them. Such narrowing remains visible in coverage status and verbose output.

Terminal window
workfold . --cluster-window 1m30s
workfold . --cluster-window 10m
workfold . --cluster-window 1h
workfold . --cluster-window 1h5m
workfold . --cluster-window '1h 5m'
workfold . --marker-style identity
workfold . --grid both
workfold . --hide-empty-days all
workfold . --hide-days weekend
workfold . --no-color
NO_COLOR=1 workfold .
workfold . --list-outside --limit 50
workfold . --coverage
workfold . --strict
workfold . --verbose

With the default source markers, every output keeps circle/square for Git/filesystem and filled/hollow for inside/outside hours. In identity-marker mode, single identities use uppercase/lowercase for inside/outside, while composite identities use /. Color reinforces these forms rather than replacing them. Identity mappings themselves remain independent of schedule state: each canonical code and full recorded identity appears once in the key.

--verbose appends operational details after the compact summary: cluster and compression policy, Scope (mode plus profile), Period (range plus timezone), the full Coverage status, source/record breakdowns, exact Git/filesystem collector selection, Git identity scope, resolved repositories/roots, and filesystem ignore/exclusion policy. It does not change the matrix, compact totals, or coverage result, and it also appends the detailed accounting ledger. --coverage prints that same ledger without the verbose context/operational block. Neither output flag changes collection scope, and the full profile enables neither one implicitly.

The outside list is chronological and bounded. --strict returns a non-zero status for partial traversal, parsing, or permission failures instead of accepting a useful-but-partial report. Unsupported platform capabilities remain visible and are never replaced with invented timestamps.