From 9454e8fc5cf6120e134f1b22e1abf75d5a9a0659 Mon Sep 17 00:00:00 2001
From: Remi  PLANEL <rplanel@pasteur.fr>
Date: Thu, 11 Apr 2024 19:41:29 +0200
Subject: [PATCH] update

---
 components/OperonStructure.vue                |  175 ++
 components/content/ArticleStructure.vue       |   41 +-
 .../{pdockqMatrix.vue => PdockqMatrix.vue}    |   26 +-
 components/content/StructureDb.vue            |   16 +-
 components/content/SystemOperonStructure.vue  |   42 +-
 content/3.defense-systems/avs.md              |   11 -
 content/3.defense-systems/brex.md             |    6 +
 data/all_predictions_statistics.csv           |    3 -
 data/all_predictions_statistics.tsv           |    3 +
 data/all_predictions_statistics_clean.csv     |    3 -
 package-lock.json                             | 1421 +++++++++++++++--
 .../df-wiki-cli/df_wiki_cli/content/main.py   |   51 +-
 .../df_wiki_cli/meilisearch/__init__.py       |  427 -----
 .../df_wiki_cli/meilisearch/main.py           |   45 -
 .../df_wiki_cli/meilisearch/update/main.py    |   56 +-
 packages/df-wiki-cli/poetry.lock              |  173 +-
 packages/df-wiki-cli/pyproject.toml           |    1 +
 .../scripts/fill-local-meilisearch.sh         |    6 +-
 types/plot.ts                                 |   15 +
 19 files changed, 1777 insertions(+), 744 deletions(-)
 create mode 100644 components/OperonStructure.vue
 rename components/content/{pdockqMatrix.vue => PdockqMatrix.vue} (84%)
 delete mode 100644 data/all_predictions_statistics.csv
 create mode 100644 data/all_predictions_statistics.tsv
 delete mode 100644 data/all_predictions_statistics_clean.csv
 create mode 100644 types/plot.ts

diff --git a/components/OperonStructure.vue b/components/OperonStructure.vue
new file mode 100644
index 00000000..ca56049b
--- /dev/null
+++ b/components/OperonStructure.vue
@@ -0,0 +1,175 @@
+<script setup lang="ts">
+import { useElementSize } from '@vueuse/core'
+import * as d3 from "d3";
+import type { PlotMargin, StructureOperonGene } from "../types/plot";
+
+
+
+interface Props {
+    genes: StructureOperonGene[] | null
+}
+
+const props = withDefaults(defineProps<Props>(), {
+    genes: null
+});
+const height = ref<number>(75)
+const svgRef = ref(null)
+
+
+
+const margin = ref<PlotMargin>({
+    marginTop: 10,
+    marginRight: 0,
+    marginBottom: 20,
+    marginLeft: 0,
+})
+
+const plotHeight = computed(() => {
+    const { marginTop, marginBottom } = toValue(margin)
+    return toValue(height) + marginTop + marginBottom
+})
+
+const xScale = ref(
+    d3.scaleBand().paddingInner(0.1)
+);
+const yScale = ref(d3.scaleBand()
+    .domain(['1'])
+    .range([toValue(margin).marginTop, toValue(height)]));
+const gbContainer = ref(null)
+const gbContainerWidth = ref(useElementSize(gbContainer, { width: 500, height: 0 }, { box: 'border-box' }).width)
+
+const computedWidth = computed(() => {
+    return gbContainerWidth.value
+})
+
+
+
+onMounted(() => {
+    draw()
+})
+
+const computedGenes = computed(() => {
+    const genes = toValue(props.genes)
+    if (genes !== null) {
+        return genes.map(d => {
+            return {
+                ...d,
+            }
+        })
+    }
+    else { return [] }
+})
+watch(computedGenes, () => {
+    const genes = toValue(computedGenes)
+    console.log(genes)
+    const domain = genes?.map(d => { return d.gene })
+    console.log(domain)
+    xScale.value.domain(domain)
+        .range([0, computedWidth.value])
+})
+
+
+
+const genesWithCoord = computed(() => {
+    const genes = toValue(computedGenes)
+    const xScaleVal = toValue(xScale)
+    const yScaleVal = toValue(yScale)
+    if (genes !== null) {
+        return genes.map(d => {
+            return {
+                ...d,
+                width: xScaleVal.bandwidth(),
+                x: xScaleVal(d.gene),
+                y: yScaleVal('1'),
+                height: yScaleVal.bandwidth()
+            }
+        })
+    }
+    else { return [] }
+})
+
+watch(genesWithCoord, () => {
+    draw()
+})
+
+function createOrSelect(container, tag, selectionClass) {
+    let selection = container.select(`${tag}.${selectionClass}`)
+    if (selection.empty()) {
+        selection = container.append(tag).classed(selectionClass, true)
+    }
+    return selection
+}
+
+function draw() {
+    const svg = d3.select(svgRef.value);
+    const { marginLeft, marginRight } = toValue(margin)
+    const xAxis = d3.axisBottom(xScale.value)
+    const gx = createOrSelect(svg, 'g', 'x-axis')
+
+    gx
+        .attr("transform", `translate(${marginLeft},${toValue(height)})`)
+        .call(xAxis)
+
+    const gxTitle = createOrSelect(gx, "text", "x-axis-title")
+    gxTitle.attr("text-anchor", "end")
+        .attr("fill", "currentColor")
+
+    gxTitle
+        .attr("x", computedWidth.value - marginLeft - marginRight)
+        .attr("y", - 10)
+
+
+    let gGenes = createOrSelect(svg, "g", "genes")
+    gGenes.call(drawGenes, xScale, yScale)
+}
+
+function drawGenes(genesGroup) {
+    console.log(toValue(genesWithCoord))
+    const genesSelection = genesGroup
+        .selectAll("g.gene") // get all "existing" lines in svg
+        .data(toValue(genesWithCoord)) // sync them with our data
+        .join(
+            enter => {
+                const g = enter.append("g")
+                    .classed("gene", true);
+                g.append("path").classed("gene", true)
+                g.append("text")
+                    // .attr("fill", "white")
+                    .classed("gene-label", true)
+                    .attr("fill", "currentColor")
+                    .attr("dominant-baseline", "middle")
+                g.append("title")
+                return g
+            },
+            update => update,
+            exit => exit.remove()
+        )
+    genesSelection.attr("transform", d => `translate(${d.x},${d.y})`)
+    genesSelection.select("path.gene")
+        .attr("d", d => drawGene(d).toString())
+}
+
+function drawGene({ width, height }) {
+    console.log("dans le draw gene")
+    console.log(width)
+    console.log(height)
+    const context = d3.path()
+    context.moveTo(0, 0)
+    context.lineTo(width, 0)
+    context.lineTo(width, height)
+    context.lineTo(0, height)
+    context.closePath()
+
+    return context
+}
+
+</script>
+<template>
+    <div ref="gbContainer">
+        <v-card flat color="transparent">
+            <svg ref="svgRef" :width="computedWidth" :height="plotHeight">
+                <g class="x-axis" />
+            </svg>
+        </v-card>
+    </div>
+</template>
\ No newline at end of file
diff --git a/components/content/ArticleStructure.vue b/components/content/ArticleStructure.vue
index 76dabe6d..8fb4be2f 100644
--- a/components/content/ArticleStructure.vue
+++ b/components/content/ArticleStructure.vue
@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { toValue } from '@vueuse/core';
 // import MolstarPdbePlugin from './MolstarPdbePlugin.vue';
+import * as d3 from "d3";
+import SystemOperonStructure from './SystemOperonStructure.vue';
 
 
 const { page } = useContent();
