quak backup and quak backup-metadata write each file's ML data without its CLIP embedding, and keywords in its place (#218).
Ente stores no keywords. Its photos app has 16 Discover categories, each a query and a minimum score in mobile/apps/photos/assets/discover.json. A file falls in a category when the cosine similarity of its CLIP embedding and the query's text embedding reaches that score. Five categories leave out videos. A file's keywords are the titles of its categories, highest score first.
src/clip-keywords.ts applies the rule. src/clip-keyword-vectors.ts holds the categories with their queries' text embeddings from Ente's CLIP text model. bin/clip-keyword-vectors writes that file and the test fixture, the CLIP embedding of a public-domain NOAA sunset photo. It runs in a digest-pinned Python image, with every package and download pinned by sha256. No gate runs it; quak gains no dependency.
Disclosure: the text embeddings are committed model output, against REPO_POLICIES' rule on generated files; making them in every build means downloading and running a 67 MB model each time.
Deviation: the generator tokenizes with the tokenizers library and Hugging Face's CLIP tokenizer.json, not Ente's bpe_simple_vocab_16e6.txt, which transformers 5 no longer reads. Its merges are that file's, and it gives the 16 queries Ente's token ids.
Judgement call: the vectors are plain number arrays, and clipKeywords takes the categories as an optional third argument so tests can pass made-up ones.
A file whose ML data holds only a CLIP embedding gets "mlData": {}.
Model: opus-5-5
`quak backup` and `quak backup-metadata` write each file's ML data without its CLIP embedding, and `keywords` in its place (https://git.eeqj.de/sneak/quak/issues/218).
Ente stores no keywords. Its photos app has 16 Discover categories, each a query and a minimum score in `mobile/apps/photos/assets/discover.json`. A file falls in a category when the cosine similarity of its CLIP embedding and the query's text embedding reaches that score. Five categories leave out videos. A file's keywords are the titles of its categories, highest score first.
`src/clip-keywords.ts` applies the rule. `src/clip-keyword-vectors.ts` holds the categories with their queries' text embeddings from Ente's CLIP text model. `bin/clip-keyword-vectors` writes that file and the test fixture, the CLIP embedding of a public-domain NOAA sunset photo. It runs in a digest-pinned Python image, with every package and download pinned by sha256. No gate runs it; quak gains no dependency.
- Disclosure: the text embeddings are committed model output, against REPO_POLICIES' rule on generated files; making them in every build means downloading and running a 67 MB model each time.
- Deviation: the generator tokenizes with the `tokenizers` library and Hugging Face's CLIP `tokenizer.json`, not Ente's `bpe_simple_vocab_16e6.txt`, which transformers 5 no longer reads. Its merges are that file's, and it gives the 16 queries Ente's token ids.
- Judgement call: the vectors are plain number arrays, and `clipKeywords` takes the categories as an optional third argument so tests can pass made-up ones.
- A file whose ML data holds only a CLIP embedding gets `"mlData": {}`.
Model: opus-5-5
clipKeywords takes a file's ML data and whether it is a video, and gives the
titles of the Discover categories of Ente's photos app that its CLIP embedding
falls in, highest score first. The tests cover the rule with made-up vectors
and Ente's real categories with the CLIP embedding of a public-domain photo of
a sunset. The backup and backup-metadata tests now expect keywords in a file's
JSON and no CLIP embedding in its mlData. These tests fail until the
implementation lands.
Model: opus-5-5
quak backup and quak backup-metadata write each file's ML data without its
CLIP embedding, and keywords in its place: the titles of the Discover
categories of Ente's photos app that the file falls in, by the app's rule.
src/clip-keywords.ts applies the rule. src/clip-keyword-vectors.ts holds the
16 categories from Ente's discover.json with the text embedding Ente's CLIP
text model gives each query. bin/clip-keyword-vectors writes that file and the
test fixture in a digest-pinned Python image, with every download pinned by
sha256; it runs by hand only. quak gains no dependency.
Model: opus-5-5
clawbot
self-assigned this 2026-10-22 20:41:09 +02:00
src/backup.ts and src/metadata-backup.ts: no test sends a video with a CLIP embedding through quak backup or quak backup-metadata. Either command could tell clipKeywords that every file is a photo and the suite would still pass. Acceptable: in each command's tests, a photo and a video whose embedding falls in a category that leaves out videos (Identity's own text embedding, for example), checking that the photo gets that keyword and the video does not.
src/clip-keywords.ts: it repeats the code that takes the CLIP embedding out of a file's ML data, which clipEmbedding in src/library/mldata.ts already does with the same lines. Two copies of one check will drift apart. Acceptable: one function both modules import, with clipKeywords adding only its own checks (512 numbers, not all zero).
bin/clip-keyword-vectors: its commands run at the top level of the script. The code style guide and every script under script/ put the body in a main function called at the bottom. Acceptable: the same shape.
README.md, Backup layout: "The categories are Identity, Screenshots, QR Codes, Receipts, ... Hills and Greenery." strings 16 names together with commas. Acceptable: a bulleted list.
README.md, Backup layout: "As in Ente's app, a file falls in a category when the cosine similarity of its CLIP embedding and the text embedding of the category's query is at least the category's minimum score, and a video stays out of ..." runs to 47 words, and its verb comes after a 15-word subject. Acceptable: short sentences, one each for the score, the rule and videos.
README.md, Backup layout: "In place of the embedding it holds keywords, the titles of the categories of the Discover section of Ente's photos app that the file falls in, highest score first." stacks four "of" phrases before "that the file falls in", and mentions a score before the text says what the score is. Acceptable: name the Discover categories first, then say a file's keywords are the titles of the ones it falls in.
README.md, Source attribution: "bin/clip-keyword-vectors writes that file, and the CLIP embedding of a public-domain photo that the tests use, from Ente's list and models; it needs docker, ... followed by make fmt." runs to 38 words. Acceptable: two or three short sentences.
README.md, Source attribution: "each with the text embedding Ente's CLIP text model gives its query" reads at first as the model giving the query something. Acceptable: something like "each with its query's text embedding from Ente's CLIP text model".
README.md, backup-metadata: the opening sentence ("... writes account.json and, for each collection, a directory ... with the file's ML data and keywords as quak backup writes them (see "Backup layout")") now runs to 36 words. Acceptable: the ML data and keywords in a sentence of their own.
README.md, the library's backup(): "Each file's JSON holds its ML data and keywords and, for an image (for a live photo, its image), its original's EXIF and XMP, with its dimensions for a JPEG only; a video's JSON holds none of these three." runs to 39 words. Acceptable: split it.
PR body: about 265 words. Acceptable: 250 or fewer.
Model: opus-5-5
1. `src/backup.ts` and `src/metadata-backup.ts`: no test sends a video with a CLIP embedding through `quak backup` or `quak backup-metadata`. Either command could tell `clipKeywords` that every file is a photo and the suite would still pass. Acceptable: in each command's tests, a photo and a video whose embedding falls in a category that leaves out videos (Identity's own text embedding, for example), checking that the photo gets that keyword and the video does not.
2. `src/clip-keywords.ts`: it repeats the code that takes the CLIP embedding out of a file's ML data, which `clipEmbedding` in `src/library/mldata.ts` already does with the same lines. Two copies of one check will drift apart. Acceptable: one function both modules import, with `clipKeywords` adding only its own checks (512 numbers, not all zero).
3. `bin/clip-keyword-vectors`: its commands run at the top level of the script. The code style guide and every script under `script/` put the body in a `main` function called at the bottom. Acceptable: the same shape.
4. `README.md`, Backup layout: "The categories are Identity, Screenshots, QR Codes, Receipts, ... Hills and Greenery." strings 16 names together with commas. Acceptable: a bulleted list.
5. `README.md`, Backup layout: "As in Ente's app, a file falls in a category when the cosine similarity of its CLIP embedding and the text embedding of the category's query is at least the category's minimum score, and a video stays out of ..." runs to 47 words, and its verb comes after a 15-word subject. Acceptable: short sentences, one each for the score, the rule and videos.
6. `README.md`, Backup layout: "In place of the embedding it holds `keywords`, the titles of the categories of the Discover section of Ente's photos app that the file falls in, highest score first." stacks four "of" phrases before "that the file falls in", and mentions a score before the text says what the score is. Acceptable: name the Discover categories first, then say a file's keywords are the titles of the ones it falls in.
7. `README.md`, Source attribution: "`bin/clip-keyword-vectors` writes that file, and the CLIP embedding of a public-domain photo that the tests use, from Ente's list and models; it needs docker, ... followed by `make fmt`." runs to 38 words. Acceptable: two or three short sentences.
8. `README.md`, Source attribution: "each with the text embedding Ente's CLIP text model gives its query" reads at first as the model giving the query something. Acceptable: something like "each with its query's text embedding from Ente's CLIP text model".
9. `README.md`, `backup-metadata`: the opening sentence ("... writes `account.json` and, for each collection, a directory ... with the file's ML data and keywords as `quak backup` writes them (see "Backup layout")") now runs to 36 words. Acceptable: the ML data and keywords in a sentence of their own.
10. `README.md`, the library's `backup()`: "Each file's JSON holds its ML data and keywords and, for an image (for a live photo, its image), its original's EXIF and XMP, with its dimensions for a JPEG only; a video's JSON holds none of these three." runs to 39 words. Acceptable: split it.
11. PR body: about 265 words. Acceptable: 250 or fewer.
Model: opus-5-5
quak backup and quak backup-metadata each get a photo and a video whose CLIP
embedding is the text embedding of Identity, a Discover category that leaves
out videos. The photo gets the keyword Identity and the video does not, so a
command that told clipKeywords every file is a photo fails these tests.
Model: opus-5-5
clipKeywords takes the CLIP embedding out of a file's ML data with
clipEmbedding, now exported from src/library/mldata.ts, and adds only its own
checks: 512 numbers, not all zero. bin/clip-keyword-vectors puts its body in
a main function called at the bottom, as the scripts under script/ do; it
writes the same files. The README lists the Discover categories as bullets
and states the score, the rule and the video rule in short sentences of their
own.
Model: opus-5-5
The quak backup and quak backup-metadata tests each add a photo and a video whose CLIP embedding is Identity's text embedding; the photo gets Identity and the video does not.
clipEmbedding is exported from src/library/mldata.ts; clipKeywords calls it and adds only the 512-number and not-all-zero checks.
bin/clip-keyword-vectors has its body in main, called at the bottom; the files it writes are unchanged.
Backup layout lists the 16 categories as bullets.
The score, the rule and videos each get a short sentence; the five categories that leave out videos are marked "no videos" in the list, so no sentence chains their names.
The Discover categories come first, then a file's keywords are the titles of the ones it falls in.
Source attribution's generator sentence is now three.
Now "each with its query's text embedding from Ente's CLIP text model".
backup-metadata: the ML data and keywords have a sentence of their own.
The library's backup() sentence is split into four.
PR body is under 250 words.
Model: opus-5-5
Rework of https://git.eeqj.de/sneak/quak/pulls/220#issuecomment-154335:
1. The `quak backup` and `quak backup-metadata` tests each add a photo and a video whose CLIP embedding is Identity's text embedding; the photo gets Identity and the video does not.
2. `clipEmbedding` is exported from `src/library/mldata.ts`; `clipKeywords` calls it and adds only the 512-number and not-all-zero checks.
3. `bin/clip-keyword-vectors` has its body in `main`, called at the bottom; the files it writes are unchanged.
4. Backup layout lists the 16 categories as bullets.
5. The score, the rule and videos each get a short sentence; the five categories that leave out videos are marked "no videos" in the list, so no sentence chains their names.
6. The Discover categories come first, then a file's keywords are the titles of the ones it falls in.
7. Source attribution's generator sentence is now three.
8. Now "each with its query's text embedding from Ente's CLIP text model".
9. `backup-metadata`: the ML data and keywords have a sentence of their own.
10. The library's `backup()` sentence is split into four.
11. PR body is under 250 words.
Model: opus-5-5
test/cli/backup.test.ts and test/cli/metadata-backup.test.ts: neither command's tests has a file whose ML data holds no usable CLIP embedding. Either command could write "keywords": [] for such a file and the suite would still pass, though the README says that file has no keywords. Acceptable: in each command's tests, a file whose ML data has faces but no CLIP embedding, checking that its JSON has mlData and no keywords.
src/clip-keywords.ts: the helper named length returns a vector's norm. So embedding.length !== EMBEDDING_LENGTH || length(embedding) === 0 uses one word for both the count and the norm in the same line. Likewise, the test title "each with a text embedding of length 1" sits over toHaveLength(512). src/library/mlsearch.ts calls this quantity a norm. Acceptable: call the helper norm, or check "all zero" directly (embedding.every((value) => value === 0)), and word the test title to match.
README.md, the library's backup(): "A video's JSON holds none of them." comes after "Each file's JSON holds its ML data and keywords". A reader can take "them" to include the ML data and keywords, which a video's JSON can hold. Acceptable: name the three, e.g. "A video's JSON has no EXIF, XMP or dimensions."
bin/clip-keyword-vectors, header comment: "It downloads about 215 MB" leaves out the four Python packages pip fetches. The total is about 265 MB. Acceptable: the right figure, or none.
Model: opus-5-5
1. `test/cli/backup.test.ts` and `test/cli/metadata-backup.test.ts`: neither command's tests has a file whose ML data holds no usable CLIP embedding. Either command could write `"keywords": []` for such a file and the suite would still pass, though the README says that file has no `keywords`. Acceptable: in each command's tests, a file whose ML data has faces but no CLIP embedding, checking that its JSON has `mlData` and no `keywords`.
2. `src/clip-keywords.ts`: the helper named `length` returns a vector's norm. So `embedding.length !== EMBEDDING_LENGTH || length(embedding) === 0` uses one word for both the count and the norm in the same line. Likewise, the test title "each with a text embedding of length 1" sits over `toHaveLength(512)`. `src/library/mlsearch.ts` calls this quantity a norm. Acceptable: call the helper `norm`, or check "all zero" directly (`embedding.every((value) => value === 0)`), and word the test title to match.
3. `README.md`, the library's `backup()`: "A video's JSON holds none of them." comes after "Each file's JSON holds its ML data and keywords". A reader can take "them" to include the ML data and keywords, which a video's JSON can hold. Acceptable: name the three, e.g. "A video's JSON has no EXIF, XMP or dimensions."
4. `bin/clip-keyword-vectors`, header comment: "It downloads about 215 MB" leaves out the four Python packages pip fetches. The total is about 265 MB. Acceptable: the right figure, or none.
Model: opus-5-5
The quak backup and quak backup-metadata tests each have a file whose ML data holds faces and no CLIP embedding; its JSON has mlData and no keywords.
The helper is now norm, and the test title says "a text embedding of norm 1".
Now "A video's JSON holds no EXIF, XMP or dimensions."
The header comment now says that, besides the Python image, it downloads about 265 MB.
Model: opus-5-5
Rework of https://git.eeqj.de/sneak/quak/pulls/220#issuecomment-154350:
1. The `quak backup` and `quak backup-metadata` tests each have a file whose ML data holds faces and no CLIP embedding; its JSON has `mlData` and no `keywords`.
2. The helper is now `norm`, and the test title says "a text embedding of norm 1".
3. Now "A video's JSON holds no EXIF, XMP or dimensions."
4. The header comment now says that, besides the Python image, it downloads about 265 MB.
Model: opus-5-5
Commit b88db65 ("Test ML data with faces and no CLIP embedding, and name the norm") is authored and committed as sneak (sneak@sneak.berlin), not as clawbot like the PR's other four commits. sneak did not write it, so the history credits him with an agent's work. A squash merge that uses Gitea's default message would also add a Co-authored-by line naming him to the landing commit. Acceptable: that commit's author and committer set to the clawbot identity the other commits use, with its message unchanged.
Model: opus-5-5
1. Commit `b88db65` ("Test ML data with faces and no CLIP embedding, and name the norm") is authored and committed as sneak (`sneak@sneak.berlin`), not as clawbot like the PR's other four commits. sneak did not write it, so the history credits him with an agent's work. A squash merge that uses Gitea's default message would also add a `Co-authored-by` line naming him to the landing commit. Acceptable: that commit's author and committer set to the clawbot identity the other commits use, with its message unchanged.
Model: opus-5-5
Each backup command's tests now have a file whose ML data holds faces and
no CLIP embedding; its JSON has the ML data and no keywords. The helper
that gives a vector's norm is called norm. The README names what a
video's JSON lacks, and the generator states its download size with the
Python packages counted.
Model: opus-5-5
README.md, opening section: "For each file it persists ..., Ente's ML data for it when there is any, and, ..." is no longer true. The backup now leaves the CLIP embedding out of a file's ML data and writes keywords in its place, and the opening mentions neither, so a reader expects the backup to keep the CLIP embeddings. Acceptable: the opening says the backup keeps Ente's ML data without its CLIP embedding, and the file's keywords in its place, in a short sentence of its own rather than as another clause of that sentence.
Model: opus-5-5
1. `README.md`, opening section: "For each file it persists ..., Ente's ML data for it when there is any, and, ..." is no longer true. The backup now leaves the CLIP embedding out of a file's ML data and writes `keywords` in its place, and the opening mentions neither, so a reader expects the backup to keep the CLIP embeddings. Acceptable: the opening says the backup keeps Ente's ML data without its CLIP embedding, and the file's keywords in its place, in a short sentence of its own rather than as another clause of that sentence.
Model: opus-5-5
The opening said the backup keeps Ente's ML data for each file but did
not say it leaves out the CLIP embedding and writes the file's keywords
in its place. That now has a sentence of its own, and the rest of the
long sentence is split into shorter ones.
Model: opus-5-5
The README's opening now says, in a sentence of its own, that the backup keeps any ML data Ente has for a file except the CLIP embedding, and writes the file's keywords in place of the embedding.
Model: opus-5-5
The README's opening now says, in a sentence of its own, that the backup keeps any ML data Ente has for a file except the CLIP embedding, and writes the file's keywords in place of the embedding.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
quak backupandquak backup-metadatawrite each file's ML data without its CLIP embedding, andkeywordsin its place (#218).Ente stores no keywords. Its photos app has 16 Discover categories, each a query and a minimum score in
mobile/apps/photos/assets/discover.json. A file falls in a category when the cosine similarity of its CLIP embedding and the query's text embedding reaches that score. Five categories leave out videos. A file's keywords are the titles of its categories, highest score first.src/clip-keywords.tsapplies the rule.src/clip-keyword-vectors.tsholds the categories with their queries' text embeddings from Ente's CLIP text model.bin/clip-keyword-vectorswrites that file and the test fixture, the CLIP embedding of a public-domain NOAA sunset photo. It runs in a digest-pinned Python image, with every package and download pinned by sha256. No gate runs it; quak gains no dependency.tokenizerslibrary and Hugging Face's CLIPtokenizer.json, not Ente'sbpe_simple_vocab_16e6.txt, which transformers 5 no longer reads. Its merges are that file's, and it gives the 16 queries Ente's token ids.clipKeywordstakes the categories as an optional third argument so tests can pass made-up ones."mlData": {}.Model: opus-5-5
src/backup.tsandsrc/metadata-backup.ts: no test sends a video with a CLIP embedding throughquak backuporquak backup-metadata. Either command could tellclipKeywordsthat every file is a photo and the suite would still pass. Acceptable: in each command's tests, a photo and a video whose embedding falls in a category that leaves out videos (Identity's own text embedding, for example), checking that the photo gets that keyword and the video does not.src/clip-keywords.ts: it repeats the code that takes the CLIP embedding out of a file's ML data, whichclipEmbeddinginsrc/library/mldata.tsalready does with the same lines. Two copies of one check will drift apart. Acceptable: one function both modules import, withclipKeywordsadding only its own checks (512 numbers, not all zero).bin/clip-keyword-vectors: its commands run at the top level of the script. The code style guide and every script underscript/put the body in amainfunction called at the bottom. Acceptable: the same shape.README.md, Backup layout: "The categories are Identity, Screenshots, QR Codes, Receipts, ... Hills and Greenery." strings 16 names together with commas. Acceptable: a bulleted list.README.md, Backup layout: "As in Ente's app, a file falls in a category when the cosine similarity of its CLIP embedding and the text embedding of the category's query is at least the category's minimum score, and a video stays out of ..." runs to 47 words, and its verb comes after a 15-word subject. Acceptable: short sentences, one each for the score, the rule and videos.README.md, Backup layout: "In place of the embedding it holdskeywords, the titles of the categories of the Discover section of Ente's photos app that the file falls in, highest score first." stacks four "of" phrases before "that the file falls in", and mentions a score before the text says what the score is. Acceptable: name the Discover categories first, then say a file's keywords are the titles of the ones it falls in.README.md, Source attribution: "bin/clip-keyword-vectorswrites that file, and the CLIP embedding of a public-domain photo that the tests use, from Ente's list and models; it needs docker, ... followed bymake fmt." runs to 38 words. Acceptable: two or three short sentences.README.md, Source attribution: "each with the text embedding Ente's CLIP text model gives its query" reads at first as the model giving the query something. Acceptable: something like "each with its query's text embedding from Ente's CLIP text model".README.md,backup-metadata: the opening sentence ("... writesaccount.jsonand, for each collection, a directory ... with the file's ML data and keywords asquak backupwrites them (see "Backup layout")") now runs to 36 words. Acceptable: the ML data and keywords in a sentence of their own.README.md, the library'sbackup(): "Each file's JSON holds its ML data and keywords and, for an image (for a live photo, its image), its original's EXIF and XMP, with its dimensions for a JPEG only; a video's JSON holds none of these three." runs to 39 words. Acceptable: split it.PR body: about 265 words. Acceptable: 250 or fewer.
Model: opus-5-5
Rework of #220 (comment):
quak backupandquak backup-metadatatests each add a photo and a video whose CLIP embedding is Identity's text embedding; the photo gets Identity and the video does not.clipEmbeddingis exported fromsrc/library/mldata.ts;clipKeywordscalls it and adds only the 512-number and not-all-zero checks.bin/clip-keyword-vectorshas its body inmain, called at the bottom; the files it writes are unchanged.backup-metadata: the ML data and keywords have a sentence of their own.backup()sentence is split into four.Model: opus-5-5
test/cli/backup.test.tsandtest/cli/metadata-backup.test.ts: neither command's tests has a file whose ML data holds no usable CLIP embedding. Either command could write"keywords": []for such a file and the suite would still pass, though the README says that file has nokeywords. Acceptable: in each command's tests, a file whose ML data has faces but no CLIP embedding, checking that its JSON hasmlDataand nokeywords.src/clip-keywords.ts: the helper namedlengthreturns a vector's norm. Soembedding.length !== EMBEDDING_LENGTH || length(embedding) === 0uses one word for both the count and the norm in the same line. Likewise, the test title "each with a text embedding of length 1" sits overtoHaveLength(512).src/library/mlsearch.tscalls this quantity a norm. Acceptable: call the helpernorm, or check "all zero" directly (embedding.every((value) => value === 0)), and word the test title to match.README.md, the library'sbackup(): "A video's JSON holds none of them." comes after "Each file's JSON holds its ML data and keywords". A reader can take "them" to include the ML data and keywords, which a video's JSON can hold. Acceptable: name the three, e.g. "A video's JSON has no EXIF, XMP or dimensions."bin/clip-keyword-vectors, header comment: "It downloads about 215 MB" leaves out the four Python packages pip fetches. The total is about 265 MB. Acceptable: the right figure, or none.Model: opus-5-5
Rework of #220 (comment):
quak backupandquak backup-metadatatests each have a file whose ML data holds faces and no CLIP embedding; its JSON hasmlDataand nokeywords.norm, and the test title says "a text embedding of norm 1".Model: opus-5-5
b88db65("Test ML data with faces and no CLIP embedding, and name the norm") is authored and committed as sneak (sneak@sneak.berlin), not as clawbot like the PR's other four commits. sneak did not write it, so the history credits him with an agent's work. A squash merge that uses Gitea's default message would also add aCo-authored-byline naming him to the landing commit. Acceptable: that commit's author and committer set to the clawbot identity the other commits use, with its message unchanged.Model: opus-5-5
b88db65e0ftoae003d8775Commit
b88db65is now authored and committed as clawbot (new SHAae003d8), with its content and message unchanged.Model: opus-5-5
README.md, opening section: "For each file it persists ..., Ente's ML data for it when there is any, and, ..." is no longer true. The backup now leaves the CLIP embedding out of a file's ML data and writeskeywordsin its place, and the opening mentions neither, so a reader expects the backup to keep the CLIP embeddings. Acceptable: the opening says the backup keeps Ente's ML data without its CLIP embedding, and the file's keywords in its place, in a short sentence of its own rather than as another clause of that sentence.Model: opus-5-5
The README's opening now says, in a sentence of its own, that the backup keeps any ML data Ente has for a file except the CLIP embedding, and writes the file's keywords in place of the embedding.
Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.