Using Workfold
Default view
Section titled “Default view”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
standardevidence profile; - Git commit records reachable through local branches plus a detached
HEAD; - author timestamps from every recorded Git identity;
Mo-Fr 08:00-16:30as the working schedule;- a
1hsparse 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.
Configuration files
Section titled “Configuration files”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 CLIEach 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:
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:
[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:
workfold . --show-configworkfold . --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:
workfold . --config ~/workfold/client.tomlworkfold . --no-configSelect dates
Section titled “Select dates”-t/--time defaults to this-week and accepts one selector kind at a time:
# One ISO week, or a union of repeated weeksworkfold . -t 2026-W31workfold . -t 2026-W30 -t 2026-W31
# Inclusive local calendar dates; either endpoint may be openworkfold . -t 2026-07-01..2026-07-31workfold . -t 2026-01-01..workfold . -t ..2026-07-31
# A rolling elapsed window ending when this run startsworkfold . -t 2wworkfold . -t 2w3dworkfold . -t 6h30m
# Every available date for the otherwise selected mode/evidence scopeworkfold . -t allthis-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.
Configure timezone and schedule
Section titled “Configure timezone and schedule”Events are converted into the selected IANA timezone before weekday, clock time, and schedule classification are derived. This preserves daylight-saving rules.
workfold . --timezone Europe/Berlinworkfold . --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.
Read and tune the sparse chart
Section titled “Read and tune the sparse chart”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:
workfold . --cluster-window 1m30sworkfold . --cluster-window 10mworkfold . --cluster-window 1hworkfold . --cluster-window 1h5mworkfold . --cluster-window '1h 5m'Internal grid lines are optional:
workfold . --grid none # defaultworkfold . --grid verticalworkfold . --grid horizontalworkfold . --grid bothVertical 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:
workfold . -E allworkfold . -H weekendworkfold . --hide-empty-days weekendworkfold . --hide-empty-days weekdaysworkfold . --hide-empty-days mon,wed,sundayworkfold . --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:
workfold . -t 2w --marker-style identityEach 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:
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.
Inspect Git evidence
Section titled “Inspect Git evidence”The Git source has independent record, granularity, timestamp-role, reachability, and identity choices:
workfold . -m git --git-records commitworkfold . -m git --git-records file-changeworkfold . -m git --git-records commit,file-changeworkfold . --git-commit-times authorworkfold . --git-commit-times committerworkfold . --git-commit-times author,committerworkfold . --git-records tagworkfold . --git-records reflogworkfold . --git-records commit,tag,reflogworkfold . --git-commits-from headworkfold . --git-commits-from local-branchesworkfold . --git-commits-from all-refsworkfold . --git-identity 'Ada' --git-identity 'ada@example.com'Commit reachability has three explicit scopes:
headfollows the ancestry of the currently checked-out branch or detachedHEAD;local-branchesfollows everyrefs/heads/*branch and also includes a detachedHEAD; this is the standard default;all-refsfollows 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.
Inspect filesystem evidence
Section titled “Inspect filesystem evidence”Filesystem mode scans current metadata rather than reconstructing editing history:
workfold . -m fs --fs-times birth,modifiedworkfold . -m fs --fs-times metadata-changed,accessedworkfold . -m fs --fs-entries file,directoryworkfold . -m fs -p fullworkfold . -m allTimestamp labels are deliberately precise:
birthuses a real birth/creation time only where available; on Linux, Workfold requestsSTATX_BTIMEthroughstatx;modifiedis mtime;metadata-changedis POSIX metadata-change ctime, never creation time;accessedis 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:
workfold . -m fs --respect-gitignoreworkfold . -m fs --include-ignoredworkfold . -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.
Choose an evidence profile
Section titled “Choose an evidence profile”workfold . -p standardworkfold . -t all -m git -p portableworkfold . -t all -m all -p fullworkfold . -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 dateThursday 15:00 Bob rebases or cherry-picks it → committer dateFriday 09:00 Bob creates annotated tag v1.0 → tagger dateStandard 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.
Control terminal detail
Section titled “Control terminal detail”workfold . --cluster-window 1m30sworkfold . --cluster-window 10mworkfold . --cluster-window 1hworkfold . --cluster-window 1h5mworkfold . --cluster-window '1h 5m'workfold . --marker-style identityworkfold . --grid bothworkfold . --hide-empty-days allworkfold . --hide-days weekendworkfold . --no-colorNO_COLOR=1 workfold .workfold . --list-outside --limit 50workfold . --coverageworkfold . --strictworkfold . --verboseWith 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.