@@ -14,9 +16,9 @@ const stuctureUrls = ref<string[] | undefined>(undefined)
 const headers = ref<Record<string, any>[]>([
     { title: 'Structure', key: 'structure', sortable: false, removable: false, fixed: true, minWidth: "110px" },
 
-    { title: " System", key: "System" },
+    { title: "System", key: "system" },
     { title: "Gene name", key: "gene_name", removable: false },
-    { title: "Subtype", key: "subtype", removable: false },
+    { title: "Subtype", key: "subsystem", removable: false },
     { title: "Proteins in structure", key: 'proteins_in_the_prediction', sortable: false, removable: true },
     { title: "System genes", key: "system_genes", sortable: false, removable: true },
     { title: "Prediction type", key: "prediction_type", removable: true },
@@ -27,7 +29,7 @@ const headers = ref<Record<string, any>[]>([
 ])
 const groupBy = ref([
     {
-        key: 'subtype',
+        key: 'subsystem',
         order: 'asc',
     },
 ])
@@ -53,7 +55,7 @@ function pdbNameToCif(pdbPath: string) {
 }
 function displayStructure(item) {
     stuctureUrls.value = item.structuresUrls
-    structureTitle.value = `${item.subtype} - ${item.gene_name}`
+    structureTitle.value = `${item.subsystem} - ${item.gene_name}`
 
 }
 
@@ -64,7 +66,7 @@ const sanitizedStructures = computed(() => {
 
         return toValStructures.hits.map(item => {
             return {
-                ...item, structuresUrls: [`/${item.System_name_ok}/${pdbNameToCif(item.pdb)}`, `/${item.System_name_ok}/${item.pdb}`]
+                ...item, structuresUrls: [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
                     .map(url => {
                         return toValue(useRefinedUrl(url).refinedUrl)
                     })
@@ -72,20 +74,28 @@ const sanitizedStructures = computed(() => {
         })
     }
     return []
-
 })
 
 
+const perSubsystemStructures = computed(() => {
+    const toValStructures = toValue(sanitizedStructures)
+    if (toValStructures?.length > 0) {
+        return d3.groups(toValStructures, d => d.subsystem)
+    }
+    else { return [] }
+})
+
 // ==================================================================================
 // ASYNC PART 
 
 async function fetchStructures() {
 
 
+
     try {
         const d = await client.index(toValue(msIndexName)).search("", {
             facets: ["*"],
-            filter: [`System='${toValue(computedSystem)}'`, "completed='true'"],
+            filter: [`system='${toValue(computedSystem)}'`, "completed='true'"],
         })
         structures.value = d
     } catch (error) {
@@ -96,14 +106,27 @@ async function fetchStructures() {
 </script>
 <template>
     <v-card flat>
+        <template v-for="[subsystem, structures] in perSubsystemStructures" :key="subsystem[0]">
+            <ProseH3>{{ computedSystem }} - {{ subsystem }}</ProseH3>
+            <v-row align="end">
+                <v-col cols="6">
+                    <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" />
+                </v-col>
+                <v-col cols="6">
+                    <PdockqMatrix :subsystem="subsystem" />
+                </v-col>
+            </v-row>
+        </template>
+
+        <ProseH3>Summary</ProseH3>
         <v-data-table :headers="headers" :items="sanitizedStructures" :group-by="groupBy">
             <template #[`item.proteins_in_the_prediction`]="{ item }">
                 <CollapsibleChips
-                    :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.System_name_ok, item.fasta_file)">
+                    :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.system, item.fasta_file)">
                 </CollapsibleChips>
             </template>
             <template #[`item.system_genes`]="{ item }">
-                <CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.System_name_ok)">
+                <CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.system)">
                 </CollapsibleChips>
 
             </template>
diff --git a/components/content/pdockqMatrix.vue b/components/content/PdockqMatrix.vue
similarity index 84%
rename from components/content/pdockqMatrix.vue
rename to components/content/PdockqMatrix.vue
index a76433c7..c03dce96 100644
--- a/components/content/pdockqMatrix.vue
+++ b/components/content/PdockqMatrix.vue
@@ -7,10 +7,12 @@ import type { SearchResponse } from 'meilisearch'
 import { useRefinedUrl } from "@/composables/useRefinedUrl"
 import { useDisplay } from "vuetify";
 
-
-
+interface Props {
+    subsystem: string
+}
+const { subsystem } = withDefaults(defineProps<Props>(), {
+});
 const { mobile } = useDisplay();
-
 const { page } = useContent();
 const dbName = "structure"
 const filterBase = ref<string[]>([
@@ -19,6 +21,7 @@ const filterBase = ref<string[]>([
 ])
 const structureTitle = ref("Structure")
 const structureUrls = ref<string[] | undefined>(undefined)
+const matrixElemSize = ref(50)
 
 export interface PlotMargin {
     marginTop: number,
@@ -42,7 +45,7 @@ onMounted(async () => {
         const d = await client.index(toValue(dbName)).search("", {
             facets: ["*"],
             filter: [
-                `System='${toValue(computedSystem)}'`,
+                `system='${toValue(computedSystem)}' AND subsystem='${subsystem}'`,
                 ...toValue(filterBase)
             ],
         })
@@ -70,7 +73,7 @@ const groupedPdocks = computed(() => {
         }
     }
     if (toValData?.hits) {
-        return d3.groups(toValData.hits.flatMap(({ System_name_ok, pDockQ, pdb, nb_sys, proteins_in_the_prediction, system_genes }) => {
+        return d3.groups(toValData.hits.flatMap(({ system, pDockQ, pdb, nb_sys, proteins_in_the_prediction, system_genes }) => {
             if (proteins_in_the_prediction.length === 2) {
                 const sanitizedSystemGenes = system_genes.map(getSeqName)
                 const sanitizedProteins = proteins_in_the_prediction.map(getSeqName)
@@ -79,15 +82,15 @@ const groupedPdocks = computed(() => {
                 if (setProteins.size === 2) {
                     return sanitizedProteins.map((prot, i) => {
                         if (i === 0) {
-                            return { key, System_name_ok, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: prot, protY: sanitizedProteins[i + 1] }
+                            return { key, system, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: prot, protY: sanitizedProteins[i + 1] }
                         }
                         else {
-                            return { key, System_name_ok, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: prot, protY: sanitizedProteins[i - 1] }
+                            return { key, system, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: prot, protY: sanitizedProteins[i - 1] }
                         }
 
                     })
                 } else {
-                    return { key, System_name_ok, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: sanitizedProteins[0], protY: sanitizedProteins[1] }
+                    return { key, system, system_genes: sanitizedSystemGenes, pDockQ, pdb, nb_sys, proteins_in_the_prediction: sanitizedProteins, protX: sanitizedProteins[0], protY: sanitizedProteins[1] }
                 }
             } else {
                 throw createError(`More than 2 proteins in a dimer structure for system ${computedSystem.value} !`)
@@ -102,11 +105,12 @@ const computedPDocksMatrixPlotOptions = computed(() => {
     const range = domain.map(d => {
         return d3.interpolatePlasma(scaleLinear(d))
     })
+    const matrixElemSizeVal = toValue(matrixElemSize)
     const { marginBottom, marginLeft, marginRight, marginTop } = toValue(margin)
     return toValue(groupedPdocks).map((matrix) => {
         return {
-            width: matrix[1][0].system_genes.length * 75 + marginLeft + marginRight,
-            height: matrix[1][0].system_genes.length * 75 + marginTop + marginBottom,
+            width: matrix[1][0].system_genes.length * matrixElemSizeVal + marginLeft + marginRight,
+            height: matrix[1][0].system_genes.length * matrixElemSizeVal + marginTop + marginBottom,
             title: matrix[0],
             padding: 0,
             marginTop,
@@ -175,7 +179,7 @@ function pdbNameToCif(pdbPath: string) {
     return `${cifPath}.cif`
 }
 function buildStructureUrl(item) {
-    return [`/${item.System_name_ok}/${pdbNameToCif(item.pdb)}`, `/${item.System_name_ok}/${item.pdb}`]
+    return [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
 }
 
 function displayStructure(item) {
diff --git a/components/content/StructureDb.vue b/components/content/StructureDb.vue
index 28232eb7..86b68c47 100644
--- a/components/content/StructureDb.vue
+++ b/components/content/StructureDb.vue
@@ -8,7 +8,7 @@ import { withQuery, joinURL, withTrailingSlash } from 'ufo'
 
 interface Item {
     Foldseek_name: string
-    System_name_ok: string
+    system: string
 }
 
 
@@ -132,7 +132,7 @@ const dataTableServerProps = computed(() => {
 
 
 function toFolseekUrl(item: Item) {
-    const url = joinURL("/" + item.System_name_ok, item.Foldseek_name)
+    const url = joinURL("/" + item.system, item.Foldseek_name)
     const { refinedUrl } = useRefinedUrl(url)
     return toValue(refinedUrl)
 }
@@ -153,7 +153,7 @@ function pdbNameToCif(pdbPath: string) {
 
 
 function buildStructureUrl(item) {
-    return [`/${item.System_name_ok}/${pdbNameToCif(item.pdb)}`, `/${item.System_name_ok}/${item.pdb}`]
+    return [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
 }
 
 function displayStructure(item) {
@@ -237,11 +237,11 @@ const { refinedUrl: downloadAllCifUrl } = useRefinedUrl("/df-all-cifs.tar.gz")
             </template>
             <template #[`item.proteins_in_the_prediction`]="{ item }">
                 <CollapsibleChips
-                    :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.System_name_ok, item.fasta_file)">
+                    :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.system, item.fasta_file)">
                 </CollapsibleChips>
             </template>
             <template #[`item.system_genes`]="{ item }">
-                <CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.System_name_ok)">
+                <CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.system)">
                 </CollapsibleChips>
             </template>
             <template #[`item.structure`]="{ item }">
@@ -265,12 +265,6 @@ const { refinedUrl: downloadAllCifUrl } = useRefinedUrl("/df-all-cifs.tar.gz")
                         </v-menu>
                     </v-col>
                 </v-row>
-                <!-- <v-row no-gutters align="center">
-                    <MolstarPdbePlugin v-if="item?.pdb && item.pdb !== 'na'"
-                        :data-urls="[`/${item.System_name_ok}/${pdbNameToCif(item.pdb)}`, `/${item.System_name_ok}/${item.pdb}`]"
-                        uniq format="cif">
-                    </MolstarPdbePlugin>
-                </v-row> -->
             </template>
             <template #[`item.completed`]="{ item }">
                 <v-icon v-if="item.completed" color="success" icon="md:check"></v-icon>
diff --git a/components/content/SystemOperonStructure.vue b/components/content/SystemOperonStructure.vue
index bfa0430b..2a5004a4 100644
--- a/components/content/SystemOperonStructure.vue
+++ b/components/content/SystemOperonStructure.vue
@@ -1,28 +1,27 @@
 <script setup lang="ts">
-import * as d3 from "d3";
+import type { StructureOperonGene } from "../../types/plot";
+import type { SearchResponse } from "meilisearch";
 
+
+interface Props {
+    system: string
+    subsystem: string
+}
+
+
+const { system, subsystem } = withDefaults(defineProps<Props>(), {
+});
 const client = useMeiliSearchRef()
-const { page } = useContent();
 
 const pending = ref<boolean>(false)
 const msIndexName = ref<'systemoperonstruct'>("systemoperonstruct")
-const msResponse = ref()
-const systemName = computed(() => {
-    const toValPage = toValue(page)
-    return toValPage?.system ?? toValPage?.title ?? undefined
-})
+const msResponse = ref<SearchResponse<StructureOperonGene> | undefined>(undefined)
 
 
-const systemGroupedBySubtype = computed(() => {
-    return d3.groups(sanitizedHits.value, d => d.subsystem)
-})
-
 const sanitizedHits = computed(() => {
     const toValMsResponse = toValue(msResponse)
-    console.log(toValMsResponse)
-    if (toValMsResponse?.hits?.length > 0) {
+    if (toValMsResponse && toValMsResponse?.hits?.length > 0) {
         return toValMsResponse.hits.map(hit => {
-            console.log(hit)
             return { ...hit, gene: hit.gene.split("__")[1] }
         })
     } else {
@@ -38,32 +37,25 @@ onMounted(() => {
 async function fetchOperonStructure() {
     try {
         pending.value = true
-        const data = await client.index(toValue(msIndexName)).search("", {
+        const data = await client.index(toValue(msIndexName)).search<StructureOperonGene>("", {
             facets: ["*"],
-            filter: [`system = '${toValue(systemName)}'`],
+            filter: [`system='${system}' AND subsystem='${subsystem}'`],
             limit: 500000,
         })
         msResponse.value = data
 
     } catch (error) {
-        throw createError(`Cannot get hits on refseq for system: ${toValue(systemName)} `)
+        throw createError(`Cannot get hits on refseq for system: ${system} `)
     } finally {
         pending.value = false
     }
 }
-
-
-
-
 </script>
 
 <template>
 
     <v-card flat>
-        <v-list-item v-for="s in sanitizedHits" :key="s.id">
-            {{ s.gene }} - {{ s.subsystem }}
-        </v-list-item>
-        <pre>{{ systemGroupedBySubtype }}</pre>
+        <OperonStructure :genes="sanitizedHits" />
     </v-card>
 
 </template>
\ No newline at end of file
diff --git a/content/3.defense-systems/avs.md b/content/3.defense-systems/avs.md
index 479c6527..f18121d5 100644
--- a/content/3.defense-systems/avs.md
+++ b/content/3.defense-systems/avs.md
@@ -79,17 +79,6 @@ Proportion of genome encoding the Avs system for the 14 phyla with more than 50
 
 ## Structure
 
-::system-operon-structure
-::
-
-
-### matrix
-
-::pdockq-matrix
-::
-
-### List
-
 ::article-structure
 ::
 
diff --git a/content/3.defense-systems/brex.md b/content/3.defense-systems/brex.md
index 4b7c3d7d..015028c9 100644
--- a/content/3.defense-systems/brex.md
+++ b/content/3.defense-systems/brex.md
@@ -86,6 +86,12 @@ Proportion of genome encoding the BREX system for the 14 phyla with more than 50
 
 
 ## Structure
+
+
+::article-structure
+::
+
+
 ### BREX_II
 ##### Example 1
 
diff --git a/data/all_predictions_statistics.csv b/data/all_predictions_statistics.csv
deleted file mode 100644
index 7403c25d..00000000
--- a/data/all_predictions_statistics.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e94119ef1fa73e1b0d2da1e070f27d1576901f53504e7b4c0f6aa2532762dc1b
-size 556202
diff --git a/data/all_predictions_statistics.tsv b/data/all_predictions_statistics.tsv
new file mode 100644
index 00000000..08320223
--- /dev/null
+++ b/data/all_predictions_statistics.tsv
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eade182c1e7173dfe7f63bfce768d38a050377d221bc91d08c1c2371a5f3a3d4
+size 547149
diff --git a/data/all_predictions_statistics_clean.csv b/data/all_predictions_statistics_clean.csv
deleted file mode 100644
index 8ade3e63..00000000
--- a/data/all_predictions_statistics_clean.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:09bae9951cf2a86917f3a65ebd9bc2eb1e37c7fd342104b4442076aacb68a133
-size 593832
diff --git a/package-lock.json b/package-lock.json
index 1808619b..88c9197d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
         "yaml": "^2.3.3"
       },
       "devDependencies": {
+        "@antfu/eslint-config": "^2.12.2",
         "@iconify-json/game-icons": "^1.1.7",
         "@iconify-json/gravity-ui": "^1.1.1",
         "@iconify-json/material-symbols": "^1.1.69",
@@ -29,11 +30,13 @@
         "@nuxtjs/seo": "^2.0.0-rc.9",
         "@types/d3": "^7.4.3",
         "@types/node": "^20.11.0",
+        "@unocss/eslint-plugin": "^0.59.0",
         "@unocss/nuxt": "^0.58.3",
         "@unocss/preset-icons": "^0.58.3",
         "@vueuse/core": "^10.7.1",
         "@vueuse/nuxt": "^10.7.1",
         "eslint": "^8.57.0",
+        "eslint-plugin-format": "^0.1.0",
         "nuxt": "^3.11.1",
         "nuxt-meilisearch": "^1.1.0",
         "vuetify-nuxt-module": "^0.9.0"
@@ -245,6 +248,174 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@antfu/eslint-config": {
+      "version": "2.13.3",
+      "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-2.13.3.tgz",
+      "integrity": "sha512-DCyrnFgWtIc0mUTn8HeVB15Z/t9oEQZk8ce6S14Kq6z42LbMfZxPu6hs4SmPFYWLJoEzYq87dxsRv3glOX+aGw==",
+      "dev": true,
+      "dependencies": {
+        "@antfu/install-pkg": "^0.3.2",
+        "@clack/prompts": "^0.7.0",
+        "@stylistic/eslint-plugin": "^1.7.0",
+        "@typescript-eslint/eslint-plugin": "^7.6.0",
+        "@typescript-eslint/parser": "^7.6.0",
+        "eslint-config-flat-gitignore": "^0.1.5",
+        "eslint-flat-config-utils": "^0.2.1",
+        "eslint-merge-processors": "^0.1.0",
+        "eslint-plugin-antfu": "^2.1.2",
+        "eslint-plugin-eslint-comments": "^3.2.0",
+        "eslint-plugin-import-x": "^0.5.0",
+        "eslint-plugin-jsdoc": "^48.2.3",
+        "eslint-plugin-jsonc": "^2.15.0",
+        "eslint-plugin-markdown": "^4.0.1",
+        "eslint-plugin-n": "^17.1.0",
+        "eslint-plugin-no-only-tests": "^3.1.0",
+        "eslint-plugin-perfectionist": "^2.8.0",
+        "eslint-plugin-toml": "^0.11.0",
+        "eslint-plugin-unicorn": "^52.0.0",
+        "eslint-plugin-unused-imports": "^3.1.0",
+        "eslint-plugin-vitest": "^0.5.1",
+        "eslint-plugin-vue": "^9.24.1",
+        "eslint-plugin-yml": "^1.14.0",
+        "eslint-processor-vue-blocks": "^0.1.1",
+        "globals": "^15.0.0",
+        "jsonc-eslint-parser": "^2.4.0",
+        "local-pkg": "^0.5.0",
+        "parse-gitignore": "^2.0.0",
+        "picocolors": "^1.0.0",
+        "toml-eslint-parser": "^0.9.3",
+        "vue-eslint-parser": "^9.4.2",
+        "yaml-eslint-parser": "^1.2.2",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "eslint-config": "bin/index.js"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@unocss/eslint-plugin": ">=0.50.0",
+        "astro-eslint-parser": "^0.16.3",
+        "eslint": ">=8.40.0",
+        "eslint-plugin-astro": "^0.31.4",
+        "eslint-plugin-format": ">=0.1.0",
+        "eslint-plugin-react": "^7.33.2",
+        "eslint-plugin-react-hooks": "^4.6.0",
+        "eslint-plugin-react-refresh": "^0.4.4",
+        "eslint-plugin-solid": "^0.13.2",
+        "eslint-plugin-svelte": ">=2.35.1",
+        "prettier-plugin-astro": "^0.13.0",
+        "prettier-plugin-slidev": "^1.0.5",
+        "svelte-eslint-parser": "^0.33.1"
+      },
+      "peerDependenciesMeta": {
+        "@unocss/eslint-plugin": {
+          "optional": true
+        },
+        "astro-eslint-parser": {
+          "optional": true
+        },
+        "eslint-plugin-astro": {
+          "optional": true
+        },
+        "eslint-plugin-format": {
+          "optional": true
+        },
+        "eslint-plugin-react": {
+          "optional": true
+        },
+        "eslint-plugin-react-hooks": {
+          "optional": true
+        },
+        "eslint-plugin-react-refresh": {
+          "optional": true
+        },
+        "eslint-plugin-solid": {
+          "optional": true
+        },
+        "eslint-plugin-svelte": {
+          "optional": true
+        },
+        "prettier-plugin-astro": {
+          "optional": true
+        },
+        "prettier-plugin-slidev": {
+          "optional": true
+        },
+        "svelte-eslint-parser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@antfu/eslint-config/node_modules/@antfu/install-pkg": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.3.2.tgz",
+      "integrity": "sha512-FFYqME8+UHlPnRlX/vn+8cTD4Wo/nG/lzRxpABs3XANBmdJdNImVz3QvjNAE/W3PSCNbG387FOz8o5WelnWOlg==",
+      "dev": true,
+      "dependencies": {
+        "execa": "^8.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@antfu/eslint-config/node_modules/execa": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+      "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^8.0.1",
+        "human-signals": "^5.0.0",
+        "is-stream": "^3.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^5.1.0",
+        "onetime": "^6.0.0",
+        "signal-exit": "^4.1.0",
+        "strip-final-newline": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=16.17"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/@antfu/eslint-config/node_modules/get-stream": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+      "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@antfu/eslint-config/node_modules/human-signals": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+      "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=16.17.0"
+      }
+    },
+    "node_modules/@antfu/eslint-config/node_modules/signal-exit": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/@antfu/install-pkg": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz",
@@ -1088,6 +1259,43 @@
       "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
       "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
     },
+    "node_modules/@clack/core": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
+      "integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
+      "dev": true,
+      "dependencies": {
+        "picocolors": "^1.0.0",
+        "sisteransi": "^1.0.5"
+      }
+    },
+    "node_modules/@clack/prompts": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
+      "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
+      "bundleDependencies": [
+        "is-unicode-supported"
+      ],
+      "dev": true,
+      "dependencies": {
+        "@clack/core": "^0.3.3",
+        "is-unicode-supported": "*",
+        "picocolors": "^1.0.0",
+        "sisteransi": "^1.0.5"
+      }
+    },
+    "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
+      "version": "1.3.0",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@cloudflare/kv-asset-handler": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz",
@@ -1392,6 +1600,24 @@
         "node": ">= 10"
       }
     },
+    "node_modules/@dprint/formatter": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@dprint/formatter/-/formatter-0.2.1.tgz",
+      "integrity": "sha512-GCzgRt2o4mhZLy8L47k2A+q9EMG/jWhzZebE29EqKsxmjDrSfv2VisEj/Q+39OOf04jTkEfB/TRO+IZSyxHdYg==",
+      "dev": true
+    },
+    "node_modules/@dprint/markdown": {
+      "version": "0.16.4",
+      "resolved": "https://registry.npmjs.org/@dprint/markdown/-/markdown-0.16.4.tgz",
+      "integrity": "sha512-WjsC4yLybR5/76+d/2s36nOBGjETe+jJR//ddFHohDXKdis+FTUv7dJ00kmd6g0AKQwDITayM1Nid10gFNG0Yg==",
+      "dev": true
+    },
+    "node_modules/@dprint/toml": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@dprint/toml/-/toml-0.5.4.tgz",
+      "integrity": "sha512-d+5GwwzztZD0QixmOBhaO6nWVLsAeYsJ1HJYNxDoDRbASFCpza9BBVshG5ctBRXCkkIHhD9BO1SnbOoRQltUQw==",
+      "dev": true
+    },
     "node_modules/@es-joy/jsdoccomment": {
       "version": "0.42.0",
       "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz",
@@ -3613,6 +3839,18 @@
         "node": ">=14"
       }
     },
+    "node_modules/@pkgr/core": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
+      "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/@polka/url": {
       "version": "1.0.0-next.25",
       "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
@@ -4060,6 +4298,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "android"
@@ -4072,6 +4311,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "android"
@@ -4084,6 +4324,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "darwin"
@@ -4096,6 +4337,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "darwin"
@@ -4108,6 +4350,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4120,6 +4363,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4132,6 +4376,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4144,6 +4389,7 @@
       "cpu": [
         "ppc64le"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4156,6 +4402,7 @@
       "cpu": [
         "riscv64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4168,6 +4415,7 @@
       "cpu": [
         "s390x"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4180,6 +4428,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4192,6 +4441,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -4204,6 +4454,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -4216,6 +4467,7 @@
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -4228,6 +4480,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -5174,22 +5427,22 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz",
-      "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz",
+      "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==",
       "dev": true,
       "dependencies": {
-        "@eslint-community/regexpp": "^4.5.1",
-        "@typescript-eslint/scope-manager": "7.5.0",
-        "@typescript-eslint/type-utils": "7.5.0",
-        "@typescript-eslint/utils": "7.5.0",
-        "@typescript-eslint/visitor-keys": "7.5.0",
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "7.6.0",
+        "@typescript-eslint/type-utils": "7.6.0",
+        "@typescript-eslint/utils": "7.6.0",
+        "@typescript-eslint/visitor-keys": "7.6.0",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
-        "ignore": "^5.2.4",
+        "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
-        "semver": "^7.5.4",
-        "ts-api-utils": "^1.0.1"
+        "semver": "^7.6.0",
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -5209,15 +5462,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz",
-      "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz",
+      "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.5.0",
-        "@typescript-eslint/types": "7.5.0",
-        "@typescript-eslint/typescript-estree": "7.5.0",
-        "@typescript-eslint/visitor-keys": "7.5.0",
+        "@typescript-eslint/scope-manager": "7.6.0",
+        "@typescript-eslint/types": "7.6.0",
+        "@typescript-eslint/typescript-estree": "7.6.0",
+        "@typescript-eslint/visitor-keys": "7.6.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -5236,15 +5489,41 @@
         }
       }
     },
+    "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz",
-      "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz",
+      "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.5.0",
-        "@typescript-eslint/visitor-keys": "7.5.0"
+        "@typescript-eslint/types": "7.6.0",
+        "@typescript-eslint/visitor-keys": "7.6.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
       },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
       },
@@ -5254,15 +5533,15 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz",
-      "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz",
+      "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.5.0",
-        "@typescript-eslint/utils": "7.5.0",
+        "@typescript-eslint/typescript-estree": "7.6.0",
+        "@typescript-eslint/utils": "7.6.0",
         "debug": "^4.3.4",
-        "ts-api-utils": "^1.0.1"
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -5294,19 +5573,19 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz",
-      "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz",
+      "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.5.0",
-        "@typescript-eslint/visitor-keys": "7.5.0",
+        "@typescript-eslint/types": "7.6.0",
+        "@typescript-eslint/visitor-keys": "7.6.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
-        "minimatch": "9.0.3",
-        "semver": "^7.5.4",
-        "ts-api-utils": "^1.0.1"
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -5321,6 +5600,19 @@
         }
       }
     },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
       "version": "11.1.0",
       "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
@@ -5341,21 +5633,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
-      "version": "9.0.3",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
-      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -5366,18 +5643,18 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz",
-      "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz",
+      "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@types/json-schema": "^7.0.12",
-        "@types/semver": "^7.5.0",
-        "@typescript-eslint/scope-manager": "7.5.0",
-        "@typescript-eslint/types": "7.5.0",
-        "@typescript-eslint/typescript-estree": "7.5.0",
-        "semver": "^7.5.4"
+        "@types/json-schema": "^7.0.15",
+        "@types/semver": "^7.5.8",
+        "@typescript-eslint/scope-manager": "7.6.0",
+        "@typescript-eslint/types": "7.6.0",
+        "@typescript-eslint/typescript-estree": "7.6.0",
+        "semver": "^7.6.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -5390,14 +5667,27 @@
         "eslint": "^8.56.0"
       }
     },
+    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz",
-      "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz",
+      "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.5.0",
-        "eslint-visitor-keys": "^3.4.1"
+        "@typescript-eslint/types": "7.6.0",
+        "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -5407,15 +5697,28 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/@ungap/structured-clone": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
-      "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
-      "dev": true
-    },
-    "node_modules/@unhead/addons": {
-      "version": "1.9.4",
-      "resolved": "https://registry.npmjs.org/@unhead/addons/-/addons-1.9.4.tgz",
+    "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@ungap/structured-clone": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+      "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+      "dev": true
+    },
+    "node_modules/@unhead/addons": {
+      "version": "1.9.4",
+      "resolved": "https://registry.npmjs.org/@unhead/addons/-/addons-1.9.4.tgz",
       "integrity": "sha512-LSvvOQPPHtGX6xh58iB4zfsR8/yDX7HR0eK42FIyeUaCDMuO+hXy1xGiJQM0V7V/eaZFXY1gYkfkmccfMP37Jw==",
       "dev": true,
       "dependencies": {
@@ -5594,6 +5897,50 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/@unocss/eslint-plugin": {
+      "version": "0.59.1",
+      "resolved": "https://registry.npmjs.org/@unocss/eslint-plugin/-/eslint-plugin-0.59.1.tgz",
+      "integrity": "sha512-Ms20aFUTXPLlMjfzxfiKGAIdkFUG6nR4vzxg+CULrIMj/APMpvI4ZRLlUO5Q/9a4RxfJ4kDDRiqLle39+7p3sg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/utils": "^7.6.0",
+        "@unocss/config": "0.59.1",
+        "@unocss/core": "0.59.1",
+        "magic-string": "^0.30.9",
+        "synckit": "^0.9.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@unocss/eslint-plugin/node_modules/@unocss/config": {
+      "version": "0.59.1",
+      "resolved": "https://registry.npmjs.org/@unocss/config/-/config-0.59.1.tgz",
+      "integrity": "sha512-VQhz6mScRlvgXCrlvEYMee1VkqTCcrEtJwW3r7KTQTEfxxNymg4TZKqqNL6dp7kqdZKLHQytQokSGuLIPRaAdQ==",
+      "dev": true,
+      "dependencies": {
+        "@unocss/core": "0.59.1",
+        "unconfig": "^0.3.12"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@unocss/eslint-plugin/node_modules/@unocss/core": {
+      "version": "0.59.1",
+      "resolved": "https://registry.npmjs.org/@unocss/core/-/core-0.59.1.tgz",
+      "integrity": "sha512-4T9eSmEhmxlYnz0xilIavROiEWvEcEtWixIQEaP4MqWabaRew6yN1ky4ALZZgaTUdS50EXGR3bYVz1vah9sfFw==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/@unocss/extractor-arbitrary-variants": {
       "version": "0.58.9",
       "resolved": "https://registry.npmjs.org/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-0.58.9.tgz",
@@ -9450,101 +9797,707 @@
         "eslint": "bin/eslint.js"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-compat-utils": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz",
+      "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==",
+      "dev": true,
+      "dependencies": {
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "eslint": ">=6.0.0"
+      }
+    },
+    "node_modules/eslint-config-flat-gitignore": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-0.1.5.tgz",
+      "integrity": "sha512-hEZLwuZjDBGDERA49c2q7vxc8sCGv8EdBp6PQYzGOMcHIgrfG9YOM6s/4jx24zhD+wnK9AI8mgN5RxSss5nClQ==",
+      "dev": true,
+      "dependencies": {
+        "find-up": "^7.0.0",
+        "parse-gitignore": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/eslint-flat-config-utils": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-0.2.1.tgz",
+      "integrity": "sha512-SKnSr4YdPD7xxynNpaad/IlJYfeDmtWvZ0UEmHEA0+eTOcZFPt1075KO87LIWN30jXGCREG2qcCqdAnRoCiAWQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/eslint": "^8.56.7",
+        "pathe": "^1.1.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/eslint-formatting-reporter": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-formatting-reporter/-/eslint-formatting-reporter-0.0.0.tgz",
+      "integrity": "sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==",
+      "dev": true,
+      "dependencies": {
+        "prettier-linter-helpers": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "eslint": ">=8.40.0"
+      }
+    },
+    "node_modules/eslint-import-resolver-node": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+      "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^3.2.7",
+        "is-core-module": "^2.13.0",
+        "resolve": "^1.22.4"
+      }
+    },
+    "node_modules/eslint-import-resolver-node/node_modules/debug": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
+    "node_modules/eslint-merge-processors": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-0.1.0.tgz",
+      "integrity": "sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "eslint": "*"
+      }
+    },
+    "node_modules/eslint-parser-plain": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-parser-plain/-/eslint-parser-plain-0.1.0.tgz",
+      "integrity": "sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==",
+      "dev": true
+    },
+    "node_modules/eslint-plugin-antfu": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-antfu/-/eslint-plugin-antfu-2.1.2.tgz",
+      "integrity": "sha512-s7ZTOM3uq0iqpp6gF0UEotnvup7f2PHBUftCytLZX0+6C9j9KadKZQh6bVVngAyFgsmeD9+gcBopOYLClb2oDg==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "eslint": "*"
+      }
+    },
+    "node_modules/eslint-plugin-es-x": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz",
+      "integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.1.2",
+        "@eslint-community/regexpp": "^4.6.0",
+        "eslint-compat-utils": "^0.5.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      },
+      "peerDependencies": {
+        "eslint": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-eslint-comments": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz",
+      "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==",
+      "dev": true,
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5",
+        "ignore": "^5.0.5"
+      },
+      "engines": {
+        "node": ">=6.5.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      },
+      "peerDependencies": {
+        "eslint": ">=4.19.1"
+      }
+    },
+    "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/eslint-plugin-format": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-format/-/eslint-plugin-format-0.1.0.tgz",
+      "integrity": "sha512-IgOu+GEH+PdKnpuPrFzY8q8QgnzAUijDZsNLhpp5jx0Lbu9u968/STcmEZGnIMVBw3zeTNN/FsU6d2Rdgcy6Aw==",
+      "dev": true,
+      "dependencies": {
+        "@dprint/formatter": "^0.2.0",
+        "@dprint/markdown": "^0.16.3",
+        "@dprint/toml": "^0.5.4",
+        "eslint-formatting-reporter": "^0.0.0",
+        "eslint-parser-plain": "^0.1.0",
+        "prettier": "^3.1.0",
+        "synckit": "^0.8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "eslint": "^8.40.0"
+      }
+    },
+    "node_modules/eslint-plugin-format/node_modules/synckit": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
+      "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
+      "dev": true,
+      "dependencies": {
+        "@pkgr/core": "^0.1.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
+    "node_modules/eslint-plugin-import-x": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-0.5.0.tgz",
+      "integrity": "sha512-C7R8Z4IzxmsoOPMtSzwuOBW5FH6iRlxHR6iTks+MzVlrk3r3TUxokkWTx3ypdj9nGOEP+CG/5e6ebZzHbxgbbQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/utils": "^7.4.0",
+        "debug": "^4.3.4",
+        "doctrine": "^3.0.0",
+        "eslint-import-resolver-node": "^0.3.9",
+        "get-tsconfig": "^4.7.3",
+        "is-glob": "^4.0.3",
+        "minimatch": "^9.0.3",
+        "semver": "^7.6.0"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "eslint": "^8.56.0 || ^9.0.0-0"
+      }
+    },
+    "node_modules/eslint-plugin-jsdoc": {
+      "version": "48.2.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.3.tgz",
+      "integrity": "sha512-r9DMAmFs66VNvNqRLLjHejdnJtILrt3xGi+Qx0op0oRfFGVpOR1Hb3BC++MacseHx93d8SKYPhyrC9BS7Os2QA==",
+      "dev": true,
+      "dependencies": {
+        "@es-joy/jsdoccomment": "~0.42.0",
+        "are-docs-informative": "^0.0.2",
+        "comment-parser": "1.4.1",
+        "debug": "^4.3.4",
+        "escape-string-regexp": "^4.0.0",
+        "esquery": "^1.5.0",
+        "is-builtin-module": "^3.2.1",
+        "semver": "^7.6.0",
+        "spdx-expression-parse": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-jsonc": {
+      "version": "2.15.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.15.0.tgz",
+      "integrity": "sha512-wAphMVgTQPAKAYV8d/QEkEYDg8uer9nMQ85N17IUiJcAWLxJs83/Exe59dEH9yKUpvpLf46H+wR7/U7lZ3/NpQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "eslint-compat-utils": "^0.5.0",
+        "espree": "^9.6.1",
+        "graphemer": "^1.4.0",
+        "jsonc-eslint-parser": "^2.0.4",
+        "natural-compare": "^1.4.0",
+        "synckit": "^0.6.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      },
+      "peerDependencies": {
+        "eslint": ">=6.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-jsonc/node_modules/synckit": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz",
+      "integrity": "sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=12.20"
+      }
+    },
+    "node_modules/eslint-plugin-markdown": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-4.0.1.tgz",
+      "integrity": "sha512-5/MnGvYU0i8MbHH5cg8S+Vl3DL+bqRNYshk1xUO86DilNBaxtTkhH+5FD0/yO03AmlI6+lfNFdk2yOw72EPzpA==",
+      "dev": true,
+      "dependencies": {
+        "mdast-util-from-markdown": "^0.8.5"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "peerDependencies": {
+        "eslint": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/@types/mdast": {
+      "version": "3.0.15",
+      "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+      "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/unist": "^2"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/@types/unist": {
+      "version": "2.0.10",
+      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
+      "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
+      "dev": true
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/character-entities": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+      "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/character-entities-legacy": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+      "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/character-reference-invalid": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+      "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/is-alphabetical": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+      "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/is-alphanumerical": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+      "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+      "dev": true,
+      "dependencies": {
+        "is-alphabetical": "^1.0.0",
+        "is-decimal": "^1.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/is-decimal": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+      "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/is-hexadecimal": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+      "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/mdast-util-from-markdown": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz",
+      "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/mdast": "^3.0.0",
+        "mdast-util-to-string": "^2.0.0",
+        "micromark": "~2.11.0",
+        "parse-entities": "^2.0.0",
+        "unist-util-stringify-position": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/mdast-util-to-string": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+      "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==",
+      "dev": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/micromark": {
+      "version": "2.11.4",
+      "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+      "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "dependencies": {
+        "debug": "^4.0.0",
+        "parse-entities": "^2.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/parse-entities": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+      "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+      "dev": true,
+      "dependencies": {
+        "character-entities": "^1.0.0",
+        "character-entities-legacy": "^1.0.0",
+        "character-reference-invalid": "^1.0.0",
+        "is-alphanumerical": "^1.0.0",
+        "is-decimal": "^1.0.0",
+        "is-hexadecimal": "^1.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/eslint-plugin-markdown/node_modules/unist-util-stringify-position": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+      "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+      "dev": true,
+      "dependencies": {
+        "@types/unist": "^2.0.2"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/eslint-plugin-n": {
+      "version": "17.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.2.0.tgz",
+      "integrity": "sha512-XBkFQxjTFKy9oc925ezHcDoZ8VLdGfxRkdZf0poR4TjC+zvm28pG2Tc7ZZpD1/UxSAzbw6Zz0WpnpUX3KruAAA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "enhanced-resolve": "^5.15.0",
+        "eslint-plugin-es-x": "^7.5.0",
+        "get-tsconfig": "^4.7.0",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.4",
+        "minimatch": "^9.0.0",
+        "semver": "^7.5.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      },
+      "peerDependencies": {
+        "eslint": ">=8.23.0"
+      }
+    },
+    "node_modules/eslint-plugin-n/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint-plugin-no-only-tests": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.1.0.tgz",
+      "integrity": "sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==",
+      "dev": true,
+      "engines": {
+        "node": ">=5.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-perfectionist": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.8.0.tgz",
+      "integrity": "sha512-XBjQ4ctU1rOzQ4bFJoUowe8XdsIIz42JqNrouFlae1TO78HjoyYBaRP8+gAHDDQCSdHY10pbChyzlJeBA6D51w==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/utils": "^6.13.0",
+        "minimatch": "^9.0.3",
+        "natural-compare-lite": "^1.4.0"
+      },
+      "peerDependencies": {
+        "astro-eslint-parser": "^0.16.0",
+        "eslint": ">=8.0.0",
+        "svelte": ">=3.0.0",
+        "svelte-eslint-parser": "^0.33.0",
+        "vue-eslint-parser": ">=9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "astro-eslint-parser": {
+          "optional": true
+        },
+        "svelte": {
+          "optional": true
+        },
+        "svelte-eslint-parser": {
+          "optional": true
+        },
+        "vue-eslint-parser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/scope-manager": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+      "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+      "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+      "dev": true,
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/typescript-estree": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+      "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "minimatch": "9.0.3",
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
-        "url": "https://opencollective.com/eslint"
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
       }
     },
-    "node_modules/eslint-config-flat-gitignore": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-0.1.5.tgz",
-      "integrity": "sha512-hEZLwuZjDBGDERA49c2q7vxc8sCGv8EdBp6PQYzGOMcHIgrfG9YOM6s/4jx24zhD+wnK9AI8mgN5RxSss5nClQ==",
+    "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/utils": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+      "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
       "dev": true,
       "dependencies": {
-        "find-up": "^7.0.0",
-        "parse-gitignore": "^2.0.0"
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@types/json-schema": "^7.0.12",
+        "@types/semver": "^7.5.0",
+        "@typescript-eslint/scope-manager": "6.21.0",
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/typescript-estree": "6.21.0",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
-        "url": "https://github.com/sponsors/antfu"
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
       }
     },
-    "node_modules/eslint-flat-config-utils": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-0.2.1.tgz",
-      "integrity": "sha512-SKnSr4YdPD7xxynNpaad/IlJYfeDmtWvZ0UEmHEA0+eTOcZFPt1075KO87LIWN30jXGCREG2qcCqdAnRoCiAWQ==",
+    "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+      "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
       "dev": true,
       "dependencies": {
-        "@types/eslint": "^8.56.7",
-        "pathe": "^1.1.2"
+        "@typescript-eslint/types": "6.21.0",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
-        "url": "https://github.com/sponsors/antfu"
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/eslint-import-resolver-node": {
-      "version": "0.3.9",
-      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
-      "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+    "node_modules/eslint-plugin-perfectionist/node_modules/globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
       "dev": true,
       "dependencies": {
-        "debug": "^3.2.7",
-        "is-core-module": "^2.13.0",
-        "resolve": "^1.22.4"
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/eslint-import-resolver-node/node_modules/debug": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+    "node_modules/eslint-plugin-perfectionist/node_modules/minimatch": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
       "dev": true,
       "dependencies": {
-        "ms": "^2.1.1"
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/eslint-plugin-import-x": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-0.5.0.tgz",
-      "integrity": "sha512-C7R8Z4IzxmsoOPMtSzwuOBW5FH6iRlxHR6iTks+MzVlrk3r3TUxokkWTx3ypdj9nGOEP+CG/5e6ebZzHbxgbbQ==",
+    "node_modules/eslint-plugin-perfectionist/node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/utils": "^7.4.0",
-        "debug": "^4.3.4",
-        "doctrine": "^3.0.0",
-        "eslint-import-resolver-node": "^0.3.9",
-        "get-tsconfig": "^4.7.3",
-        "is-glob": "^4.0.3",
-        "minimatch": "^9.0.3",
-        "semver": "^7.6.0"
-      },
       "engines": {
-        "node": ">=16"
-      },
-      "peerDependencies": {
-        "eslint": "^8.56.0 || ^9.0.0-0"
+        "node": ">=8"
       }
     },
-    "node_modules/eslint-plugin-jsdoc": {
-      "version": "48.2.3",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.3.tgz",
-      "integrity": "sha512-r9DMAmFs66VNvNqRLLjHejdnJtILrt3xGi+Qx0op0oRfFGVpOR1Hb3BC++MacseHx93d8SKYPhyrC9BS7Os2QA==",
+    "node_modules/eslint-plugin-toml": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.11.0.tgz",
+      "integrity": "sha512-sau+YvPU4fWTjB+qtBt3n8WS87aoDCs+BVbSUAemGaIsRNbvR9uEk+Tt892iLHTGvp/DPWYoCX4/8DoyAbB+sQ==",
       "dev": true,
       "dependencies": {
-        "@es-joy/jsdoccomment": "~0.42.0",
-        "are-docs-informative": "^0.0.2",
-        "comment-parser": "1.4.1",
-        "debug": "^4.3.4",
-        "escape-string-regexp": "^4.0.0",
-        "esquery": "^1.5.0",
-        "is-builtin-module": "^3.2.1",
-        "semver": "^7.6.0",
-        "spdx-expression-parse": "^4.0.0"
+        "debug": "^4.1.1",
+        "eslint-compat-utils": "^0.5.0",
+        "lodash": "^4.17.19",
+        "toml-eslint-parser": "^0.9.0"
       },
       "engines": {
-        "node": ">=18"
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
       },
       "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+        "eslint": ">=6.0.0"
       }
     },
     "node_modules/eslint-plugin-unicorn": {
@@ -9580,10 +10533,55 @@
         "eslint": ">=8.56.0"
       }
     },
+    "node_modules/eslint-plugin-unused-imports": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz",
+      "integrity": "sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw==",
+      "dev": true,
+      "dependencies": {
+        "eslint-rule-composer": "^0.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/eslint-plugin": "6 - 7",
+        "eslint": "8"
+      },
+      "peerDependenciesMeta": {
+        "@typescript-eslint/eslint-plugin": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-vitest": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.1.tgz",
+      "integrity": "sha512-g137wC+LCq2N+tfqK39Nl6Rs2N9u+zu6lWxaVgpN3wX+Kq6zSyxjPSI/ZBXUjP+qandT3z1DM5wK5IjD4XrAIw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/utils": "^7.6.0"
+      },
+      "engines": {
+        "node": "^18.0.0 || >= 20.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "vitest": "*"
+      },
+      "peerDependenciesMeta": {
+        "@typescript-eslint/eslint-plugin": {
+          "optional": true
+        },
+        "vitest": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/eslint-plugin-vue": {
-      "version": "9.24.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
-      "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
+      "version": "9.24.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.1.tgz",
+      "integrity": "sha512-wk3SuwmS1pZdcuJlokGYEi/buDOwD6KltvhIZyOnpJ/378dcQ4zchu9PAMbbLAaydCz1iYc5AozszcOOgZIIOg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
@@ -9599,7 +10597,7 @@
         "node": "^14.17.0 || >=16.0.0"
       },
       "peerDependencies": {
-        "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+        "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
       }
     },
     "node_modules/eslint-plugin-vue/node_modules/globals": {
@@ -9629,6 +10627,50 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/eslint-plugin-yml": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz",
+      "integrity": "sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.3.2",
+        "eslint-compat-utils": "^0.5.0",
+        "lodash": "^4.17.21",
+        "natural-compare": "^1.4.0",
+        "yaml-eslint-parser": "^1.2.1"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      },
+      "peerDependencies": {
+        "eslint": ">=6.0.0"
+      }
+    },
+    "node_modules/eslint-processor-vue-blocks": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-processor-vue-blocks/-/eslint-processor-vue-blocks-0.1.1.tgz",
+      "integrity": "sha512-9+dU5lU881log570oBwpelaJmOfOzSniben7IWEDRYQPPWwlvaV7NhOtsTuUWDqpYT+dtKKWPsgz4OkOi+aZnA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/compiler-sfc": "^3.3.0",
+        "eslint": "^8.50.0"
+      }
+    },
+    "node_modules/eslint-rule-composer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
+      "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
     "node_modules/eslint-scope": {
       "version": "7.2.2",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -10019,6 +11061,12 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
+    "node_modules/fast-diff": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+      "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+      "dev": true
+    },
     "node_modules/fast-fifo": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -11723,6 +12771,24 @@
         "node": ">=6"
       }
     },
+    "node_modules/jsonc-eslint-parser": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
+      "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.5.0",
+        "eslint-visitor-keys": "^3.0.0",
+        "espree": "^9.0.0",
+        "semver": "^7.3.5"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      }
+    },
     "node_modules/jsonc-parser": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
@@ -17194,6 +18260,33 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/prettier": {
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+      "dev": true,
+      "bin": {
+        "prettier": "bin/prettier.cjs"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "dependencies": {
+        "fast-diff": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/pretty-bytes": {
       "version": "6.1.1",
       "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
@@ -19346,6 +20439,22 @@
         "url": "https://opencollective.com/svgo"
       }
     },
+    "node_modules/synckit": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz",
+      "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==",
+      "dev": true,
+      "dependencies": {
+        "@pkgr/core": "^0.1.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/system-architecture": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz",
@@ -19558,6 +20667,21 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/toml-eslint-parser": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz",
+      "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.0.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      }
+    },
     "node_modules/totalist": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -21364,6 +22488,23 @@
         "node": ">= 14"
       }
     },
+    "node_modules/yaml-eslint-parser": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz",
+      "integrity": "sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.0.0",
+        "lodash": "^4.17.21",
+        "yaml": "^2.0.0"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ota-meshi"
+      }
+    },
     "node_modules/yargs": {
       "version": "17.7.2",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/packages/df-wiki-cli/df_wiki_cli/content/main.py b/packages/df-wiki-cli/df_wiki_cli/content/main.py
index 9202e8a3..bc669941 100644
--- a/packages/df-wiki-cli/df_wiki_cli/content/main.py
+++ b/packages/df-wiki-cli/df_wiki_cli/content/main.py
@@ -115,7 +115,7 @@ def structure(
         count_row = 0
         for row in reader:
             count_row += 1
-            system_dir_name = row["System_name_ok"]
+            system_dir_name = row["system"]
             pdb_path_file = Path(row["pdb"])
             foldseek_html_file = Path(str(pdb_path_file).split(".pdb")[0] + ".html")
             png_structure = Path(str(pdb_path_file).split(".pdb")[0] + ".png")
@@ -255,11 +255,12 @@ def system_operon_structure(
         ),
     ] = "./system-structures.csv",
     version: Annotated[str, typer.Option(help="Defense finder model")] = "v1.2.4",
+    tag: Annotated[str, typer.Option(help="Defense finder model")] = "1.2.4",
 ):
 
     # get defense finder model from github
 
-    df_model_url = f"https://github.com/mdmparis/defense-finder-models/releases/download/{version}/defense-finder-models-{version}.tar.gz"
+    df_model_url = f"https://github.com/mdmparis/defense-finder-models/releases/download/{tag}/defense-finder-models-{version}.tar.gz"
     _, tmp_path = tempfile.mkstemp()
     tmp_root_dir = tempfile.gettempdir()
     df_model_dir = Path(f"{tmp_root_dir}/defense-finder-models-{version}")
@@ -281,33 +282,39 @@ def system_operon_structure(
     for child in df_model_definitions_dir.iterdir():
         for system_path in child.iterdir():
             system = system_path.name
-            console.rule(system)
+            # console.rule(system)
             subsystem_list = (
                 s for s in system_path.iterdir() if str(s).endswith(".xml")
             )
             for subsystem in subsystem_list:
                 susbsystem_name = subsystem.stem
                 console.print(susbsystem_name)
+                in_exchangeables = False
+                current_gene = {}
+                exchangeables = []
+
                 with open(subsystem) as file:
-                    tree = ET.parse(file)
-                    root = tree.getroot()
-                    current_gene = {}
-                    for child in root.iter():
-                        if child.tag == "gene":
-                            current_gene = {
-                                "system": system,
-                                "subsystem": susbsystem_name,
-                                "gene": child.attrib["name"],
-                                "exchangeables": None,
-                            }
-                            system_genes.append(current_gene)
-                        if child.tag == "exchangeables":
-                            exchangeables = []
-                            for ex_gene in child.iter():
-                                if ex_gene.tag == "gene":
-                                    exchangeables.append(ex_gene.attrib["name"])
-                            current_gene["exchangeables"] = ",".join(exchangeables)
-                            current_gene = {}
+                    for event, elem in ET.iterparse(file, events=("start", "end")):
+                        if event == "start":
+                            if elem.tag == "gene" and not in_exchangeables:
+                                current_gene = {
+                                    "system": system,
+                                    "subsystem": susbsystem_name,
+                                    "gene": elem.attrib["name"],
+                                    "exchangeables": None,
+                                }
+                                system_genes.append(current_gene)
+                            if elem.tag == "gene" and in_exchangeables:
+                                exchangeables.append(elem.attrib["name"])
+                            if elem.tag == "exchangeables":
+                                in_exchangeables = True
+                                exchangeables = []
+                        elif event == "end":
+                            if elem.tag == "exchangeables":
+                                in_exchangeables = False
+                                current_gene["exchangeables"] = ",".join(exchangeables)
+                                exchangeables = []
+
     with open(output, "w") as f:
         fieldnames = ["id", "system", "subsystem", "gene", "exchangeables"]
         writer = csv.DictWriter(f, fieldnames=fieldnames)
diff --git a/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py b/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
index 52668833..e69de29b 100644
--- a/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
+++ b/packages/df-wiki-cli/df_wiki_cli/meilisearch/__init__.py
@@ -1,427 +0,0 @@
-import meilisearch
-from pathlib import Path
-import csv
-import json
-from typing import Annotated, List, Optional
-from pydantic import BaseModel, Field, BeforeValidator
-from enum import Enum
-from rich.console import Console
-
-
-console = Console()
-
-
-def emptyStringToNone(val: str) -> None | int:
-    if val == "":
-        return None
-    return int(float(val))
-
-
-class RefSeqCsv(BaseModel):
-    sys_id: str
-    Assembly: str
-    replicon: str
-    type: str
-    subtype: str
-    sys_beg: str
-    sys_end: str
-    protein_in_syst: List[str]
-    genes_count: Annotated[int | None, BeforeValidator(emptyStringToNone)]
-    name_of_profiles_in_sys: List[str]
-    accession_in_sys: List[str]
-    Superkingdom: str
-    phylum: str
-    class_: str = Field(..., alias="class")
-    order: str
-    family: str
-    genus: str
-    species: str
-
-
-class RefSeqTaxo(BaseModel):
-    index: int
-    Assembly: str
-    Superkingdom: str
-    phylum: str
-    class_: str = Field(..., alias="class")
-    order: str
-    family: str
-    genus: str
-    species: str
-    size: int
-
-
-class RefSeqTaxoType(RefSeqTaxo):
-    type: str
-
-
-class RefSeqTypeCount(BaseModel):
-    type: str
-    size: int
-
-
-class StructureTypes(str, Enum):
-    Validated = "Validated"
-    DF = "DF"
-    na = "na"
-
-
-def na_to_none(v: Optional[float]) -> Optional[float]:
-    if v == "na":
-        return None
-    else:
-        return v
-
-
-NaFloat = Annotated[Optional[float], BeforeValidator(na_to_none)]
-
-
-class StrucutreStatistics(BaseModel):
-    id: int
-    System_name_ok: str
-    System: str
-    gene_name: str
-    subtype: str
-    proteins_in_the_prediction: List[str]
-    prediction_type: str
-    batch: int
-    nb_sys: str
-    type: StructureTypes
-    system_number_of_genes: int
-    system_genes: List[str]
-    pdb: str
-    pae_table: str
-    plddt_table: str
-    fasta_file: str
-    completed: bool
-    iptm_ptm: NaFloat = Field(..., alias="iptm+ptm")
-    pDockQ: Optional[NaFloat]
-    plddts: Optional[NaFloat]
-    Foldseek_name: Optional[str]
-
-
-def update_refseqtaxo(host: str, key: str, file: Path, document: str):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    documents = []
-    with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
-        for row in csvreader:
-            doc = RefSeqTaxo(**row)
-            documents.append(doc.model_dump(by_alias=True))
-    tasks = index.add_documents_in_batches(documents, primary_key="Assembly")
-    for task in tasks:
-        console.print(task)
-    index.update_pagination_settings({"maxTotalHits": 1000000})
-    index.update_filterable_attributes(
-        body=[
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-            "Assembly",
-        ]
-    )
-    index.update_sortable_attributes(
-        [
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-            "Assembly",
-            "size",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-
-def update_refseqtaxotype(host: str, key: str, file: Path, document: str):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    documents = []
-    with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
-        for row in csvreader:
-            doc = RefSeqTaxoType(**row)
-            documents.append(doc.model_dump(by_alias=True))
-    tasks = index.add_documents_in_batches(documents, primary_key="index")
-    for task in tasks:
-        console.print(task)
-    index.update_pagination_settings({"maxTotalHits": 1000000})
-    index.update_filterable_attributes(
-        body=[
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-            "Assembly",
-        ]
-    )
-    index.update_sortable_attributes(
-        [
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-            "Assembly",
-            "type",
-            "size",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-
-def update_refseqtypecount(host: str, key: str, file: Path, document: str):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    documents = []
-    with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
-        for row in csvreader:
-            doc = RefSeqTypeCount(**row)
-            documents.append(doc.model_dump(by_alias=True))
-    tasks = index.add_documents_in_batches(documents, primary_key="type")
-    for task in tasks:
-        console.print(task)
-    index.update_pagination_settings({"maxTotalHits": 1000000})
-    index.update_filterable_attributes(body=["type"])
-    index.update_sortable_attributes(
-        [
-            "type",
-            "size",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-
-def update_refseq(
-    host: str,
-    key: str,
-    file: Path,
-    document: str,
-):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    documents = []
-    with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
-        for row in csvreader:
-            row["protein_in_syst"] = split_on_comma(row["protein_in_syst"])
-            row["name_of_profiles_in_sys"] = split_on_comma(
-                row["name_of_profiles_in_sys"]
-            )
-            row["accession_in_sys"] = split_on_comma(row["accession_in_sys"])
-            doc = RefSeqCsv(**row)
-            documents.append(doc.model_dump(by_alias=True))
-        tasks = index.add_documents_in_batches(documents, primary_key="sys_id")
-        for task in tasks:
-            console.print(task)
-    index.update_pagination_settings({"maxTotalHits": 1000000})
-    index.update_filterable_attributes(
-        body=[
-            "replicon",
-            "Assembly",
-            "type",
-            "subtype",
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-        ]
-    )
-    index.update_sortable_attributes(
-        [
-            "replicon",
-            "Assembly",
-            "type",
-            "subtype",
-            "Superkingdom",
-            "phylum",
-            "class",
-            "order",
-            "family",
-            "genus",
-            "species",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-    index.update_typo_tolerance(
-        {
-            "enabled": False
-            # "minWordSizeForTypos": {"oneTypo": 50, "twoTypos": 100}
-        }
-    )
-
-
-def update_structure(
-    host: str,
-    key: str,
-    file: Path,
-    document: str,
-):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    documents = []
-    with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
-        for id, row in enumerate(csvreader):
-            row["proteins_in_the_prediction"] = split_on_comma(
-                row["proteins_in_the_prediction"]
-            )
-            row["system_genes"] = split_on_comma(row["system_genes"])
-            doc = StrucutreStatistics(**row, id=id)
-            documents.append(doc.model_dump(by_alias=True))
-        tasks = index.add_documents_in_batches(documents, primary_key="id")
-        for task in tasks:
-            print(task)
-    pagination_settings_task = index.update_pagination_settings(
-        {"maxTotalHits": 100000}
-    )
-    print(pagination_settings_task)
-    attr_task = index.update_filterable_attributes(
-        body=[
-            "System",
-            "gene_name",
-            "subtype",
-            "completed",
-            "prediction_type",
-            "plddts",
-            "iptm+ptm",
-            "proteins_in_the_prediction",
-            "system_genes",
-            "pDockQ",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-    print(attr_task)
-    index.update_sortable_attributes(
-        [
-            "System_name_ok",
-            "System",
-            "gene_name",
-            "subtype",
-            "completed",
-            "plddts",
-            "nb_sys",
-            "completed",
-            "prediction_type",
-            "system_number_of_genes",
-            "iptm+ptm",
-            "pDockQ",
-        ]
-    )
-    index.update_typo_tolerance({"enabled": False})
-
-
-def update_systems(
-    host: str,
-    key: str,
-    file: Path,
-    document: str,
-):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    with open(file, "r") as jsonfile:
-        json_object = json.load(jsonfile)
-        tasks = index.add_documents_in_batches(json_object, primary_key="title")
-        for task in tasks:
-            print(task)
-    pagination_settings_task = index.update_pagination_settings(
-        {"maxTotalHits": 100000}
-    )
-    print(pagination_settings_task)
-    attr_task = index.update_filterable_attributes(
-        body=[
-            "title",
-            "Sensor",
-            "Activator",
-            "Effector",
-            "PFAM.AC",
-            "PFAM.DE",
-            "contributors",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-    print(attr_task)
-    index.update_sortable_attributes(["title", "Sensor", "Activator", "Effector"])
-    index.update_typo_tolerance({"enabled": False})
-
-
-def update_articles(
-    host: str,
-    key: str,
-    file: Path,
-    document: str,
-):
-    client = meilisearch.Client(host, key)
-    index = client.index(document.lower())
-    with open(file, "r") as jsonfile:
-        json_object = json.load(jsonfile)
-        for obj in json_object:
-            obj["ms_id"] = obj["id"].replace("/", "_")
-        tasks = index.add_documents_in_batches(json_object, primary_key="ms_id")
-        for task in tasks:
-            print(task)
-
-    pagination_settings_task = index.update_pagination_settings(
-        {"maxTotalHits": 100000}
-    )
-    print(pagination_settings_task)
-    attr_task = index.update_filterable_attributes(
-        body=[
-            "DOI",
-        ]
-    )
-    params = {
-        "maxValuesPerFacet": 1000000,
-        "sortFacetValuesBy": {"*": "count"},
-    }
-    index.update_faceting_settings(params)
-
-    print(attr_task)
-
-
-def split_on_comma(str_val: str) -> List[str]:
-    for val in str_val.split(","):
-        yield val.strip()
diff --git a/packages/df-wiki-cli/df_wiki_cli/meilisearch/main.py b/packages/df-wiki-cli/df_wiki_cli/meilisearch/main.py
index ea904573..b7bbffdb 100644
--- a/packages/df-wiki-cli/df_wiki_cli/meilisearch/main.py
+++ b/packages/df-wiki-cli/df_wiki_cli/meilisearch/main.py
@@ -5,16 +5,6 @@ from pathlib import Path
 
 from df_wiki_cli.meilisearch.update import main as update_main
 
-# from df_wiki_cli.meilisearch import update
-from df_wiki_cli.meilisearch import (
-    update_refseqtaxo,
-    update_articles,
-    update_refseq,
-    update_refseqtaxotype,
-    update_refseqtypecount,
-    update_structure,
-    update_systems,
-)
 
 from enum import Enum
 from types import SimpleNamespace
@@ -58,41 +48,6 @@ def main(
     # ctx.obj = SimpleNamespace()
 
 
-# @app.command()
-# def update(
-#     ctx: typer.Context,
-#     file: Annotated[
-#         Path,
-#         typer.Option(
-#             exists=False,
-#             file_okay=True,
-#             writable=True,
-#         ),
-#     ],
-#     document: Annotated[
-#         Documents, typer.Option(case_sensitive=False)
-#     ] = Documents.refseq,
-#     content_type: Annotated[str, typer.Option(help="Content-Type header")] = "text/csv",
-# ):
-
-#     if document == "refseqtaxo":
-#         update_refseqtaxo(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "refseq":
-#         update_refseq(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "structure":
-#         update_structure(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "systems":
-#         update_systems(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "article":
-#         update_articles(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "refseqtaxotype":
-#         update_refseqtaxotype(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "refseqsanitized":
-#         update_refseq(ctx.obj.host, ctx.obj.key, file, document)
-#     if document == "refseqtypecount":
-#         update_refseqtypecount(ctx.obj.host, ctx.obj.key, file, document)
-
-
 @app.command()
 def delete_all_documents(ctx: typer.Context, id: str):
     client = meilisearch.Client(ctx.obj.host, ctx.obj.key)
diff --git a/packages/df-wiki-cli/df_wiki_cli/meilisearch/update/main.py b/packages/df-wiki-cli/df_wiki_cli/meilisearch/update/main.py
index 24a6290b..02f3965a 100644
--- a/packages/df-wiki-cli/df_wiki_cli/meilisearch/update/main.py
+++ b/packages/df-wiki-cli/df_wiki_cli/meilisearch/update/main.py
@@ -81,7 +81,9 @@ NaFloat = Annotated[Optional[float], BeforeValidator(na_to_none)]
 
 class StrucutreStatistics(BaseModel):
     id: int
+    structure_system: str
     system: str
+    subsystem: str
     proteins_in_the_prediction: Optional[List[str]]
     prediction_type: str
     batch: int
@@ -97,6 +99,9 @@ class StrucutreStatistics(BaseModel):
     iptm_ptm: NaFloat = Field(..., alias="iptm+ptm")
     pDockQ: Optional[NaFloat]
     plddts: Optional[NaFloat]
+    Abi2: str
+    OrbA: str
+    Anti_BREX: str
 
 
 class SystemOperonStructure(BaseModel):
@@ -312,53 +317,31 @@ def structure(
     index = client.index(document.lower())
     documents = []
     with open(file, "r") as csvfile:
-        csvreader = csv.DictReader(csvfile)
+        csvreader = csv.DictReader(csvfile, delimiter="\t")
         for id, row in enumerate(csvreader):
             row["proteins_in_the_prediction"] = split_on_comma(
                 row["proteins_in_the_prediction"]
             )
             row["system_genes"] = split_on_comma(row["system_genes"])
-            # get info from systemoperonstruct
-
-            search_subtype = re.split(r"-\d+$", row["system"])[0]
-            # search_subtype = row["system"].split("-")[0]
-            console.print(search_subtype)
-            res = client.index("systemoperonstruct").search(
-                "",
-                {
-                    "filter": f"subsystem = '{search_subtype}'",
-                },
+            doc = StrucutreStatistics(
+                **row,
+                id=id,
             )
-            if len(res["hits"]) < 1:
-
-                # raise RuntimeError("do not find hits for " + search_subtype)
-                console.print(f"[red] no hits for {search_subtype}")
-            # else:
-            #     console.print(res["hits"])
-            # group data per subtype
-
-            # create a set of list of gene
-
-            # compare with the one defined in system_genes=
-
-            # if it is the same, set subtype
-
-            doc = StrucutreStatistics(**row, id=id)
             # console.print(doc)
 
             documents.append(doc.model_dump(by_alias=True))
-        # tasks = index.add_documents_in_batches(documents, primary_key="id")
-        # for task in tasks:
-        #     print(task)
+        tasks = index.add_documents_in_batches(documents, primary_key="id")
+        for task in tasks:
+            print(task)
     pagination_settings_task = index.update_pagination_settings(
         {"maxTotalHits": 100000}
     )
     print(pagination_settings_task)
     attr_task = index.update_filterable_attributes(
         body=[
-            "System",
+            "system",
+            "subsystem",
             "gene_name",
-            "subtype",
             "completed",
             "prediction_type",
             "plddts",
@@ -366,6 +349,9 @@ def structure(
             "proteins_in_the_prediction",
             "system_genes",
             "pDockQ",
+            "Abi2",
+            "OrbA",
+            "Anti_BREX",
         ]
     )
     params = {
@@ -377,10 +363,9 @@ def structure(
     print(attr_task)
     index.update_sortable_attributes(
         [
-            "System_name_ok",
-            "System",
+            "system",
+            "subsystem",
             "gene_name",
-            "subtype",
             "completed",
             "plddts",
             "nb_sys",
@@ -389,6 +374,9 @@ def structure(
             "system_number_of_genes",
             "iptm+ptm",
             "pDockQ",
+            "Abi2",
+            "OrbA",
+            "Anti_BREX",
         ]
     )
     index.update_typo_tolerance({"enabled": False})
diff --git a/packages/df-wiki-cli/poetry.lock b/packages/df-wiki-cli/poetry.lock
index 34991255..391b6c00 100644
--- a/packages/df-wiki-cli/poetry.lock
+++ b/packages/df-wiki-cli/poetry.lock
@@ -570,6 +570,177 @@ files = [
     {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"},
 ]
 
+[[package]]
+name = "lxml"
+version = "5.2.1"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1"},
+    {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a"},
+    {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01"},
+    {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1"},
+    {file = "lxml-5.2.1-cp310-cp310-win32.whl", hash = "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5"},
+    {file = "lxml-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f"},
+    {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"},
+    {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"},
+    {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"},
+    {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"},
+    {file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"},
+    {file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"},
+    {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"},
+    {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"},
+    {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"},
+    {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"},
+    {file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"},
+    {file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"},
+    {file = "lxml-5.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c"},
+    {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"},
+    {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"},
+    {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"},
+    {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62"},
+    {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461"},
+    {file = "lxml-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0"},
+    {file = "lxml-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289"},
+    {file = "lxml-5.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029"},
+    {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af"},
+    {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0"},
+    {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75"},
+    {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"},
+    {file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"},
+    {file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"},
+    {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"},
+    {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"},
+    {file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"},
+    {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"},
+    {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86"},
+    {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533"},
+    {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c"},
+    {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637"},
+    {file = "lxml-5.2.1-cp38-cp38-win32.whl", hash = "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da"},
+    {file = "lxml-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806"},
+    {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd"},
+    {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b"},
+    {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c"},
+    {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188"},
+    {file = "lxml-5.2.1-cp39-cp39-win32.whl", hash = "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708"},
+    {file = "lxml-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"},
+    {file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"},
+    {file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"},
+    {file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"},
+    {file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"},
+    {file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"},
+]
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html-clean = ["lxml-html-clean"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=3.0.10)"]
+
 [[package]]
 name = "markdown-it-py"
 version = "3.0.0"
@@ -1452,4 +1623,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.11,<3.13"
-content-hash = "8834fc25ed4a598a81158cb81ef55d0fb6d950ee29a02caa62e3241dcefc3ca0"
+content-hash = "e869e30378379cbc207695da4dede924c88dad26782577ce3d6c0d8948911e2a"
diff --git a/packages/df-wiki-cli/pyproject.toml b/packages/df-wiki-cli/pyproject.toml
index 421d54c8..27dd969e 100644
--- a/packages/df-wiki-cli/pyproject.toml
+++ b/packages/df-wiki-cli/pyproject.toml
@@ -20,6 +20,7 @@ python-frontmatter = "^1.0.1"
 matplotlib = "^3.8.2"
 habanero = "^1.2.6"
 biopython = "^1.83"
+lxml = "^5.2.1"
 
 
 [tool.poetry.group.dev.dependencies]
diff --git a/packages/df-wiki-cli/scripts/fill-local-meilisearch.sh b/packages/df-wiki-cli/scripts/fill-local-meilisearch.sh
index 2fabe8ea..588c51ff 100755
--- a/packages/df-wiki-cli/scripts/fill-local-meilisearch.sh
+++ b/packages/df-wiki-cli/scripts/fill-local-meilisearch.sh
@@ -6,7 +6,8 @@ PUBLIC_DIR=../../../public
 
 
 # system operon structure
-df-wiki-cli content system-operon-structure --version "1.2.3" --output ${DATA_DIR}/system-structures.csv
+df-wiki-cli meilisearch delete-all-documents system-operon-structure
+df-wiki-cli content system-operon-structure --tag "1.2.2" --version "1.2.2" --output ${DATA_DIR}/system-structures.csv
 df-wiki-cli meilisearch update system-operon-structure --file ${DATA_DIR}/system-structures.csv
 
 
@@ -38,7 +39,8 @@ df-wiki-cli content systems --dir ${CONTENT_DIR}/3.defense-systems/ --pfam ${PUB
 df-wiki-cli meilisearch update systems --file /tmp/list-systems.json
 
 # STRUCTURE 
-df-wiki-cli meilisearch update structure --file ${DATA_DIR}/all_predictions_statistics.csv
+df-wiki-cli meilisearch delete-all-documents structure
+df-wiki-cli meilisearch update structure --file ${DATA_DIR}/all_predictions_statistics.tsv
 
 # ARTICLES
 # df-wiki-cli meilisearch delete-all-documents article
diff --git a/types/plot.ts b/types/plot.ts
new file mode 100644
index 00000000..483cd6bf
--- /dev/null
+++ b/types/plot.ts
@@ -0,0 +1,15 @@
+export interface PlotMargin {
+    marginTop: number
+    marginRight: number
+    marginBottom: number
+    marginLeft: number
+}
+
+
+
+export interface StructureOperonGene {
+    gene: string
+    id: number
+    subsystem: string
+    system: string
+}
\ No newline at end of file
-- 
GitLab