ufo-tdkit-report
Writes the commit message and the release notes for your font project — through Claude, GPT, Gemini, Grok, Mistral, Groq, DeepSeek, Qwen, Kimi, GLM, or a local model.
What makes that trustworthy is what sits underneath. The prose is not written from a
git diff; it is written from a semantic diff of the sources — outlines compared
coordinate by coordinate, OpenType features at the rule level, kerning and fontinfo
normalized so an editor's re-serialization diffs to nothing. The model may only restate
those facts, a deterministic check afterwards says so if it strayed, and the facts
themselves are attached verbatim to every narration so you can verify it in seconds.
tdreport # a drafted commit message for the working tree
tdreport --notes v2.005..HEAD # release notes for a range
tdreport --no-ai # …or just the facts, no model call
AI narration is the default, and it degrades rather than fails: with no provider configured — or one that is unreachable — you get the deterministic report and a note on stderr saying why. Standalone and build-tool-agnostic: it needs git, not any particular font compiler.
Features
- Grounded AI narration, on by default — the deterministic facts become prose, with the facts attached verbatim for verification. The model may only restate them; a deterministic post-check flags invention, and nothing is ever published. See AI narration.
- Commit assistant — draft a commit message from the working tree, edit it, commit it.
- Range / release notes — aggregate a tag/commit range into notes.
- Semantic source diff — the ground truth under the prose: outlines (coordinate-based,
not text), kerning/groups, fontinfo, OpenType features (
feaLibrule level), designspace (axes/masters/instances), and build-profile YAML (option level).--no-aigives you exactly this and nothing else. - No silent omissions — every changed tracked file surfaces as a fact (semantic when
available, else a bare added/removed/modified note).
.gitignoreis honoured. - Your choice of AI provider — Claude, GPT, Gemini, Grok, Mistral, Groq, DeepSeek, Qwen, Kimi, GLM, OpenRouter, or a local model (Ollama, LM Studio, vLLM, llama.cpp) via any OpenAI-compatible endpoint. See the table for the full list.
- Per-repo AI accounts — a corporate repo can use a different provider and key from your personal ones, with the key stored once and shared by name, never per repo.
- Prose in your language —
tdreport set-lang Spanishnarrates in Spanish while the deterministic facts stay English and byte-stable. - One settings screen —
tdreport settingsfor all of it; addressing a repo by path once is enough, the short name works afterwards. See the settings map for the whole model on one page.
Install
This is a command-line tool, so install it as a global CLI: that puts the tdreport
command on your PATH (no sudo, no virtualenv to activate) while keeping its
dependencies isolated in their own environment.
uv tool install ufo-tdkit-report # or: pipx install ufo-tdkit-report
Either works; uv is what this project uses for everything
else. pip install ufo-tdkit-report also works but puts the dependencies wherever pip
is pointing, which for a CLI is rarely what you want.
Then tdreport works from any directory:
uv tool upgrade ufo-tdkit-report # take the newest release
uv tool uninstall ufo-tdkit-report
From source instead, to run an unreleased main or your own edits:
uv tool install git+https://github.com/typedev/ufo-tdkit-report.git # from the git URL
# …or, inside a clone, `uv tool install .` (add --force to replace an existing install)
As a library, add it the way you add any dependency — a compatible-release bound is the sensible pin, since the deterministic report is byte-stable within a minor version:
dependencies = ["ufo-tdkit-report>=0.5,<0.6"]
PyPI history starts at 0.5.2. Earlier versions exist as git tags and GitHub Releases
but were never uploaded, so an exact pin below that (==0.5.1) resolves to nothing from
the index — use the git URL for those, or a range, which lands on a published version
either way.
For hacking on the tool itself, work inside a synced project env instead and run via
uv run (no global install needed):
git clone https://github.com/typedev/ufo-tdkit-report.git
cd ufo-tdkit-report
uv sync --extra dev
uv run tdreport --help
Verify a global install landed on your PATH:
tdreport --help
Usage
# Working-tree commit assistant
tdreport # draft a message for the current repo, narrated
tdreport --no-ai # …the deterministic facts only, no model call
tdreport --ai # …require the narrative: fail rather than fall back
tdreport commit # commit the working tree with the drafted message
# One-time setup for the narration
tdreport set-provider # pick the provider (Claude, GPT, Gemini, Grok, local…)
tdreport set-key sk-... # store that provider's API key, owner-only
tdreport set-model # pick the narration model from a menu
tdreport set-lang Spanish # optional: narrate in another language
tdreport set-url http://localhost:11434/v1 # only for `custom` or a non-default local host
# Several providers side by side (e.g. a corporate one)
tdreport accounts # menu: what exists, and "a" to add one
tdreport bind work ~/fonts/AcmeSans # that repo uses that account (and its key)
# Named repos — addressing one by path remembers it
tdreport ~/fonts/MyFont # works, and remembers it as "MyFont"
tdreport MyFont # …so from now on the short name is enough
tdreport add myfont ~/fonts/X # or register a name of your choosing
tdreport ls / rm <name> / prune # list, forget, drop entries whose repo is gone
# Everything in one screen
tdreport settings # interactive: provider, model, key, language, accounts, repos
# Committed history
tdreport v2.005..v2.006 # endpoint diff of a range (cwd repo)
tdreport --notes v2.005..HEAD # aggregate every commit in the range into release notes
Editing the drafted commit message
tdreport <repo> prints the draft and where it lives; answer n at the prompt, edit
that file, then tdreport <repo> commit — the edited text is used as-is, and you do not
need to repeat anything.
Two things are guarded:
- Re-running
tdreport <repo>to look at the report again keeps your edits rather than overwriting them. Use--regeneratewhen you do want a fresh draft. - If the working tree changes after you drafted, the message no longer describes what
would be committed. Committing it is refused — on a terminal it asks whether to
redraft, commit anyway, or abort; in a pipe it errors and names
--stale-ok. A wrong description in git history cannot be fixed without rewriting it.
Staleness is judged on the facts, not the bytes: an editor re-serializing a UFO does not invalidate a draft, because the extracted facts are unchanged.
Recipes
A one-page reference for all of the below — the two levels, what wins, and what to do when something looks wrong — is in docs/settings.md.
One repo, the default model. Address it by path once; it is remembered.
tdreport set-key sk-ant-... # once, for everything
tdreport ~/fonts/MyFont # remembered 'MyFont' -> …
tdreport MyFont # from now on, narrated
A second repo on a different model, same key. Override the model on that repo only — no second account, so the key is still stored exactly once.
tdreport ~/fonts/AcmeSans # remembered 'AcmeSans'
tdreport repo AcmeSans model claude-haiku-4-5 # this repo only
tdreport repo AcmeSans # check what it resolves to
Switch the model of one repo, or hand it back to the account's model:
tdreport repo MyFont model claude-sonnet-5
tdreport repo MyFont clear model # back to whatever the account uses
Change the model everywhere (the account's model, i.e. every repo without an override):
tdreport set-model # menu of the provider's models
A repo on a different provider with its own key — that is what accounts are for:
tdreport accounts # "a" walks through name, provider, key, model
tdreport bind work ~/fonts/ClientFont # and any other repo of that client
One-off, changing nothing:
tdreport MyFont --ai-model claude-haiku-4-5 --ai-lang German
Where does this repo's setting come from? tdreport settings <repo> shows every
value with its source and lets you change it at the right level; tdreport repo <name>
prints the same resolution non-interactively; tdreport ls lists every repo with
its overrides; tdreport settings shows and edits all of it.
AI narration
Narration is on by default, and needs an API key (where it lives).
Three things turn it off, and they are not the same thing:
| what it means | note on stderr | |
|---|---|---|
--no-ai |
you asked for the facts only | no — nothing to explain |
--json |
machine-readable facts; a narrative is not part of to_dict() |
no |
| nothing configured, or the provider failed | the prose could not be produced | yes, saying which |
The fallback is deliberate and it is deliberately loud. A tool whose output silently
depends on whether a key happens to be lying around is exactly the sort of thing this one
exists to eliminate — so the report on stdout stays the byte-stable artefact a pipe or
a redirect expects, and the explanation goes to stderr. When you would rather fail
than receive the quieter output — CI that publishes the prose, say — --ai says so:
tdreport --notes v2.005..HEAD --ai > notes.md # non-zero exit; notes.md left empty
# rather than holding a report you did not ask for
--ai-note, the pre-0.5 way of asking for narration, is still accepted and does nothing,
so existing scripts and hooks keep working. Asking for both at once (--ai --json, or
--ai --no-ai) is an argument error rather than a silent choice between them.
Where the key lives
The key comes from one place, the tool's own config directory:
| OS | path |
|---|---|
| Linux | ~/.config/ufo-tdkit-report/ (or $XDG_CONFIG_HOME/ufo-tdkit-report/) |
| macOS | ~/Library/Application Support/ufo-tdkit-report/ |
| Windows | %APPDATA%\ufo-tdkit-report\ |
Set TDREPORT_CONFIG_DIR to put all of it somewhere else (a portable install, a test
harness). That chooses the directory only — the API key still comes from .env inside
it, or from an explicit argument, and never from the environment.
Three files and a drafts directory live there, and only the first holds secrets:
| file | contents | permissions |
|---|---|---|
.env |
API keys, one per account | 0600 * |
settings.json |
accounts: provider, model, language | — |
repos.json |
registered repos and their bindings | — |
drafts/ |
commit-message drafts and their fingerprints, deleted once committed | — |
* 0600 on Linux and macOS, and checked every time the file is read, not only set
when it is written: a cp -r of your dotfiles, a restored backup or a permissive umask
widens it silently, so a readable key file gets one note naming the mode and the chmod
that fixes it. Windows has no such mode bit — chmod there can only toggle read-only —
so the file is protected by the ACL of the user-profile directory it sits in, which is
already user-only.
Keys never appear in settings.json or repos.json, and nothing tdreport-related is
ever written inside your font repository — not a config file, which would be committed
and shared, and not the drafted commit message either. An earlier version kept that draft
in <repo>/.tdreport/ and appended a line to your .gitignore to hide it; if you still
have that directory, tdreport says so and you can delete both. settings.json and
repos.json are safe to back up or keep in dotfiles.
The key lives in that one file and nowhere else: the tool deliberately does not read
ANTHROPIC_API_KEY/OPENAI_API_KEY from the environment, nor a .env in the repo or
cwd — so a stray export or a project .env can't leak in.
tdreport set-provider # menu of providers
tdreport set-key sk-... # that provider's key (also accepts stdin: `… | tdreport set-key`)
tdreport set-model # menu of that provider's models
tdreport # commit message, AI-drafted
tdreport --notes v2.005..HEAD # release notes, AI-drafted
Running tdreport set-key with no argument prompts for the key without echoing it
(so it never lands in your shell history).
Providers
| provider | endpoint | key needed |
|---|---|---|
anthropic |
Claude — the default | yes |
openai |
GPT (the models behind Codex) | yes |
gemini |
Google Gemini | yes |
xai |
Grok | yes |
mistral |
Mistral AI (incl. Codestral) | yes |
groq |
Groq — open models, fast inference | yes |
deepseek |
DeepSeek | yes |
qwen |
Qwen via Alibaba DashScope | yes |
moonshot |
Moonshot — Kimi | yes |
zai |
Z.ai — GLM | yes |
openrouter |
many vendors behind one key | yes |
ollama |
local, http://localhost:11434/v1 |
no |
lmstudio |
local, http://localhost:1234/v1 |
no |
custom |
any OpenAI-compatible server (vLLM, llama.cpp, gateway) | optional |
Use tdreport set-url <base-url> for the custom provider, a local server on a
non-default host or port, or a vendor's regional endpoint — mainland-China DashScope
and Moonshot, or Z.ai's coding-plan host. tdreport settings prints the alternative
alongside the provider that has one.
Everything except Anthropic speaks the OpenAI-compatible /chat/completions API — Google
included, through its /v1beta/openai compatibility layer — so a provider is a table row,
not an integration, and a local model is just a base URL. No
vendor SDK is used; the call is a plain urllib POST, and the tool has no third-party
runtime dependency for this.
Reasoning models (DeepSeek's reasoners, and the reasoning modes of others) spend the
completion budget on their private reasoning before writing anything, so they need a
much larger cap than the prose alone would suggest — the default is 8192 and
the default is 8192 and it covers the whole completion. If one still stops before
answering, the note names the token counts rather than reporting an empty narrative —
and, like any narration failure, you get the deterministic report rather than nothing.
Use --ai to make it an error instead.
Raise the cap where it belongs rather than typing a flag every run:
tdreport set-max-tokens 32000 # this account
tdreport repo Evacode max-tokens 32000 # …or just this repo
tdreport --ai-max-tokens 32000 # …or just this run
The built-in default stays conservative on purpose: max_tokens goes to every provider,
and some models cap their output well below a number chosen to suit a talkative one.
Local models are worth a caveat: the narrator's grounding depends on the model following instructions, and a small local model invents more than a large hosted one. The deterministic facts are attached verbatim to every narration precisely so you can check.
The settings screen
tdreport settings <repo> scopes the screen to one repository. Two settings live on the
account (the provider and its key — a key is a key for a provider) and four can
live on the repo (model, language, grounding strictness, token cap); looking at a repo that split
is invisible, so this screen shows every value with where it comes from:
Settings — repo 'AcmeSans'
~/fonts/AcmeSans
1. Account work from this repo
provider openai from account 'work'
API key set (…9999) from account 'work'
2. Model gpt-5-mini from this repo
3. Language German from account 'work'
4. Grounding strict from account 'work'
5. Max tokens 32000 from this repo
6. Edit the account itself (affects every repo using it)
q. Quit
For a repo you do not pick a provider — you pick an account, and option 1 lists them with what each one actually brings:
Accounts — an account carries the provider AND the key for it:
1. default anthropic key set (…6QAA)
2. work openai key set (…9999) <- current
Options 2 through 5 set overrides for this repo only (an empty answer clears one, handing the field back to the account). Option 6 jumps into the account's own screen, which affects every repo using it.
tdreport settings with no repo puts all of it on one screen — provider, model, key,
language, base URL, grounding strictness, the token cap, accounts, and the registered
repos with their bindings:
Settings — account 'default'
1. AI provider deepseek
2. Model deepseek-chat
3. API key set (…4f2a)
4. Language German
5. Base URL https://api.deepseek.com/v1
6. Grounding warn
7. Max tokens 32000
8. Accounts default, work
9. Repos 3 registered, 2 bound
q. Quit
It changes nothing on its own: every edit goes through the same functions the set-*
commands use, so the two can be mixed freely. Keys are shown masked and never printed.
Without a TTY (a pipe, CI, a hook) it prints the listing and exits instead of blocking —
tdreport settings --json gives the same thing as machine-readable JSON.
Registered repos
Addressing a repo by path remembers it under the git root's basename, so this is a one-time cost:
tdreport ~/fonts/AcmeSans # remembered 'AcmeSans' -> … (next time: `tdreport AcmeSans`)
tdreport AcmeSans # from now on
tdreport ls # what is registered, with bindings; MISSING marks dead paths
tdreport rm AcmeSans # forget one
tdreport prune # drop every entry whose repo is gone
It is never silent (it says what it remembered) and never destructive: if the name
already points at a different repo, that is reported and nothing is overwritten — use
tdreport add <other-name> <path>. The bare tdreport in a cwd registers nothing, and
an unknown bare name is still an error rather than a silent guess.
Accounts
An account bundles a provider, model, language and one key under a short name. Repositories reference an account by name, so twenty corporate repos share one key that is stored exactly once:
tdreport accounts is the screen for them — it lists what each account brings (provider,
model, key status) and a walks you through adding one, asking for the name, the
provider, the key and the model in turn:
AI accounts — an account carries a provider AND the key for it:
1. default anthropic claude-opus-5 key set (…6QAA) [default]
2. work openai gpt-5 key set (…9999)
a. add an account d. change the default r. remove one
a number opens that account's settings; q. back
Then point repos at it:
tdreport bind work ~/fonts/AcmeSans # this repo now uses that account
tdreport bind work ~/fonts/AcmeSerif # …and so does this one, same key
Scriptable equivalents, for CI or a dotfiles bootstrap:
tdreport account add work openai # name, then provider
tdreport --ai-account work set-key # stored as TDREPORT_KEY_WORK
tdreport --ai-account work set-model gpt-5
tdreport account use work # make it the default for unbound repos
tdreport account rm work # removes the account AND its stored key
Choosing the model
tdreport set-model sets the model for the account — that is, for every repo that
has no override of its own. It shows a numbered menu of the models available to that
account's provider:
tdreport set-model # menu; Enter keeps the current model
tdreport set-model deepseek-chat # or set one directly (no menu, works in CI)
The menu is fetched live from the provider's /models endpoint, so it never goes stale;
with no key or no network it falls back to a built-in hint list, and you can always type
a model id that is not listed. The picker says which of the two you are looking at — a
short fallback list otherwise reads as "this provider only has two models" when it really
means nobody could ask yet.
The key prompt does not echo. That is deliberate (a pasted key must not land in a scroll buffer), but it means a paste shows nothing at all — the prompt says so, and confirms the stored key masked once it lands.
To give one repo a different model without a second account (and without storing the key twice), override it on that repo:
tdreport repo AcmeSans model claude-haiku-4-5 # this repo only
tdreport repo AcmeSans language German # same idea for the prose language
tdreport repo AcmeSans clear model # back to the account's model
tdreport repo AcmeSans # what it resolves to, and why
Language
tdreport set-lang Spanish (or --ai-lang Spanish for one run) makes the AI write its
prose in that language. Only the prose: the deterministic report, the attached facts, the
section headings and the attribution footer stay English, because they are machine output
and localizing them would make the byte-stable report depend on a local preference. The
model is also instructed to keep every identifier verbatim — glyph names, codepoints,
feature tags, paths and option keys are never translated or transliterated.
How settings are resolved
Every setting resolves in the same order, and this order applies to library callers too, not just the CLI:
- an explicit argument —
--ai-model/--ai-provider/--ai-lang/--ai-account(in the library,narrate(model=..., provider=...)) - the repository's own entry (
tdreport bind, or a per-repomodel/language/grounding/max-tokens) - the account that entry names
- the default account (
tdreport account use) - the built-in default — provider
anthropic, modelclaude-opus-5, language English
The provider and model that actually ran are named in the attribution line of every AI-drafted report and commit message, so it is always visible after the fact.
Grounding check
The narrator may only restate the facts. That used to rest entirely on the model's
obedience plus a human reading the attached <details> — fine for a large hosted model,
thin once a local 7B can narrate. So after every narration the prose is compared against
the facts, deterministically and offline:
- codepoints, coordinate deltas and feature tags/alternates the facts do not contain;
- near-misses of names they do contain (
guillemotleftforguillemetleft) — what invention actually looks like; - anything the model marked up as an identifier that is not in the facts.
That last one is why the prompt asks the model to backtick every glyph name, codepoint and
tag. four, one, section, period and bullet are standard glyph names and
ordinary English words, so "in one glyph across four masters" is correct prose containing
two of them; no length filter or neighbourhood rule separates that from a glyph claim. The
model declares what it means by marking it up, and the check verifies the declaration
rather than guessing. If a model ignores that instruction, the result says glyph-name
checking was skipped — a skipped check must not look like a clean pass.
By default a finding is a note; a strict account or repo refuses the narration instead —
you get the deterministic report and a note saying the narration was refused, or a
non-zero exit if you also passed --ai:
tdreport set-grounding strict # this account
tdreport repo AcmeSans grounding warn # …but not this repo
tdreport --strict-grounding # just this run
Strictness is a setting rather than only a flag because it belongs to the model: a small local one earns a refusal, a large hosted one usually needs only the note.
What it cannot catch: an invented meaning attached to a real identifier ("uni20C5,
the Tamil currency sign"). No token comparison reaches that. The facts travel with every
narration precisely so that a human can.
The narrator is strictly grounded — it may only restate the deterministic facts, which
are always attached verbatim in a <details> block — and it never publishes anything.
In code, everything unset resolves the same way:
from ufo_tdkit_report import extract_facts, narrate, resolve_ai_settings
report = extract_facts(".", "HEAD~1..HEAD")
print(narrate(report, repo=".")) # this repo's account, model, language
print(narrate(report, provider="ollama", # …or pin any of it per call
model="llama3.2", language="German"))
print(resolve_ai_settings(repo=".").model) # what a run here would use
Library
The CLI's default — narrate, fall back to the facts — is one call:
from ufo_tdkit_report import describe_changes
print(describe_changes(".", "HEAD~1..HEAD")) # narrated, or the facts
print(describe_changes(".", "v1..v2", notes=True)) # release notes
print(describe_changes(".", "HEAD~1..HEAD", ai=False)) # never calls out
describe_changes is the only call here that turns a repository into a report by way of
a model; the extractors stay pure and offline, on purpose: they get embedded in build pipelines, and a fact
extractor spending money on its own initiative is not a reasonable default at that layer.
Compose them deliberately when you want the failure rather than the fallback:
from ufo_tdkit_report import extract_facts, aggregate_range, narrate, resolve_ai_settings
report = extract_facts(".", "HEAD~1..HEAD")
print(report.render_text()) # deterministic, no network
print(narrate(report)) # raises NarratorError instead of falling back
print(resolve_ai_settings(repo=".").model) # what a run here would use
Two other public calls reach a provider by their nature and are not part of extraction:
narrate / narrate_commit, and list_models, which queries /models to fill a picker
and degrades to a built-in hint list with no key or no network.
Everything unset resolves through the same chain the CLI uses, so an embedding tool
honours the owner's accounts and per-repo bindings without re-implementing the lookup.
A report remembers the repository it came from, and any path inside a registered repo
resolves to it, so narrate(report) reaches the right provider and key without the
caller restating them. When a repository is named but not registered — meaning its
settings came from the default account — a UnboundRepoWarning says so rather than
letting it pass unnoticed.
Three warning categories a public call can raise are exported from the package root
alongside the calls themselves, so production code never has to name an internal module
to catch one: UnboundRepoWarning (an unregistered repo fell back to the default account),
GroundingWarning (the narration used identifiers the facts do not support) and
InsecureKeyFileWarning (the key file is readable by others). They are defined in
settings, narrator and config respectively — beside the checks that raise them —
so the root is the address to import from in production code.
A test reaching past the root is a different thing, and a good one: raising a warning
through its defining module while your production code catches the root-imported name is
how the re-export gets verified from the outside — if one ever became a distinct subclass
rather than the same object, that test fails. tests/test_report_public_api.py does
exactly this here, and TDKit's suite does it from the consumer side.
import warnings
from ufo_tdkit_report import GroundingWarning, InsecureKeyFileWarning, UnboundRepoWarning, narrate
with warnings.catch_warnings(record=True) as notes:
warnings.simplefilter("always", (UnboundRepoWarning, GroundingWarning, InsecureKeyFileWarning))
prose = narrate(report) # route them into your own report…
If your code must tolerate an older install than it asks for — a stale uv tool
install, say, which uv sync does not update — read them off the module by name instead.
One combined from … import A, B, C fails entirely when one name is missing, and the one
you would silently lose is GroundingWarning: the only automatic signal that a narration
invented something.
import ufo_tdkit_report as report_pkg
categories = tuple(
c for c in (getattr(report_pkg, n, None) for n in
("UnboundRepoWarning", "GroundingWarning", "InsecureKeyFileWarning"))
if c is not None
)
ufo_tdkit_report.settings.UnboundRepoWarning, ufo_tdkit_report.narrator.GroundingWarning
and ufo_tdkit_report.config.InsecureKeyFileWarning remain valid names for the same
classes — the root export is an alias, never a second definition.
Build-profile consequence semantics (e.g. "ttfautohint off → no TT hinting") are
build-tool-specific: a consumer that owns an option schema injects it via
fold_facts(..., schema=...). Without one, profile changes render as a bare option diff.
License
Apache-2.0
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 ufo_tdkit_report-0.5.3.tar.gz.
File metadata
- Download URL: ufo_tdkit_report-0.5.3.tar.gz
- Upload date:
- Size: 193.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f90b185dd5dc6cf1a00d7ef08ebffdcfe41769d1dc001449fd6a2afa6860560c
|
|
| MD5 |
9256c8b613589f6f4cbe0b3bf8b710be
|
|
| BLAKE2b-256 |
8c3723824fa5ef99e47acc1f1dd6c6dc6edecbd539e8302eb6233a967769c1ba
|
File details
Details for the file ufo_tdkit_report-0.5.3-py3-none-any.whl.
File metadata
- Download URL: ufo_tdkit_report-0.5.3-py3-none-any.whl
- Upload date:
- Size: 98.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a6af432bd25c7ac58ce5009d11f0bf238249fae6f9b72a0e637929f769ac9db
|
|
| MD5 |
f5134d2eae24493939d839c9cfaeacb5
|
|
| BLAKE2b-256 |
44a2cb84feb26236fcf04f4a947609f5ad2a2b96d617d6394e8622f1a31bcb97
|