DayTrace
DayTrace turns ActivityWatch history into a compact, privacy-aware account of what a day appears to contain: coherent activity episodes, inferred projects/workstreams, broad topics, and evidence-backed achievements.
It is available in two forms from the same repository:
| Package | Install | Best for |
|---|---|---|
Python daytrace |
uv tool install daytrace@latest |
A ready-to-run ActivityWatch CLI |
npm daytrace |
npm install daytrace |
Browser/Electron and Obsidian integrations |
Both implementations preserve the same versioned schemas and core semantics. Python remains the reference implementation; synthetic parity fixtures keep the TypeScript port aligned.
From raw activity to a useful journal
DayTrace does not need project definitions to sanitize, fuse, sessionize, and compact a day. Its deterministic stages first turn overlapping ActivityWatch watchers into project-neutral episodes:
ActivityWatch → normalize → remove AFK → sanitize → fuse overlaps
→ sessions → compact episodes → deterministic output
↘ optional AI workstream summary
In AI mode, the model groups those minimized episodes into inferred daily workstreams and reports work/topics separately from apparent achievements. An achievement is shown only when the trace contains evidence of a resulting state—not merely because an application was open.
These workstreams are deliberately not canonical project definitions. An Obsidian Second Brain plugin can map the validated daily digest onto the project definitions stored in the vault. DayTrace therefore remains useful without vault access, while Obsidian remains the source of truth for project identity.
Privacy boundary
DayTrace processes locally unless AI mode is explicitly selected. Before a cloud request it:
- removes AFK time and overlapping-duration inflation;
- normalizes and sanitizes titles, URLs, paths, email addresses, and secrets;
- sends compact episodes rather than raw ActivityWatch events;
- discloses the planned calls, input size, data categories, and estimated cost;
- asks for confirmation (
Continue? [Y/n]); - validates response structure, evidence, and complete episode allocation;
- secret-scans generated text before returning it.
The durable output contains text and structured metadata only. DayTrace does not retain screenshots, audio, video, query strings, raw browser URLs, source bucket IDs, source event IDs, or a duplicate ActivityWatch database.
Python CLI
Install uv and make sure ActivityWatch is running. On Windows:
winget install --id=astral-sh.uv -e
uv tool install daytrace@latest
uv tool update-shell
Open a new PowerShell window, then create a fully local report:
daytrace activitywatch --date 2026-09-10 --output daytrace.md
For an AI-assisted workstream digest:
daytrace activitywatch `
--date 2026-09-10 `
--summary ai `
--provider openai `
--model gpt-5.6-terra `
--debug-output daytrace-ai-failure.json `
--output daytrace.md
The CLI asks for the OpenAI API key in a hidden prompt after you approve the
disclosed request. The key is held in memory only. Do not put it in command-line
arguments or paste it into logs. OpenAI requests use store=False.
For a one-off run without persistent installation:
uvx --refresh --link-mode=copy daytrace@latest activitywatch `
--date 2026-09-10 --output daytrace.md
To install into a specific directory instead:
mkdir C:\Tools\daytrace
cd C:\Tools\daytrace
uv venv
uv pip install daytrace
.\.venv\Scripts\daytrace.exe activitywatch --date 2026-09-10 --output daytrace.md
Useful CLI options:
--format jsonwrites a versioned structured handoff.--detailsadds sanitized episode allocation and evidence IDs.--rawadds the fine-grained sanitized audit trail; review it before sharing.--diagnosticsemits aggregate, content-free coverage counts only.--yesconfirms the disclosed cloud send for non-interactive workflows; key collection remains separate.--timezone America/Los_Angelesselects an explicit IANA timezone.--server http://127.0.0.1:5600overrides the default ActivityWatch server.
Interactive AI calls print content-free progress and an elapsed-seconds timer.
DayTrace allows one disclosed allocation-repair retry, then uses deterministic
fallback on failure. With --debug-output, the support artifact contains only
allow-listed structure and opaque identifiers—never prompt/response prose or
the API key. A successful output write ends with Done!.
The cost estimate uses the small, dated pricing table in the Python package. Unknown models are reported as unavailable rather than guessed.
Python callers can use the same project-neutral core directly:
from datetime import date
from daytrace.activitywatch import collect_day
from daytrace.markdown import render_episode_markdown
bundle = collect_day(date(2026, 9, 10), timezone_name="Europe/Lisbon")
markdown = render_episode_markdown(bundle)
TypeScript core
Install the browser-compatible ESM package:
npm install daytrace
The package has zero runtime dependencies and no Node built-in imports. It is designed for browser and Electron renderer environments, including an Obsidian plugin. HTTP, credentials, persistence, UI, and scheduling remain host-owned.
import {
collectDay,
renderDigestMarkdown,
renderEpisodeMarkdown,
summarizeBundleOrFallback,
type ActivityWatchTransport,
type SummaryProvider,
} from "daytrace";
const activityWatchTransport: ActivityWatchTransport = {
async request({ server, path, query, signal }) {
const url = new URL(path, `${server}/`);
for (const [key, value] of Object.entries(query ?? {})) {
url.searchParams.set(key, value);
}
const response = await fetch(url, { signal });
if (!response.ok) throw new Error("ActivityWatch request failed");
return response.json();
},
};
// The host adapter can reuse the API key already managed by your plugin.
declare const aiProvider: SummaryProvider;
declare const controller: AbortController;
declare function updateStatus(stage: string, elapsedSeconds: number): void;
const bundle = await collectDay({
day: "2026-09-10",
timezoneName: "Europe/Lisbon",
server: "http://127.0.0.1:5600",
transport: activityWatchTransport,
signal: controller.signal,
onProgress: ({ stage, elapsedSeconds }) => updateStatus(stage, elapsedSeconds),
});
const result = await summarizeBundleOrFallback(bundle, aiProvider, undefined, {
signal: controller.signal,
onProgress: ({ stage, elapsedSeconds }) => updateStatus(stage, elapsedSeconds),
});
const markdown = result.kind === "ai"
? renderDigestMarkdown(bundle, result.digest, result.provenance)
: renderEpisodeMarkdown(result.bundle);
The two injected boundaries are intentionally small:
interface ActivityWatchTransport {
request(input: {
server: string;
path: string;
query?: Readonly<Record<string, string>>;
signal?: AbortSignal;
}): Promise<unknown>;
}
interface SummaryProvider {
complete(
request: {
passKind: "chunk" | "merge";
payload: Readonly<Record<string, unknown>>;
instructions: string;
responseFormat: Readonly<Record<string, unknown>>;
},
options?: { signal?: AbortSignal },
): Promise<{
payload: unknown;
provider: string;
model: string;
inputTokens?: number;
outputTokens?: number;
responseId?: string;
requestId?: string;
}>;
}
Core public operations include:
| API | Purpose |
|---|---|
collectDay(options) |
Query through an injected transport and produce an episode bundle |
buildSummaryPlan(bundle) |
Inspect minimized requests before any AI call |
summarizeBundle(bundle, provider, plan?, options?) |
Return only a validated AI digest or throw a typed safe error |
summarizeBundleOrFallback(...) |
Return a discriminated AI/deterministic result |
renderEpisodeJson/Markdown(...) |
Render deterministic episode-bundle.v1 output |
renderDigestJson/Markdown(...) |
Render AI-assisted workstream-report.v2 output |
renderSummaryFailureJson(failure) |
Render a content-free ai-failure.v1 artifact |
DAYTRACE_VERSION |
Package/core version |
The package also exports the reusable deterministic stages (normalizeEvents,
removeAfk, sanitizeRecords, fuseObservations, sessionize, and
compactSessions), strict validators, domain types, and the inspectable summary
prompts. See the npm package guide for the
complete integration contract.
Schemas and fallback
The stable serialized contracts are:
daytrace.episode-bundle.v1daytrace.workstream-report.v2daytrace.summary-request.v2daytrace.workstream-digest.v2daytrace.workstream-merge-request.v1daytrace.workstream-merge.v1daytrace.ai-failure.v1
Every AI topic and visible outcome cites supplied episode IDs. An episode can be evidence for multiple narrative claims, but it has exactly one primary allocation across workstreams or the unassigned list. Durations always come from that deterministic primary allocation.
Development
# Python
uv sync --all-groups
uv run pytest
uv build --no-sources
# TypeScript
npm --prefix packages/daytrace-core ci
npm --prefix packages/daytrace-core test
npm --prefix packages/daytrace-core run typecheck
npm --prefix packages/daytrace-core run build
npm --prefix packages/daytrace-core run smoke:browser
The parity adapter writes only invented fixture data under
tests/fixtures/cross-language/. Never commit personal ActivityWatch exports.
Product and architecture notes
- Implementation plan
- Architecture
- Data model and search
- Privacy and security
- Product and UX specification
- Screenpipe reuse audit
- ActivityWatch integration research
- TypeScript core design
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file daytrace-0.4.0.tar.gz.
File metadata
- Download URL: daytrace-0.4.0.tar.gz
- Upload date:
- Size: 37.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ed042b4db67377b2d194211cd906880eab0c27076c6988a8672b85ada7450f8
|
|
| MD5 |
a26bcd6d3ea109ce0645e2c12e56b950
|
|
| BLAKE2b-256 |
155064103aa906d6abe24ef62694177beec42f6879061fdc7a7fc2531186e076
|
File details
Details for the file daytrace-0.4.0-py3-none-any.whl.
File metadata
- Download URL: daytrace-0.4.0-py3-none-any.whl
- Upload date:
- Size: 47.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6512feb6a9f9a0dc618dc5add4f2b419fa695a0ac57f38f3a10686d8816cd069
|
|
| MD5 |
29458a0b6459dceb13c4c2b475cf769a
|
|
| BLAKE2b-256 |
352ef08696fc84047c8493a08417296c673dbc9c6a463cad3ce999f80d5fa9e3
|