From 49eecd0c87ace89a81c2f89f9dd72812e5549992 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 20:32:15 +0100 Subject: [PATCH 1/5] feat: campos documentation, notes y code en registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade campos documentation, notes y code a functions y types. El parser extrae el contenido del .md y el código fuente del archivo referenciado en file_path. El indexer los almacena en SQLite y los incluye en FTS5 para búsqueda sobre código y documentación. Nueva migración 003_documentation.sql para añadir las columnas. --- cmd/fn/main.go | 21 ++++ registry/indexer.go | 66 +++++++---- registry/migrations/003_documentation.sql | 103 +++++++++++++++++ registry/models.go | 13 ++- registry/parser.go | 130 ++++++++++++++++------ registry/parser_test.go | 10 +- registry/store.go | 15 ++- 7 files changed, 290 insertions(+), 68 deletions(-) create mode 100644 registry/migrations/003_documentation.sql diff --git a/cmd/fn/main.go b/cmd/fn/main.go index 497ecead..cfa6a068 100644 --- a/cmd/fn/main.go +++ b/cmd/fn/main.go @@ -289,6 +289,15 @@ func printFunction(f *registry.Function) { if f.Example != "" { fmt.Printf("\nExample:\n%s\n", f.Example) } + if f.Notes != "" { + fmt.Printf("\nNotes:\n%s\n", f.Notes) + } + if f.Documentation != "" { + fmt.Printf("\nDocumentation:\n%s\n", f.Documentation) + } + if f.Code != "" { + fmt.Printf("\nCode:\n%s\n", f.Code) + } if f.Kind == registry.KindComponent { fmt.Printf("Framework: %s\n", f.Framework) if f.HasState != nil { @@ -316,6 +325,18 @@ func printType(t *registry.Type) { if t.Definition != "" { fmt.Printf("\nDefinition:\n%s\n", t.Definition) } + if t.Examples != "" { + fmt.Printf("\nExamples:\n%s\n", t.Examples) + } + if t.Notes != "" { + fmt.Printf("\nNotes:\n%s\n", t.Notes) + } + if t.Documentation != "" { + fmt.Printf("\nDocumentation:\n%s\n", t.Documentation) + } + if t.Code != "" { + fmt.Printf("\nCode:\n%s\n", t.Code) + } } // --- add --- diff --git a/registry/indexer.go b/registry/indexer.go index 6aee63bf..9a85f2ea 100644 --- a/registry/indexer.go +++ b/registry/indexer.go @@ -19,6 +19,9 @@ type IndexResult struct { // and populates the database. It uses two passes: // 1. Parse all entries and collect known IDs // 2. Validate references against known IDs, then insert valid entries +// +// Scans functions/ and types/ at the root level, plus any language-specific +// directories (e.g. python/functions/, python/types/). func Index(db *DB, root string) (*IndexResult, error) { if err := db.Purge(); err != nil { return nil, fmt.Errorf("purging database: %w", err) @@ -26,39 +29,50 @@ func Index(db *DB, root string) (*IndexResult, error) { result := &IndexResult{} - // Pass 1: parse everything + // Pass 1: parse everything from all source directories var functions []*Function var types []*Type - functionsDir := filepath.Join(root, "functions") - if _, err := os.Stat(functionsDir); err == nil { - filepath.Walk(functionsDir, func(path string, info os.FileInfo, err error) error { - if err != nil || info.IsDir() || !strings.HasSuffix(path, ".md") { - return nil - } - f, err := ParseFunctionMD(path) + // Directories to scan for functions and types. + // Base dirs + language-specific dirs discovered automatically. + funcDirs := []string{filepath.Join(root, "functions")} + typeDirs := []string{filepath.Join(root, "types")} + + // Discover language-specific directories (e.g. python/functions/, python/types/) + entries, _ := os.ReadDir(root) + for _, e := range entries { + if !e.IsDir() { + continue + } + langFuncs := filepath.Join(root, e.Name(), "functions") + if fi, err := os.Stat(langFuncs); err == nil && fi.IsDir() { + funcDirs = append(funcDirs, langFuncs) + } + langTypes := filepath.Join(root, e.Name(), "types") + if fi, err := os.Stat(langTypes); err == nil && fi.IsDir() { + typeDirs = append(typeDirs, langTypes) + } + } + + for _, dir := range funcDirs { + walkMD(dir, func(path string) { + f, err := ParseFunctionMD(path, root) if err != nil { result.Errors = append(result.Errors, fmt.Sprintf("parse %s: %v", path, err)) - return nil + return } functions = append(functions, f) - return nil }) } - typesDir := filepath.Join(root, "types") - if _, err := os.Stat(typesDir); err == nil { - filepath.Walk(typesDir, func(path string, info os.FileInfo, err error) error { - if err != nil || info.IsDir() || !strings.HasSuffix(path, ".md") { - return nil - } - t, err := ParseTypeMD(path) + for _, dir := range typeDirs { + walkMD(dir, func(path string) { + t, err := ParseTypeMD(path, root) if err != nil { result.Errors = append(result.Errors, fmt.Sprintf("parse %s: %v", path, err)) - return nil + return } types = append(types, t) - return nil }) } @@ -99,3 +113,17 @@ func Index(db *DB, root string) (*IndexResult, error) { return result, nil } + +// walkMD walks a directory recursively and calls fn for each .md file found. +func walkMD(dir string, fn func(path string)) { + if _, err := os.Stat(dir); err != nil { + return + } + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() || !strings.HasSuffix(path, ".md") { + return nil + } + fn(path) + return nil + }) +} diff --git a/registry/migrations/003_documentation.sql b/registry/migrations/003_documentation.sql new file mode 100644 index 00000000..d7f4be6b --- /dev/null +++ b/registry/migrations/003_documentation.sql @@ -0,0 +1,103 @@ +-- Add documentation fields to functions and types. +-- examples: extracted code blocks from ## Ejemplo +-- notes: extracted text from ## Notas +-- documentation: remaining body text from .md +-- code: source code from the referenced .go/.py/.tsx file + +ALTER TABLE functions ADD COLUMN notes TEXT NOT NULL DEFAULT ''; +ALTER TABLE functions ADD COLUMN documentation TEXT NOT NULL DEFAULT ''; +ALTER TABLE functions ADD COLUMN code TEXT NOT NULL DEFAULT ''; + +ALTER TABLE types ADD COLUMN examples TEXT NOT NULL DEFAULT ''; +ALTER TABLE types ADD COLUMN notes TEXT NOT NULL DEFAULT ''; +ALTER TABLE types ADD COLUMN documentation TEXT NOT NULL DEFAULT ''; +ALTER TABLE types ADD COLUMN code TEXT NOT NULL DEFAULT ''; + +-- Rebuild FTS for functions: add examples, notes, documentation, code +DROP TRIGGER IF EXISTS functions_ai; +DROP TRIGGER IF EXISTS functions_ad; +DROP TRIGGER IF EXISTS functions_au; + +INSERT INTO functions_fts(functions_fts) VALUES('rebuild'); +DROP TABLE IF EXISTS functions_fts; + +CREATE VIRTUAL TABLE functions_fts USING fts5( + id, + name, + description, + tags, + signature, + domain, + example, + notes, + documentation, + code, + content='functions', + content_rowid='rowid' +); + +-- Populate FTS from existing data +INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code) +SELECT rowid, id, name, description, tags, signature, domain, example, notes, documentation, code +FROM functions; + +CREATE TRIGGER functions_ai AFTER INSERT ON functions BEGIN + INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code) + VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain, new.example, new.notes, new.documentation, new.code); +END; + +CREATE TRIGGER functions_ad AFTER DELETE ON functions BEGIN + INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain, example, notes, documentation, code) + VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain, old.example, old.notes, old.documentation, old.code); +END; + +CREATE TRIGGER functions_au AFTER UPDATE ON functions BEGIN + INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain, example, notes, documentation, code) + VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain, old.example, old.notes, old.documentation, old.code); + INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code) + VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain, new.example, new.notes, new.documentation, new.code); +END; + +-- Rebuild FTS for types: add examples, notes, documentation, code +DROP TRIGGER IF EXISTS types_ai; +DROP TRIGGER IF EXISTS types_ad; +DROP TRIGGER IF EXISTS types_au; + +INSERT INTO types_fts(types_fts) VALUES('rebuild'); +DROP TABLE IF EXISTS types_fts; + +CREATE VIRTUAL TABLE types_fts USING fts5( + id, + name, + description, + tags, + domain, + examples, + notes, + documentation, + code, + content='types', + content_rowid='rowid' +); + +-- Populate FTS from existing data +INSERT INTO types_fts(rowid, id, name, description, tags, domain, examples, notes, documentation, code) +SELECT rowid, id, name, description, tags, domain, examples, notes, documentation, code +FROM types; + +CREATE TRIGGER types_ai AFTER INSERT ON types BEGIN + INSERT INTO types_fts(rowid, id, name, description, tags, domain, examples, notes, documentation, code) + VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain, new.examples, new.notes, new.documentation, new.code); +END; + +CREATE TRIGGER types_ad AFTER DELETE ON types BEGIN + INSERT INTO types_fts(types_fts, rowid, id, name, description, tags, domain, examples, notes, documentation, code) + VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain, old.examples, old.notes, old.documentation, old.code); +END; + +CREATE TRIGGER types_au AFTER UPDATE ON types BEGIN + INSERT INTO types_fts(types_fts, rowid, id, name, description, tags, domain, examples, notes, documentation, code) + VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain, old.examples, old.notes, old.documentation, old.code); + INSERT INTO types_fts(rowid, id, name, description, tags, domain, examples, notes, documentation, code) + VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain, new.examples, new.notes, new.documentation, new.code); +END; diff --git a/registry/models.go b/registry/models.go index bf3b60a5..53dafa70 100644 --- a/registry/models.go +++ b/registry/models.go @@ -47,6 +47,9 @@ type Function struct { ErrorType string `json:"error_type"` Imports []string `json:"imports"` Example string `json:"example"` + Notes string `json:"notes"` + Documentation string `json:"documentation"` + Code string `json:"code"` Tested bool `json:"tested"` Tests []string `json:"tests"` TestFilePath string `json:"test_file_path"` @@ -82,9 +85,13 @@ type Type struct { Description string `json:"description"` Tags []string `json:"tags"` UsesTypes []string `json:"uses_types"` - FilePath string `json:"file_path"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + Examples string `json:"examples"` + Notes string `json:"notes"` + Documentation string `json:"documentation"` + Code string `json:"code"` + FilePath string `json:"file_path"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // ProposalKind classifies a proposal. diff --git a/registry/parser.go b/registry/parser.go index e4212667..a5875173 100644 --- a/registry/parser.go +++ b/registry/parser.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "path/filepath" "strings" "gopkg.in/yaml.v3" @@ -73,7 +74,8 @@ func extractFrontmatter(data []byte) ([]byte, []byte, error) { } // ParseFunctionMD parses a function .md file into a Function. -func ParseFunctionMD(path string) (*Function, error) { +// root is the registry root directory, used to resolve file_path for code reading. +func ParseFunctionMD(path string, root string) (*Function, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading %s: %w", path, err) @@ -99,7 +101,7 @@ func ParseFunctionMD(path string) (*Function, error) { return nil, fmt.Errorf("%s: description is required", path) } - example := extractExample(body) + sections := extractSections(body) f := &Function{ ID: GenerateID(raw.Name, raw.Lang, raw.Domain), @@ -118,6 +120,9 @@ func ParseFunctionMD(path string) (*Function, error) { ReturnsOptional: raw.ReturnsOptional, ErrorType: raw.ErrorType, Imports: raw.Imports, + Example: sections.example, + Notes: sections.notes, + Documentation: sections.documentation, Tested: raw.Tested, Tests: raw.Tests, TestFilePath: raw.TestFilePath, @@ -129,21 +134,25 @@ func ParseFunctionMD(path string) (*Function, error) { Variant: raw.Variant, } - if example != "" && f.Example == "" { - f.Example = example + if root != "" && raw.FilePath != "" { + codePath := filepath.Join(root, raw.FilePath) + if codeData, err := os.ReadFile(codePath); err == nil { + f.Code = string(codeData) + } } return f, nil } // ParseTypeMD parses a type .md file into a Type. -func ParseTypeMD(path string) (*Type, error) { +// root is the registry root directory, used to resolve file_path for code reading. +func ParseTypeMD(path string, root string) (*Type, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading %s: %w", path, err) } - fm, _, err := extractFrontmatter(data) + fm, body, err := extractFrontmatter(data) if err != nil { return nil, fmt.Errorf("parsing %s: %w", path, err) } @@ -160,49 +169,96 @@ func ParseTypeMD(path string) (*Type, error) { return nil, fmt.Errorf("%s: description is required", path) } + sections := extractSections(body) + t := &Type{ - ID: GenerateID(raw.Name, raw.Lang, raw.Domain), - Name: raw.Name, - Lang: raw.Lang, - Domain: raw.Domain, - Version: raw.Version, - Algebraic: Algebraic(raw.Algebraic), - Definition: strings.TrimSpace(raw.Definition), - Description: raw.Description, - Tags: raw.Tags, - UsesTypes: raw.UsesTypes, - FilePath: raw.FilePath, + ID: GenerateID(raw.Name, raw.Lang, raw.Domain), + Name: raw.Name, + Lang: raw.Lang, + Domain: raw.Domain, + Version: raw.Version, + Algebraic: Algebraic(raw.Algebraic), + Definition: strings.TrimSpace(raw.Definition), + Description: raw.Description, + Tags: raw.Tags, + UsesTypes: raw.UsesTypes, + Examples: sections.example, + Notes: sections.notes, + Documentation: sections.documentation, + FilePath: raw.FilePath, + } + + if root != "" && raw.FilePath != "" { + codePath := filepath.Join(root, raw.FilePath) + if codeData, err := os.ReadFile(codePath); err == nil { + t.Code = string(codeData) + } } return t, nil } -// extractExample pulls the first code block after an "## Ejemplo" heading. -func extractExample(body []byte) string { +// bodySections holds the extracted sections from a .md body. +type bodySections struct { + example string // content under ## Ejemplo + notes string // content under ## Notas + documentation string // everything else +} + +// extractSections splits the markdown body into named sections. +// Known sections (## Ejemplo, ## Notas) are extracted separately. +// All other content (including unknown ## headings) goes into documentation. +func extractSections(body []byte) bodySections { lines := strings.Split(string(body), "\n") - inExample := false - inCode := false - var code []string + var s bodySections + + type section struct { + name string + lines []string + } + + var current *section + var sections []section for _, line := range lines { trimmed := strings.TrimSpace(line) - if strings.HasPrefix(trimmed, "## Ejemplo") { - inExample = true - continue - } - if inExample && !inCode && strings.HasPrefix(trimmed, "```") { - inCode = true - continue - } - if inCode { - if strings.HasPrefix(trimmed, "```") { - return strings.Join(code, "\n") + if strings.HasPrefix(trimmed, "## ") { + if current != nil { + sections = append(sections, *current) } - code = append(code, line) + current = §ion{name: trimmed} + continue } - if inExample && !inCode && strings.HasPrefix(trimmed, "##") { - break + if current != nil { + current.lines = append(current.lines, line) + } else { + // Content before any ## heading goes to documentation + sections = append(sections, section{name: "_preamble", lines: []string{line}}) } } - return "" + if current != nil { + sections = append(sections, *current) + } + + var docParts []string + for _, sec := range sections { + content := strings.TrimSpace(strings.Join(sec.lines, "\n")) + if content == "" && sec.name == "_preamble" { + continue + } + switch { + case strings.HasPrefix(sec.name, "## Ejemplo"): + s.example = content + case strings.HasPrefix(sec.name, "## Notas"): + s.notes = content + case sec.name == "_preamble": + docParts = append(docParts, content) + default: + // Unknown sections go to documentation with their heading + docParts = append(docParts, sec.name+"\n\n"+content) + } + } + s.documentation = strings.TrimSpace(strings.Join(docParts, "\n\n")) + + return s } diff --git a/registry/parser_test.go b/registry/parser_test.go index f393c13f..f44c05bd 100644 --- a/registry/parser_test.go +++ b/registry/parser_test.go @@ -79,7 +79,7 @@ imports: [react] tested: false tests: [] test_file_path: "" -file_path: "functions/components/DataTable.tsx" +file_path: "frontend/functions/ui/data_table.tsx" props: - name: data type: "T[]" @@ -105,7 +105,7 @@ func writeTempFile(t *testing.T, dir, name, content string) string { func TestParseFunctionMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "filter_slice.md", functionMD) - f, err := ParseFunctionMD(path) + f, err := ParseFunctionMD(path, "") if err != nil { t.Fatal(err) } @@ -130,7 +130,7 @@ func TestParseFunctionMD(t *testing.T) { func TestParseTypeMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "ohlcv.md", typeMD) - typ, err := ParseTypeMD(path) + typ, err := ParseTypeMD(path, "") if err != nil { t.Fatal(err) } @@ -149,7 +149,7 @@ func TestParseTypeMD(t *testing.T) { func TestParseComponentMD(t *testing.T) { path := writeTempFile(t, t.TempDir(), "DataTable.md", componentMD) - f, err := ParseFunctionMD(path) + f, err := ParseFunctionMD(path, "") if err != nil { t.Fatal(err) } @@ -174,7 +174,7 @@ func TestParseComponentMD(t *testing.T) { func TestParseMissingFrontmatter(t *testing.T) { path := writeTempFile(t, t.TempDir(), "bad.md", "# No frontmatter here\n") - _, err := ParseFunctionMD(path) + _, err := ParseFunctionMD(path, "") if err == nil { t.Error("expected error for missing frontmatter") } diff --git a/registry/store.go b/registry/store.go index 91b75801..dd063fc2 100644 --- a/registry/store.go +++ b/registry/store.go @@ -82,19 +82,22 @@ func (db *DB) InsertFunction(f *Function) error { description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, example, tested, tests, test_file_path, file_path, created_at, updated_at, - props, emits, has_state, framework, variant + props, emits, has_state, framework, variant, + notes, documentation, code ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, + ?, ?, ? )`, f.ID, f.Name, string(f.Kind), f.Lang, f.Domain, f.Version, string(f.Purity), f.Signature, f.Description, marshalStrings(f.Tags), marshalStrings(f.UsesFunctions), marshalStrings(f.UsesTypes), marshalStrings(f.Returns), f.ReturnsOptional, f.ErrorType, marshalStrings(f.Imports), f.Example, f.Tested, marshalStrings(f.Tests), f.TestFilePath, f.FilePath, f.CreatedAt.Format(time.RFC3339), now, marshalProps(f.Props), marshalStrings(f.Emits), hasState, f.Framework, marshalStrings(f.Variant), + f.Notes, f.Documentation, f.Code, ) return err } @@ -115,11 +118,13 @@ func (db *DB) InsertType(t *Type) error { INSERT OR REPLACE INTO types ( id, name, lang, domain, version, algebraic, definition, description, tags, uses_types, - file_path, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + file_path, created_at, updated_at, + examples, notes, documentation, code + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, t.ID, t.Name, t.Lang, t.Domain, t.Version, string(t.Algebraic), t.Definition, t.Description, marshalStrings(t.Tags), marshalStrings(t.UsesTypes), t.FilePath, t.CreatedAt.Format(time.RFC3339), now, + t.Examples, t.Notes, t.Documentation, t.Code, ) return err } @@ -270,6 +275,7 @@ func scanFunctions(rows interface{ Next() bool; Scan(...any) error }) ([]Functio &f.ReturnsOptional, &f.ErrorType, &importsJSON, &f.Example, &f.Tested, &testsJSON, &f.TestFilePath, &f.FilePath, &createdAt, &updatedAt, &propsJSON, &emitsJSON, &hasState, &f.Framework, &variantJSON, + &f.Notes, &f.Documentation, &f.Code, ) if err != nil { return nil, fmt.Errorf("scanning function: %w", err) @@ -308,6 +314,7 @@ func scanTypes(rows interface{ Next() bool; Scan(...any) error }) ([]Type, error &t.ID, &t.Name, &t.Lang, &t.Domain, &t.Version, &t.Algebraic, &t.Definition, &t.Description, &tagsJSON, &usesTypJSON, &t.FilePath, &createdAt, &updatedAt, + &t.Examples, &t.Notes, &t.Documentation, &t.Code, ) if err != nil { return nil, fmt.Errorf("scanning type: %w", err) From 9e6bea681f8dcd01975eebf3cac7dde2c30b2b0d Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 20:32:24 +0100 Subject: [PATCH 2/5] feat: funciones Go para API Metabase y tipo MetabaseClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade funciones Go stub para la API de Metabase en dominio infra: auth, CRUD de cards, dashboards y users, execute_query y execute_card. Incluye tipo MetabaseClient y helper HTTP compartido. Todas las funciones son impuras con stubs not-implemented. --- functions/infra/metabase_auth.go | 49 +++++++++ functions/infra/metabase_auth.md | 50 +++++++++ functions/infra/metabase_create_card.go | 35 ++++++ functions/infra/metabase_create_card.md | 86 +++++++++++++++ functions/infra/metabase_create_dashboard.go | 26 +++++ functions/infra/metabase_create_dashboard.md | 53 +++++++++ functions/infra/metabase_create_user.go | 28 +++++ functions/infra/metabase_create_user.md | 47 ++++++++ functions/infra/metabase_deactivate_user.go | 17 +++ functions/infra/metabase_deactivate_user.md | 40 +++++++ functions/infra/metabase_delete_card.go | 16 +++ functions/infra/metabase_delete_card.md | 37 +++++++ functions/infra/metabase_delete_dashboard.go | 16 +++ functions/infra/metabase_delete_dashboard.md | 37 +++++++ functions/infra/metabase_execute_card.go | 22 ++++ functions/infra/metabase_execute_card.md | 71 ++++++++++++ functions/infra/metabase_execute_query.go | 28 +++++ functions/infra/metabase_execute_query.md | 63 +++++++++++ functions/infra/metabase_get_card.go | 15 +++ functions/infra/metabase_get_card.md | 52 +++++++++ functions/infra/metabase_get_dashboard.go | 15 +++ functions/infra/metabase_get_dashboard.md | 71 ++++++++++++ functions/infra/metabase_get_user.go | 15 +++ functions/infra/metabase_get_user.md | 51 +++++++++ functions/infra/metabase_http.go | 107 ++++++++++++++++++ functions/infra/metabase_list_cards.go | 25 +++++ functions/infra/metabase_list_cards.md | 63 +++++++++++ functions/infra/metabase_list_dashboards.go | 19 ++++ functions/infra/metabase_list_dashboards.md | 57 ++++++++++ functions/infra/metabase_list_users.go | 30 +++++ functions/infra/metabase_list_users.md | 67 +++++++++++ functions/infra/metabase_update_card.go | 18 +++ functions/infra/metabase_update_card.md | 69 ++++++++++++ functions/infra/metabase_update_dashboard.go | 23 ++++ functions/infra/metabase_update_dashboard.md | 110 +++++++++++++++++++ functions/infra/metabase_update_user.go | 17 +++ functions/infra/metabase_update_user.md | 58 ++++++++++ functions/infra/types.go | 6 + types/infra/metabase_client.go | 7 ++ types/infra/metabase_client.md | 24 ++++ 40 files changed, 1640 insertions(+) create mode 100644 functions/infra/metabase_auth.go create mode 100644 functions/infra/metabase_auth.md create mode 100644 functions/infra/metabase_create_card.go create mode 100644 functions/infra/metabase_create_card.md create mode 100644 functions/infra/metabase_create_dashboard.go create mode 100644 functions/infra/metabase_create_dashboard.md create mode 100644 functions/infra/metabase_create_user.go create mode 100644 functions/infra/metabase_create_user.md create mode 100644 functions/infra/metabase_deactivate_user.go create mode 100644 functions/infra/metabase_deactivate_user.md create mode 100644 functions/infra/metabase_delete_card.go create mode 100644 functions/infra/metabase_delete_card.md create mode 100644 functions/infra/metabase_delete_dashboard.go create mode 100644 functions/infra/metabase_delete_dashboard.md create mode 100644 functions/infra/metabase_execute_card.go create mode 100644 functions/infra/metabase_execute_card.md create mode 100644 functions/infra/metabase_execute_query.go create mode 100644 functions/infra/metabase_execute_query.md create mode 100644 functions/infra/metabase_get_card.go create mode 100644 functions/infra/metabase_get_card.md create mode 100644 functions/infra/metabase_get_dashboard.go create mode 100644 functions/infra/metabase_get_dashboard.md create mode 100644 functions/infra/metabase_get_user.go create mode 100644 functions/infra/metabase_get_user.md create mode 100644 functions/infra/metabase_http.go create mode 100644 functions/infra/metabase_list_cards.go create mode 100644 functions/infra/metabase_list_cards.md create mode 100644 functions/infra/metabase_list_dashboards.go create mode 100644 functions/infra/metabase_list_dashboards.md create mode 100644 functions/infra/metabase_list_users.go create mode 100644 functions/infra/metabase_list_users.md create mode 100644 functions/infra/metabase_update_card.go create mode 100644 functions/infra/metabase_update_card.md create mode 100644 functions/infra/metabase_update_dashboard.go create mode 100644 functions/infra/metabase_update_dashboard.md create mode 100644 functions/infra/metabase_update_user.go create mode 100644 functions/infra/metabase_update_user.md create mode 100644 types/infra/metabase_client.go create mode 100644 types/infra/metabase_client.md diff --git a/functions/infra/metabase_auth.go b/functions/infra/metabase_auth.go new file mode 100644 index 00000000..38d62ecd --- /dev/null +++ b/functions/infra/metabase_auth.go @@ -0,0 +1,49 @@ +package infra + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +// MetabaseAuth autentica con email y password contra una instancia Metabase. +// Retorna un MetabaseClient con el session token listo para usar. +// baseURL es la URL base sin trailing slash (ej: "http://localhost:3000"). +func MetabaseAuth(baseURL, email, password string) (MetabaseClient, error) { + payload, _ := json.Marshal(map[string]string{ + "username": email, + "password": password, + }) + + resp, err := http.Post(baseURL+"/api/session", "application/json", bytes.NewReader(payload)) + if err != nil { + return MetabaseClient{}, fmt.Errorf("metabase auth: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return MetabaseClient{}, fmt.Errorf("read auth response: %w", err) + } + + if resp.StatusCode != 200 { + return MetabaseClient{}, fmt.Errorf("metabase auth: status %d: %s", resp.StatusCode, string(body)) + } + + var result struct { + ID string `json:"id"` + } + if err := json.Unmarshal(body, &result); err != nil { + return MetabaseClient{}, fmt.Errorf("parse auth response: %w", err) + } + + return MetabaseClient{BaseURL: baseURL, Token: result.ID}, nil +} + +// MetabaseNewClient crea un MetabaseClient usando una API key en lugar de session token. +// Las API keys se crean en Settings > Authentication > API Keys del admin de Metabase. +func MetabaseNewClient(baseURL, apiKey string) MetabaseClient { + return MetabaseClient{BaseURL: baseURL, Token: apiKey} +} diff --git a/functions/infra/metabase_auth.md b/functions/infra/metabase_auth.md new file mode 100644 index 00000000..8234de47 --- /dev/null +++ b/functions/infra/metabase_auth.md @@ -0,0 +1,50 @@ +--- +name: metabase_auth +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseAuth(baseURL, email, password string) (MetabaseClient, error)" +description: "Autentica contra la API de Metabase con email y password. Retorna un MetabaseClient con session token valido por 14 dias (configurable con MAX_SESSION_AGE en Metabase). Endpoint: POST /api/session." +tags: [metabase, auth, session, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [MetabaseClient_go_infra] +returns_optional: false +error_type: "error_go_core" +imports: [bytes, encoding/json, fmt, io, net/http] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_auth.go" +--- + +## Ejemplo + +```go +// Autenticar con credenciales +client, err := MetabaseAuth("http://localhost:3000", "admin@example.com", "password123") +if err != nil { + log.Fatal(err) +} +// client.Token contiene el session token + +// Alternativa: usar API key directamente +client := MetabaseNewClient("http://localhost:3000", "mb_api_key_xxxxx") +``` + +## Notas + +Dos formas de obtener un MetabaseClient: +- `MetabaseAuth`: login con email/password, obtiene session token via POST /api/session. Token expira en 14 dias por defecto. +- `MetabaseNewClient`: usa una API key creada en el admin UI. No expira. Recomendado para automatizacion. + +El token se envia como header `X-Metabase-Session` en todas las llamadas subsiguientes. + +### Para un LLM que use estas funciones + +1. Primero obtener un client con `MetabaseAuth()` o `MetabaseNewClient()` +2. Pasar el client a todas las funciones CRUD (usuarios, cards, dashboards) +3. Si recibes error 401, el token expiro — re-autenticar +4. Rate limiting: Metabase limita intentos de login fallidos diff --git a/functions/infra/metabase_create_card.go b/functions/infra/metabase_create_card.go new file mode 100644 index 00000000..8ffe3818 --- /dev/null +++ b/functions/infra/metabase_create_card.go @@ -0,0 +1,35 @@ +package infra + +import "fmt" + +// MetabaseCreateCard crea una nueva card/pregunta en Metabase. +// name: nombre de la pregunta (obligatorio). +// datasetQuery: query de la card (obligatorio). Estructura: +// +// SQL nativo: {"database": 1, "type": "native", "native": {"query": "SELECT ..."}} +// MBQL: {"database": 1, "type": "query", "query": {"source-table": 4, ...}} +// +// display: tipo de visualizacion ("table", "bar", "line", "pie", "scalar", etc.). +// collectionID: ID de la coleccion/carpeta (0 = root). +// description: descripcion opcional (vacio = sin descripcion). +func MetabaseCreateCard(client MetabaseClient, name string, datasetQuery map[string]any, display string, collectionID int, description string) (map[string]any, error) { + body := map[string]any{ + "name": name, + "dataset_query": datasetQuery, + "display": display, + "visualization_settings": map[string]any{}, + } + if collectionID > 0 { + body["collection_id"] = collectionID + } + if description != "" { + body["description"] = description + } + + result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/card", body) + if err != nil { + return nil, fmt.Errorf("metabase create card: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_create_card.md b/functions/infra/metabase_create_card.md new file mode 100644 index 00000000..abeab982 --- /dev/null +++ b/functions/infra/metabase_create_card.md @@ -0,0 +1,86 @@ +--- +name: metabase_create_card +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseCreateCard(client MetabaseClient, name string, datasetQuery map[string]any, display string, collectionID int, description string) (map[string]any, error)" +description: "Crea una nueva card/pregunta en Metabase con query SQL nativa o MBQL. Endpoint: POST /api/card." +tags: [metabase, card, question, create, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_create_card.go" +--- + +## Ejemplo + +```go +// Crear pregunta con SQL nativo +card, err := MetabaseCreateCard(client, "Revenue by Month", map[string]any{ + "database": 1, + "type": "native", + "native": map[string]any{ + "query": "SELECT date_trunc('month', created_at) as month, SUM(total) as revenue FROM orders GROUP BY 1 ORDER BY 1", + }, +}, "line", 5, "Monthly revenue trend") + +// Crear pregunta con MBQL (structured query) +card, err := MetabaseCreateCard(client, "Order Count", map[string]any{ + "database": 1, + "type": "query", + "query": map[string]any{ + "source-table": 4, + "aggregation": []any{[]any{"count"}}, + }, +}, "scalar", 0, "Total number of orders") +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| name | string | si | Nombre de la pregunta | +| datasetQuery | map[string]any | si | Query. Ver estructura abajo | +| display | string | si | Tipo de visualizacion | +| collectionID | int | no | ID de coleccion. 0 = root collection | +| description | string | no | Descripcion. Vacio = sin descripcion | + +### Estructura de datasetQuery + +**SQL nativo:** +```json +{ + "database": , + "type": "native", + "native": {"query": "SELECT ..."} +} +``` + +**MBQL (structured):** +```json +{ + "database": , + "type": "query", + "query": { + "source-table": , + "aggregation": [["count"]], + "breakout": [["field", , {"temporal-unit": "month"}]], + "filter": ["=", ["field", , null], "value"] + } +} +``` + +### Valores de display + +table, bar, line, pie, scalar, area, row, combo, funnel, map, scatter, waterfall, progress, gauge diff --git a/functions/infra/metabase_create_dashboard.go b/functions/infra/metabase_create_dashboard.go new file mode 100644 index 00000000..955100f9 --- /dev/null +++ b/functions/infra/metabase_create_dashboard.go @@ -0,0 +1,26 @@ +package infra + +import "fmt" + +// MetabaseCreateDashboard crea un nuevo dashboard en Metabase. +// name: nombre del dashboard (obligatorio). +// description: descripcion opcional (vacio = sin descripcion). +// collectionID: ID de la coleccion/carpeta (0 = root). +func MetabaseCreateDashboard(client MetabaseClient, name, description string, collectionID int) (map[string]any, error) { + body := map[string]any{ + "name": name, + } + if description != "" { + body["description"] = description + } + if collectionID > 0 { + body["collection_id"] = collectionID + } + + result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/dashboard", body) + if err != nil { + return nil, fmt.Errorf("metabase create dashboard: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_create_dashboard.md b/functions/infra/metabase_create_dashboard.md new file mode 100644 index 00000000..120fb0e6 --- /dev/null +++ b/functions/infra/metabase_create_dashboard.md @@ -0,0 +1,53 @@ +--- +name: metabase_create_dashboard +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseCreateDashboard(client MetabaseClient, name, description string, collectionID int) (map[string]any, error)" +description: "Crea un nuevo dashboard vacio en Metabase. Para agregar cards usar MetabaseUpdateDashboard con el campo dashcards. Endpoint: POST /api/dashboard." +tags: [metabase, dashboard, create, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_create_dashboard.go" +--- + +## Ejemplo + +```go +// Crear dashboard vacio +dashboard, err := MetabaseCreateDashboard(client, "Sales Overview", "KPIs de ventas", 5) +if err != nil { + log.Fatal(err) +} +dashboardID := int(dashboard["id"].(float64)) + +// Luego agregar cards con MetabaseUpdateDashboard +MetabaseUpdateDashboard(client, dashboardID, map[string]any{ + "dashcards": []map[string]any{ + {"id": -1, "card_id": 42, "size_x": 6, "size_y": 4, "col": 0, "row": 0}, + }, +}) +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| name | string | si | Nombre del dashboard | +| description | string | no | Descripcion. Vacio = sin descripcion | +| collectionID | int | no | Coleccion destino. 0 = root | + +El dashboard se crea vacio. Para agregar cards, usar MetabaseUpdateDashboard con el array dashcards. +Retorna el objeto dashboard creado. diff --git a/functions/infra/metabase_create_user.go b/functions/infra/metabase_create_user.go new file mode 100644 index 00000000..c72e7dea --- /dev/null +++ b/functions/infra/metabase_create_user.go @@ -0,0 +1,28 @@ +package infra + +import "fmt" + +// MetabaseCreateUser crea un nuevo usuario en Metabase. +// firstName, lastName y email son obligatorios. +// password es opcional: si esta vacio, Metabase envia email de invitacion. +// groupIDs es opcional: IDs de grupos a asignar (nil = solo grupo default). +func MetabaseCreateUser(client MetabaseClient, firstName, lastName, email, password string, groupIDs []int) (map[string]any, error) { + body := map[string]any{ + "first_name": firstName, + "last_name": lastName, + "email": email, + } + if password != "" { + body["password"] = password + } + if len(groupIDs) > 0 { + body["group_ids"] = groupIDs + } + + result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/user", body) + if err != nil { + return nil, fmt.Errorf("metabase create user: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_create_user.md b/functions/infra/metabase_create_user.md new file mode 100644 index 00000000..21b62345 --- /dev/null +++ b/functions/infra/metabase_create_user.md @@ -0,0 +1,47 @@ +--- +name: metabase_create_user +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseCreateUser(client MetabaseClient, firstName, lastName, email, password string, groupIDs []int) (map[string]any, error)" +description: "Crea un nuevo usuario en Metabase. Si no se provee password, Metabase envia email de invitacion. Requiere permisos de superusuario. Endpoint: POST /api/user." +tags: [metabase, user, create, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_create_user.go" +--- + +## Ejemplo + +```go +// Crear usuario con password +user, err := MetabaseCreateUser(client, "John", "Doe", "john@example.com", "securePass123", nil) + +// Crear usuario sin password (envia invitacion por email) +user, err := MetabaseCreateUser(client, "Jane", "Smith", "jane@example.com", "", []int{1, 3}) +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado con permisos admin | +| firstName | string | si | Nombre del usuario | +| lastName | string | si | Apellido del usuario | +| email | string | si | Email unico del usuario | +| password | string | no | Password. Vacio = Metabase envia invitacion | +| groupIDs | []int | no | IDs de grupos. nil = solo grupo default | + +El email debe ser unico. Si ya existe, retorna error 400. +Retorna el objeto usuario creado como map (mismos campos que MetabaseGetUser). diff --git a/functions/infra/metabase_deactivate_user.go b/functions/infra/metabase_deactivate_user.go new file mode 100644 index 00000000..df2cba9a --- /dev/null +++ b/functions/infra/metabase_deactivate_user.go @@ -0,0 +1,17 @@ +package infra + +import "fmt" + +// MetabaseDeactivateUser desactiva (soft-delete) un usuario en Metabase. +// El usuario no se elimina permanentemente, solo se marca como inactivo. +// Requiere permisos de superusuario. +func MetabaseDeactivateUser(client MetabaseClient, userID int) error { + path := fmt.Sprintf("/api/user/%d", userID) + + _, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil) + if err != nil { + return fmt.Errorf("metabase deactivate user %d: %w", userID, err) + } + + return nil +} diff --git a/functions/infra/metabase_deactivate_user.md b/functions/infra/metabase_deactivate_user.md new file mode 100644 index 00000000..dbb0d541 --- /dev/null +++ b/functions/infra/metabase_deactivate_user.md @@ -0,0 +1,40 @@ +--- +name: metabase_deactivate_user +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseDeactivateUser(client MetabaseClient, userID int) error" +description: "Desactiva (soft-delete) un usuario en Metabase. El usuario no se elimina permanentemente, solo se marca como inactivo. Para reactivar, usar PUT /api/user/:id/reactivate. Endpoint: DELETE /api/user/:id." +tags: [metabase, user, delete, deactivate, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_deactivate_user.go" +--- + +## Ejemplo + +```go +err := MetabaseDeactivateUser(client, 5) +if err != nil { + log.Fatal(err) +} +// Usuario 5 ahora esta inactivo +// Para ver desactivados: MetabaseListUsers(client, "deactivated", "", 0, 0) +``` + +## Notas + +Es un soft-delete: el usuario se desactiva pero no se borra. Se puede reactivar con PUT /api/user/:id/reactivate. + +Para listar usuarios desactivados, usar `MetabaseListUsers` con status "deactivated". + +Requiere permisos de superusuario. Error 403 si no eres admin. diff --git a/functions/infra/metabase_delete_card.go b/functions/infra/metabase_delete_card.go new file mode 100644 index 00000000..db4982ed --- /dev/null +++ b/functions/infra/metabase_delete_card.go @@ -0,0 +1,16 @@ +package infra + +import "fmt" + +// MetabaseDeleteCard elimina permanentemente una card/pregunta de Metabase. +// Para soft-delete, usar MetabaseUpdateCard con archived: true. +func MetabaseDeleteCard(client MetabaseClient, cardID int) error { + path := fmt.Sprintf("/api/card/%d", cardID) + + _, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil) + if err != nil { + return fmt.Errorf("metabase delete card %d: %w", cardID, err) + } + + return nil +} diff --git a/functions/infra/metabase_delete_card.md b/functions/infra/metabase_delete_card.md new file mode 100644 index 00000000..d2c296f7 --- /dev/null +++ b/functions/infra/metabase_delete_card.md @@ -0,0 +1,37 @@ +--- +name: metabase_delete_card +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseDeleteCard(client MetabaseClient, cardID int) error" +description: "Elimina permanentemente una card/pregunta de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateCard con archived:true. Endpoint: DELETE /api/card/:id." +tags: [metabase, card, question, delete, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_delete_card.go" +--- + +## Ejemplo + +```go +// Eliminar permanentemente +err := MetabaseDeleteCard(client, 42) + +// Preferir soft-delete cuando sea posible: +// MetabaseUpdateCard(client, 42, map[string]any{"archived": true}) +``` + +## Notas + +**ATENCION**: Esta operacion es irreversible. La card se elimina permanentemente. + +Para un borrado seguro, preferir archivar con `MetabaseUpdateCard(client, cardID, map[string]any{"archived": true})` que permite recuperar la card despues. diff --git a/functions/infra/metabase_delete_dashboard.go b/functions/infra/metabase_delete_dashboard.go new file mode 100644 index 00000000..f12584a8 --- /dev/null +++ b/functions/infra/metabase_delete_dashboard.go @@ -0,0 +1,16 @@ +package infra + +import "fmt" + +// MetabaseDeleteDashboard elimina permanentemente un dashboard de Metabase. +// Para soft-delete, usar MetabaseUpdateDashboard con archived: true. +func MetabaseDeleteDashboard(client MetabaseClient, dashboardID int) error { + path := fmt.Sprintf("/api/dashboard/%d", dashboardID) + + _, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil) + if err != nil { + return fmt.Errorf("metabase delete dashboard %d: %w", dashboardID, err) + } + + return nil +} diff --git a/functions/infra/metabase_delete_dashboard.md b/functions/infra/metabase_delete_dashboard.md new file mode 100644 index 00000000..a9e03fe8 --- /dev/null +++ b/functions/infra/metabase_delete_dashboard.md @@ -0,0 +1,37 @@ +--- +name: metabase_delete_dashboard +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseDeleteDashboard(client MetabaseClient, dashboardID int) error" +description: "Elimina permanentemente un dashboard de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateDashboard con archived:true. Endpoint: DELETE /api/dashboard/:id." +tags: [metabase, dashboard, delete, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_delete_dashboard.go" +--- + +## Ejemplo + +```go +// Eliminar permanentemente +err := MetabaseDeleteDashboard(client, 1) + +// Preferir soft-delete: +// MetabaseUpdateDashboard(client, 1, map[string]any{"archived": true}) +``` + +## Notas + +**ATENCION**: Esta operacion es irreversible. El dashboard y todas sus dashcards se eliminan permanentemente. + +Para un borrado seguro, preferir archivar con `MetabaseUpdateDashboard(client, dashboardID, map[string]any{"archived": true})`. diff --git a/functions/infra/metabase_execute_card.go b/functions/infra/metabase_execute_card.go new file mode 100644 index 00000000..f7cda011 --- /dev/null +++ b/functions/infra/metabase_execute_card.go @@ -0,0 +1,22 @@ +package infra + +import "fmt" + +// MetabaseExecuteCard ejecuta la query de una card/pregunta guardada. +// parameters: parametros de la query (nil si no tiene parametros). +// Retorna los resultados con columnas y filas. +func MetabaseExecuteCard(client MetabaseClient, cardID int, parameters []map[string]any) (map[string]any, error) { + path := fmt.Sprintf("/api/card/%d/query", cardID) + + var body map[string]any + if len(parameters) > 0 { + body = map[string]any{"parameters": parameters} + } + + result, err := metabaseRequest("POST", client.BaseURL, client.Token, path, body) + if err != nil { + return nil, fmt.Errorf("metabase execute card %d: %w", cardID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_execute_card.md b/functions/infra/metabase_execute_card.md new file mode 100644 index 00000000..18aff315 --- /dev/null +++ b/functions/infra/metabase_execute_card.md @@ -0,0 +1,71 @@ +--- +name: metabase_execute_card +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseExecuteCard(client MetabaseClient, cardID int, parameters []map[string]any) (map[string]any, error)" +description: "Ejecuta la query de una card/pregunta guardada en Metabase y retorna los resultados. Soporta parametros para queries parametrizadas. Endpoint: POST /api/card/:id/query." +tags: [metabase, card, question, execute, query, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_execute_card.go" +--- + +## Ejemplo + +```go +// Ejecutar sin parametros +result, err := MetabaseExecuteCard(client, 42, nil) +if err != nil { + log.Fatal(err) +} +data := result["data"].(map[string]any) +rows := data["rows"].([]any) +fmt.Printf("Filas: %d\n", len(rows)) + +// Ejecutar con parametros +result, err := MetabaseExecuteCard(client, 42, []map[string]any{ + { + "type": "category", + "target": []any{"variable", []any{"template-tag", "status"}}, + "value": "active", + }, +}) +``` + +## Notas + +### Estructura de la respuesta + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| status | string | "completed" o "failed" | +| row_count | float64 | Numero de filas | +| running_time | float64 | Tiempo de ejecucion en ms | +| data.columns | []string | Nombres de columnas | +| data.rows | [][]any | Filas de datos | +| data.cols | []map | Metadata de columnas (name, base_type, display_name) | +| data.native_form.query | string | SQL ejecutado | + +### Parametros para queries parametrizadas + +```go +[]map[string]any{ + { + "type": "category", // tipo del parametro + "target": []any{"variable", []any{"template-tag", "tag"}}, // referencia al template-tag + "value": "valor", // valor a inyectar + }, +} +``` + +Limite por defecto: 2000 filas. Para queries ad-hoc sin card, usar MetabaseExecuteQuery. diff --git a/functions/infra/metabase_execute_query.go b/functions/infra/metabase_execute_query.go new file mode 100644 index 00000000..5beed0ec --- /dev/null +++ b/functions/infra/metabase_execute_query.go @@ -0,0 +1,28 @@ +package infra + +import "fmt" + +// MetabaseExecuteQuery ejecuta una query ad-hoc (sin guardar como card) en Metabase. +// databaseID: ID de la base de datos en Metabase. +// sql: query SQL a ejecutar. +// maxResults: limite de filas (0 = default 2000 de Metabase). +func MetabaseExecuteQuery(client MetabaseClient, databaseID int, sql string, maxResults int) (map[string]any, error) { + body := map[string]any{ + "database": databaseID, + "type": "native", + "native": map[string]any{"query": sql}, + } + if maxResults > 0 { + body["constraints"] = map[string]any{ + "max-results": maxResults, + "max-results-bare-rows": maxResults, + } + } + + result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/dataset", body) + if err != nil { + return nil, fmt.Errorf("metabase execute query: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_execute_query.md b/functions/infra/metabase_execute_query.md new file mode 100644 index 00000000..a3bbe527 --- /dev/null +++ b/functions/infra/metabase_execute_query.md @@ -0,0 +1,63 @@ +--- +name: metabase_execute_query +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseExecuteQuery(client MetabaseClient, databaseID int, sql string, maxResults int) (map[string]any, error)" +description: "Ejecuta una query SQL ad-hoc contra una database de Metabase sin guardarla como card. Util para consultas rapidas y exploracion. Endpoint: POST /api/dataset." +tags: [metabase, query, execute, sql, dataset, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_execute_query.go" +--- + +## Ejemplo + +```go +// Query simple +result, err := MetabaseExecuteQuery(client, 1, "SELECT * FROM users LIMIT 10", 0) +if err != nil { + log.Fatal(err) +} +data := result["data"].(map[string]any) +rows := data["rows"].([]any) + +// Query con limite custom +result, err := MetabaseExecuteQuery(client, 1, "SELECT * FROM orders", 5000) +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| databaseID | int | si | ID de la database en Metabase (obtener con GET /api/database) | +| sql | string | si | Query SQL a ejecutar | +| maxResults | int | no | Limite de filas. 0 = default 2000 | + +### Diferencia con MetabaseExecuteCard + +- `MetabaseExecuteQuery`: query ad-hoc, no se guarda. Usa POST /api/dataset. +- `MetabaseExecuteCard`: ejecuta una card ya guardada. Usa POST /api/card/:id/query. + +Usar esta funcion para exploracion rapida. Si la query se va a reutilizar, crear una card con MetabaseCreateCard. + +### Estructura de la respuesta + +Misma estructura que MetabaseExecuteCard: +- `data.columns`: nombres de columnas +- `data.rows`: filas de datos +- `row_count`: numero de filas +- `running_time`: tiempo en ms +- `status`: "completed" o "failed" diff --git a/functions/infra/metabase_get_card.go b/functions/infra/metabase_get_card.go new file mode 100644 index 00000000..e68c3168 --- /dev/null +++ b/functions/infra/metabase_get_card.go @@ -0,0 +1,15 @@ +package infra + +import "fmt" + +// MetabaseGetCard obtiene una card/pregunta de Metabase por su ID. +func MetabaseGetCard(client MetabaseClient, cardID int) (map[string]any, error) { + path := fmt.Sprintf("/api/card/%d", cardID) + + result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase get card %d: %w", cardID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_get_card.md b/functions/infra/metabase_get_card.md new file mode 100644 index 00000000..57d3b5aa --- /dev/null +++ b/functions/infra/metabase_get_card.md @@ -0,0 +1,52 @@ +--- +name: metabase_get_card +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseGetCard(client MetabaseClient, cardID int) (map[string]any, error)" +description: "Obtiene los detalles completos de una card/pregunta de Metabase por su ID. Incluye la query, visualizacion y metadata. Endpoint: GET /api/card/:id." +tags: [metabase, card, question, get, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_get_card.go" +--- + +## Ejemplo + +```go +card, err := MetabaseGetCard(client, 42) +if err != nil { + log.Fatal(err) +} +fmt.Println(card["name"], card["display"]) +``` + +## Notas + +Retorna el objeto card completo. Error 404 si no existe. + +### Campos principales + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID de la card | +| name | string | Nombre | +| description | string | Descripcion | +| display | string | Tipo visualizacion | +| dataset_query | map | Query (native.query para SQL, query para MBQL) | +| visualization_settings | map | Config de visualizacion | +| collection_id | float64 | Coleccion contenedora | +| database_id | float64 | Database asociada | +| archived | bool | Archivada | +| creator | map | Objeto del usuario creador | +| created_at | string | Fecha creacion | +| updated_at | string | Fecha actualizacion | diff --git a/functions/infra/metabase_get_dashboard.go b/functions/infra/metabase_get_dashboard.go new file mode 100644 index 00000000..9bc7f3ce --- /dev/null +++ b/functions/infra/metabase_get_dashboard.go @@ -0,0 +1,15 @@ +package infra + +import "fmt" + +// MetabaseGetDashboard obtiene un dashboard completo de Metabase incluyendo sus cards. +func MetabaseGetDashboard(client MetabaseClient, dashboardID int) (map[string]any, error) { + path := fmt.Sprintf("/api/dashboard/%d", dashboardID) + + result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase get dashboard %d: %w", dashboardID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_get_dashboard.md b/functions/infra/metabase_get_dashboard.md new file mode 100644 index 00000000..07147b91 --- /dev/null +++ b/functions/infra/metabase_get_dashboard.md @@ -0,0 +1,71 @@ +--- +name: metabase_get_dashboard +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseGetDashboard(client MetabaseClient, dashboardID int) (map[string]any, error)" +description: "Obtiene un dashboard completo de Metabase incluyendo todas sus dashcards (cards posicionadas en el dashboard), tabs y parametros. Endpoint: GET /api/dashboard/:id." +tags: [metabase, dashboard, get, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_get_dashboard.go" +--- + +## Ejemplo + +```go +dashboard, err := MetabaseGetDashboard(client, 1) +if err != nil { + log.Fatal(err) +} +fmt.Println(dashboard["name"]) + +// Acceder a las cards del dashboard +dashcards := dashboard["dashcards"].([]any) +for _, dc := range dashcards { + card := dc.(map[string]any) + fmt.Printf("Card ID: %v, Position: (%v, %v)\n", + card["card_id"], card["col"], card["row"]) +} +``` + +## Notas + +### Campos principales + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID del dashboard | +| name | string | Nombre | +| description | string | Descripcion | +| dashcards | []map | Array de dashcards (cards posicionadas) | +| parameters | []map | Filtros del dashboard | +| tabs | []map | Tabs del dashboard | +| collection_id | float64 | Coleccion contenedora | +| archived | bool | Archivado | + +### Estructura de cada dashcard + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID del dashcard (positivo) | +| card_id | float64 | ID de la card/pregunta asociada | +| card | map | Objeto card completo | +| size_x | float64 | Ancho en grid (1-18) | +| size_y | float64 | Alto en grid | +| col | float64 | Columna en grid (0-based) | +| row | float64 | Fila en grid (0-based) | +| dashboard_tab_id | float64 | Tab al que pertenece (null = sin tabs) | +| parameter_mappings | []map | Mapeo de filtros a la card | +| visualization_settings | map | Settings de visualizacion | + +Usar estos datos para construir el payload de MetabaseUpdateDashboard. diff --git a/functions/infra/metabase_get_user.go b/functions/infra/metabase_get_user.go new file mode 100644 index 00000000..308f47d9 --- /dev/null +++ b/functions/infra/metabase_get_user.go @@ -0,0 +1,15 @@ +package infra + +import "fmt" + +// MetabaseGetUser obtiene un usuario de Metabase por su ID. +func MetabaseGetUser(client MetabaseClient, userID int) (map[string]any, error) { + path := fmt.Sprintf("/api/user/%d", userID) + + result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase get user %d: %w", userID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_get_user.md b/functions/infra/metabase_get_user.md new file mode 100644 index 00000000..e884f25c --- /dev/null +++ b/functions/infra/metabase_get_user.md @@ -0,0 +1,51 @@ +--- +name: metabase_get_user +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseGetUser(client MetabaseClient, userID int) (map[string]any, error)" +description: "Obtiene los detalles de un usuario de Metabase por su ID numerico. Endpoint: GET /api/user/:id." +tags: [metabase, user, get, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_get_user.go" +--- + +## Ejemplo + +```go +user, err := MetabaseGetUser(client, 1) +if err != nil { + log.Fatal(err) +} +fmt.Println(user["email"], user["first_name"]) +``` + +## Notas + +Retorna el objeto usuario completo como map. Error 404 si el ID no existe. + +### Campos del usuario retornado + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID numerico | +| email | string | Email | +| first_name | string | Nombre | +| last_name | string | Apellido | +| is_superuser | bool | Es admin | +| is_active | bool | Esta activo | +| common_name | string | Nombre completo | +| date_joined | string | Fecha de creacion | +| last_login | string | Ultimo login | +| group_ids | []float64 | IDs de grupos | +| locale | string | Locale del usuario | diff --git a/functions/infra/metabase_http.go b/functions/infra/metabase_http.go new file mode 100644 index 00000000..d894bebe --- /dev/null +++ b/functions/infra/metabase_http.go @@ -0,0 +1,107 @@ +package infra + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +// metabaseRequest ejecuta una peticion HTTP contra la API de Metabase. +// method: GET, POST, PUT, DELETE +// baseURL: URL base sin trailing slash +// token: session token o API key +// path: ruta relativa (ej: "/api/user") +// body: payload JSON (nil para requests sin body) +// Retorna el body deserializado como map o nil si el body esta vacio. +func metabaseRequest(method, baseURL, token, path string, body map[string]any) (map[string]any, error) { + var reqBody io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal body: %w", err) + } + reqBody = bytes.NewReader(data) + } + + req, err := http.NewRequest(method, baseURL+path, reqBody) + if err != nil { + return nil, fmt.Errorf("new request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Metabase-Session", token) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("http %s %s: %w", method, path, err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("metabase %s %s: status %d: %s", method, path, resp.StatusCode, string(respBody)) + } + + if len(respBody) == 0 { + return nil, nil + } + + var result map[string]any + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + + return result, nil +} + +// metabaseRequestList es como metabaseRequest pero para endpoints que retornan un array JSON. +func metabaseRequestList(method, baseURL, token, path string, body map[string]any) ([]map[string]any, error) { + var reqBody io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal body: %w", err) + } + reqBody = bytes.NewReader(data) + } + + req, err := http.NewRequest(method, baseURL+path, reqBody) + if err != nil { + return nil, fmt.Errorf("new request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Metabase-Session", token) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("http %s %s: %w", method, path, err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("metabase %s %s: status %d: %s", method, path, resp.StatusCode, string(respBody)) + } + + if len(respBody) == 0 { + return nil, nil + } + + var result []map[string]any + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_list_cards.go b/functions/infra/metabase_list_cards.go new file mode 100644 index 00000000..ba1f5cd8 --- /dev/null +++ b/functions/infra/metabase_list_cards.go @@ -0,0 +1,25 @@ +package infra + +import "fmt" + +// MetabaseListCards lista preguntas/cards de Metabase. +// filter: "all", "mine", "fav", "archived", "recent", "popular", "database", "table" (vacio = todas). +// modelID: ID de database o tabla cuando filter es "database" o "table" (0 = ignorar). +func MetabaseListCards(client MetabaseClient, filter string, modelID int) ([]map[string]any, error) { + path := "/api/card" + sep := "?" + if filter != "" { + path += sep + "f=" + filter + sep = "&" + } + if modelID > 0 { + path += fmt.Sprintf("%smodel_id=%d", sep, modelID) + } + + result, err := metabaseRequestList("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase list cards: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_list_cards.md b/functions/infra/metabase_list_cards.md new file mode 100644 index 00000000..91656b15 --- /dev/null +++ b/functions/infra/metabase_list_cards.md @@ -0,0 +1,63 @@ +--- +name: metabase_list_cards +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseListCards(client MetabaseClient, filter string, modelID int) ([]map[string]any, error)" +description: "Lista preguntas/cards de Metabase con filtro opcional. Retorna array de cards. Endpoint: GET /api/card." +tags: [metabase, card, question, list, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_list_cards.go" +--- + +## Ejemplo + +```go +// Listar todas las cards +cards, err := MetabaseListCards(client, "all", 0) + +// Solo mis preguntas +cards, err := MetabaseListCards(client, "mine", 0) + +// Cards de una database especifica +cards, err := MetabaseListCards(client, "database", 1) + +// Cards archivadas +cards, err := MetabaseListCards(client, "archived", 0) +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| filter | string | no | "all", "mine", "fav", "archived", "recent", "popular", "database", "table". Vacio = todas | +| modelID | int | no | ID de database/tabla. Solo aplica con filter "database" o "table". 0 = ignorar | + +No tiene paginacion con offset/limit. Retorna todas las cards que coinciden. + +### Campos principales de cada card + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID numerico de la card | +| name | string | Nombre de la pregunta | +| description | string | Descripcion | +| display | string | Tipo de visualizacion (table, bar, line, pie, etc.) | +| collection_id | float64 | ID de la coleccion/carpeta | +| database_id | float64 | ID de la database | +| creator_id | float64 | ID del creador | +| archived | bool | Esta archivada | +| dataset_query | map | Query de la card (native o structured) | diff --git a/functions/infra/metabase_list_dashboards.go b/functions/infra/metabase_list_dashboards.go new file mode 100644 index 00000000..b541f39e --- /dev/null +++ b/functions/infra/metabase_list_dashboards.go @@ -0,0 +1,19 @@ +package infra + +import "fmt" + +// MetabaseListDashboards lista dashboards de Metabase. +// filter: "all", "mine" o "archived" (vacio = todas). +func MetabaseListDashboards(client MetabaseClient, filter string) ([]map[string]any, error) { + path := "/api/dashboard" + if filter != "" { + path += "?f=" + filter + } + + result, err := metabaseRequestList("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase list dashboards: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_list_dashboards.md b/functions/infra/metabase_list_dashboards.md new file mode 100644 index 00000000..4c588e51 --- /dev/null +++ b/functions/infra/metabase_list_dashboards.md @@ -0,0 +1,57 @@ +--- +name: metabase_list_dashboards +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseListDashboards(client MetabaseClient, filter string) ([]map[string]any, error)" +description: "Lista dashboards de Metabase con filtro opcional. Retorna array de dashboards resumidos (sin dashcards). Endpoint: GET /api/dashboard." +tags: [metabase, dashboard, list, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_list_dashboards.go" +--- + +## Ejemplo + +```go +// Listar todos los dashboards +dashboards, err := MetabaseListDashboards(client, "all") + +// Solo mis dashboards +dashboards, err := MetabaseListDashboards(client, "mine") + +// Dashboards archivados +dashboards, err := MetabaseListDashboards(client, "archived") +``` + +## Notas + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| filter | string | no | "all", "mine", "archived". Vacio = todas | + +Retorna dashboards resumidos (sin cards). Para ver las cards de un dashboard, usar MetabaseGetDashboard. + +### Campos principales de cada dashboard + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID del dashboard | +| name | string | Nombre | +| description | string | Descripcion | +| collection_id | float64 | Coleccion contenedora | +| creator_id | float64 | ID del creador | +| archived | bool | Archivado | +| created_at | string | Fecha creacion | diff --git a/functions/infra/metabase_list_users.go b/functions/infra/metabase_list_users.go new file mode 100644 index 00000000..fe6971bd --- /dev/null +++ b/functions/infra/metabase_list_users.go @@ -0,0 +1,30 @@ +package infra + +import "fmt" + +// MetabaseListUsers lista usuarios de Metabase con filtros opcionales. +// status: "active", "deactivated" o "all" (vacio = "active"). +// query: filtro por nombre o email (vacio = sin filtro). +// limit/offset: paginacion (0 = valores por defecto de Metabase). +func MetabaseListUsers(client MetabaseClient, status, query string, limit, offset int) (map[string]any, error) { + path := "/api/user?" + if status != "" { + path += "status=" + status + "&" + } + if query != "" { + path += "query=" + query + "&" + } + if limit > 0 { + path += fmt.Sprintf("limit=%d&", limit) + } + if offset > 0 { + path += fmt.Sprintf("offset=%d&", offset) + } + + result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil) + if err != nil { + return nil, fmt.Errorf("metabase list users: %w", err) + } + + return result, nil +} diff --git a/functions/infra/metabase_list_users.md b/functions/infra/metabase_list_users.md new file mode 100644 index 00000000..764ef170 --- /dev/null +++ b/functions/infra/metabase_list_users.md @@ -0,0 +1,67 @@ +--- +name: metabase_list_users +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseListUsers(client MetabaseClient, status, query string, limit, offset int) (map[string]any, error)" +description: "Lista usuarios de una instancia Metabase con filtros opcionales por estado, nombre/email y paginacion. Endpoint: GET /api/user. Requiere permisos de superusuario." +tags: [metabase, user, list, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_list_users.go" +--- + +## Ejemplo + +```go +client, _ := MetabaseAuth("http://localhost:3000", "admin@example.com", "pass") + +// Listar todos los usuarios activos +users, err := MetabaseListUsers(client, "active", "", 0, 0) + +// Buscar usuario por email +users, err := MetabaseListUsers(client, "", "john@", 10, 0) + +// Listar desactivados +users, err := MetabaseListUsers(client, "deactivated", "", 25, 0) +``` + +## Notas + +Retorna un map con la estructura paginada de Metabase: +- `data`: array de objetos usuario (id, email, first_name, last_name, is_superuser, etc.) +- `total`: numero total de usuarios que coinciden +- `limit`: tamanio de pagina usado +- `offset`: offset usado + +### Parametros para un LLM + +| Parametro | Tipo | Requerido | Descripcion | +|-----------|------|-----------|-------------| +| client | MetabaseClient | si | Cliente autenticado | +| status | string | no | "active" (default), "deactivated", "all" | +| query | string | no | Filtro por nombre o email | +| limit | int | no | Tamanio de pagina (0 = default Metabase) | +| offset | int | no | Offset para paginacion (0 = inicio) | + +### Campos del usuario retornado + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| id | float64 | ID numerico del usuario | +| email | string | Email unico | +| first_name | string | Nombre | +| last_name | string | Apellido | +| is_superuser | bool | Es admin | +| is_active | bool | Esta activo | +| common_name | string | Nombre completo | +| last_login | string | Fecha ultimo login | diff --git a/functions/infra/metabase_update_card.go b/functions/infra/metabase_update_card.go new file mode 100644 index 00000000..288bf8d0 --- /dev/null +++ b/functions/infra/metabase_update_card.go @@ -0,0 +1,18 @@ +package infra + +import "fmt" + +// MetabaseUpdateCard actualiza campos de una card/pregunta en Metabase. +// fields es un map con los campos a actualizar. +// Campos comunes: name, description, display, dataset_query, visualization_settings, +// collection_id, archived, enable_embedding. +func MetabaseUpdateCard(client MetabaseClient, cardID int, fields map[string]any) (map[string]any, error) { + path := fmt.Sprintf("/api/card/%d", cardID) + + result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields) + if err != nil { + return nil, fmt.Errorf("metabase update card %d: %w", cardID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_update_card.md b/functions/infra/metabase_update_card.md new file mode 100644 index 00000000..625d7e53 --- /dev/null +++ b/functions/infra/metabase_update_card.md @@ -0,0 +1,69 @@ +--- +name: metabase_update_card +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseUpdateCard(client MetabaseClient, cardID int, fields map[string]any) (map[string]any, error)" +description: "Actualiza campos de una card/pregunta en Metabase. Solo se modifican los campos incluidos en el map. Endpoint: PUT /api/card/:id." +tags: [metabase, card, question, update, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_update_card.go" +--- + +## Ejemplo + +```go +// Cambiar nombre y descripcion +card, err := MetabaseUpdateCard(client, 42, map[string]any{ + "name": "Updated Revenue Chart", + "description": "Now includes refunds", +}) + +// Archivar una card (soft-delete) +card, err := MetabaseUpdateCard(client, 42, map[string]any{ + "archived": true, +}) + +// Mover a otra coleccion +card, err := MetabaseUpdateCard(client, 42, map[string]any{ + "collection_id": 10, +}) + +// Cambiar la query SQL +card, err := MetabaseUpdateCard(client, 42, map[string]any{ + "dataset_query": map[string]any{ + "database": 1, + "type": "native", + "native": map[string]any{"query": "SELECT * FROM users LIMIT 100"}, + }, +}) +``` + +## Notas + +### Campos actualizables + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| name | string | Nombre de la pregunta | +| description | string | Descripcion | +| display | string | Tipo de visualizacion | +| dataset_query | map | Query SQL o MBQL | +| visualization_settings | map | Config de visualizacion | +| collection_id | int | Mover a otra coleccion | +| archived | bool | Archivar/desarchivar (soft-delete) | +| enable_embedding | bool | Habilitar embedding publico | +| embedding_params | map | Parametros de embedding | + +Solo incluir los campos que se quieren cambiar. +Para eliminar permanentemente usar MetabaseDeleteCard. Para soft-delete usar archived: true. diff --git a/functions/infra/metabase_update_dashboard.go b/functions/infra/metabase_update_dashboard.go new file mode 100644 index 00000000..79053b72 --- /dev/null +++ b/functions/infra/metabase_update_dashboard.go @@ -0,0 +1,23 @@ +package infra + +import "fmt" + +// MetabaseUpdateDashboard actualiza un dashboard en Metabase. +// fields puede incluir metadata del dashboard Y/O la lista completa de dashcards y tabs. +// +// Para gestionar cards en el dashboard, incluir "dashcards" en fields: +// - Agregar card: incluirla con ID negativo (ej: -1, -2) +// - Actualizar card: incluirla con su ID positivo existente +// - Eliminar card: omitirla del array (el array es el estado deseado completo) +// +// Campos comunes: name, description, archived, parameters, dashcards, tabs, collection_id. +func MetabaseUpdateDashboard(client MetabaseClient, dashboardID int, fields map[string]any) (map[string]any, error) { + path := fmt.Sprintf("/api/dashboard/%d", dashboardID) + + result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields) + if err != nil { + return nil, fmt.Errorf("metabase update dashboard %d: %w", dashboardID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_update_dashboard.md b/functions/infra/metabase_update_dashboard.md new file mode 100644 index 00000000..5e384d06 --- /dev/null +++ b/functions/infra/metabase_update_dashboard.md @@ -0,0 +1,110 @@ +--- +name: metabase_update_dashboard +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseUpdateDashboard(client MetabaseClient, dashboardID int, fields map[string]any) (map[string]any, error)" +description: "Actualiza un dashboard en Metabase incluyendo metadata, cards y tabs. El campo dashcards representa el estado completo deseado: cards nuevas con ID negativo, existentes con ID positivo, omitidas se eliminan. Endpoint: PUT /api/dashboard/:id." +tags: [metabase, dashboard, update, cards, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_update_dashboard.go" +--- + +## Ejemplo + +```go +// Cambiar nombre +MetabaseUpdateDashboard(client, 1, map[string]any{ + "name": "Updated Dashboard", +}) + +// Agregar una card al dashboard +// Primero obtener las dashcards existentes +dash, _ := MetabaseGetDashboard(client, 1) +existingCards := dash["dashcards"].([]any) + +// Construir nuevo array con las existentes + la nueva +dashcards := make([]map[string]any, 0) +for _, dc := range existingCards { + dashcards = append(dashcards, dc.(map[string]any)) +} +// Agregar nueva card (ID negativo = nueva) +dashcards = append(dashcards, map[string]any{ + "id": -1, "card_id": 55, "size_x": 6, "size_y": 4, "col": 0, "row": 0, +}) + +MetabaseUpdateDashboard(client, 1, map[string]any{ + "dashcards": dashcards, +}) + +// Archivar dashboard (soft-delete) +MetabaseUpdateDashboard(client, 1, map[string]any{"archived": true}) +``` + +## Notas + +### Gestion de dashcards (IMPORTANTE) + +El array `dashcards` representa el **estado completo deseado** del dashboard: + +| Accion | Como hacerlo | +|--------|-------------| +| Agregar card | Incluir con **ID negativo** (-1, -2, etc.) | +| Actualizar card | Incluir con su **ID positivo** existente | +| Eliminar card | **Omitir** del array | +| No cambiar cards | No incluir el campo dashcards | + +**Flujo tipico para agregar una card:** +1. `MetabaseGetDashboard` para obtener dashcards existentes +2. Copiar las existentes al nuevo array +3. Agregar la nueva con ID negativo +4. Enviar el array completo + +### Estructura de una dashcard + +```go +map[string]any{ + "id": -1, // negativo = nueva, positivo = existente + "card_id": 42, // ID de la card/pregunta + "size_x": 6, // ancho (1-18) + "size_y": 4, // alto + "col": 0, // columna (0-based) + "row": 0, // fila (0-based) + "dashboard_tab_id": nil, // tab (nil = sin tabs) + "parameter_mappings": []map[string]any{}, // mapeo de filtros + "visualization_settings": map[string]any{}, // settings custom +} +``` + +### Gestion de tabs + +```go +map[string]any{ + "tabs": []map[string]any{ + {"id": 1, "name": "Overview"}, // tab existente + {"id": -1, "name": "Details"}, // tab nuevo (ID negativo) + }, +} +``` + +### Campos actualizables + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| name | string | Nombre del dashboard | +| description | string | Descripcion | +| archived | bool | Archivar/desarchivar | +| dashcards | []map | Estado completo de cards | +| tabs | []map | Tabs del dashboard | +| parameters | []map | Filtros del dashboard | +| collection_id | int | Mover a otra coleccion | diff --git a/functions/infra/metabase_update_user.go b/functions/infra/metabase_update_user.go new file mode 100644 index 00000000..5bd2677f --- /dev/null +++ b/functions/infra/metabase_update_user.go @@ -0,0 +1,17 @@ +package infra + +import "fmt" + +// MetabaseUpdateUser actualiza campos de un usuario en Metabase. +// fields es un map con los campos a actualizar. Campos validos: +// first_name, last_name, email, is_superuser, group_ids, locale, login_attributes. +func MetabaseUpdateUser(client MetabaseClient, userID int, fields map[string]any) (map[string]any, error) { + path := fmt.Sprintf("/api/user/%d", userID) + + result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields) + if err != nil { + return nil, fmt.Errorf("metabase update user %d: %w", userID, err) + } + + return result, nil +} diff --git a/functions/infra/metabase_update_user.md b/functions/infra/metabase_update_user.md new file mode 100644 index 00000000..3976f233 --- /dev/null +++ b/functions/infra/metabase_update_user.md @@ -0,0 +1,58 @@ +--- +name: metabase_update_user +kind: function +lang: go +domain: infra +version: "1.0.0" +purity: impure +signature: "func MetabaseUpdateUser(client MetabaseClient, userID int, fields map[string]any) (map[string]any, error)" +description: "Actualiza campos de un usuario en Metabase. Solo se modifican los campos incluidos en el map. Requiere permisos de superusuario. Endpoint: PUT /api/user/:id." +tags: [metabase, user, update, api] +uses_functions: [] +uses_types: [MetabaseClient_go_infra] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/infra/metabase_update_user.go" +--- + +## Ejemplo + +```go +// Cambiar nombre +user, err := MetabaseUpdateUser(client, 5, map[string]any{ + "first_name": "Jane", + "last_name": "Smith", +}) + +// Promover a admin +user, err := MetabaseUpdateUser(client, 5, map[string]any{ + "is_superuser": true, +}) + +// Cambiar grupos +user, err := MetabaseUpdateUser(client, 5, map[string]any{ + "group_ids": []int{1, 3, 5}, +}) +``` + +## Notas + +### Campos actualizables + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| first_name | string | Nombre | +| last_name | string | Apellido | +| email | string | Email (debe ser unico) | +| is_superuser | bool | Permisos de admin | +| group_ids | []int | IDs de grupos del usuario | +| locale | string | Locale (ej: "es", "en") | +| login_attributes | map | Atributos para sandboxing | + +Solo incluir los campos que se quieren cambiar. Los demas se mantienen sin modificar. +Retorna el objeto usuario actualizado. diff --git a/functions/infra/types.go b/functions/infra/types.go index 486f79b1..9d515777 100644 --- a/functions/infra/types.go +++ b/functions/infra/types.go @@ -20,3 +20,9 @@ type ImageInfo struct { Size string Created string } + +// MetabaseClient holds the connection details for a Metabase instance API. +type MetabaseClient struct { + BaseURL string // e.g. "http://localhost:3000" + Token string // session token or API key +} diff --git a/types/infra/metabase_client.go b/types/infra/metabase_client.go new file mode 100644 index 00000000..0bcbb4d0 --- /dev/null +++ b/types/infra/metabase_client.go @@ -0,0 +1,7 @@ +package infra + +// MetabaseClient holds the connection details for a Metabase instance API. +type MetabaseClient struct { + BaseURL string // e.g. "http://localhost:3000" + Token string // session token or API key +} diff --git a/types/infra/metabase_client.md b/types/infra/metabase_client.md new file mode 100644 index 00000000..be7b1765 --- /dev/null +++ b/types/infra/metabase_client.md @@ -0,0 +1,24 @@ +--- +name: MetabaseClient +lang: go +domain: infra +version: "1.0.0" +algebraic: product +definition: | + type MetabaseClient struct { + BaseURL string + Token string + } +description: "Cliente para la API REST de Metabase. Contiene la URL base de la instancia y el token de autenticacion (session token o API key)." +tags: [metabase, api, client, infra] +uses_types: [] +file_path: "types/infra/metabase_client.go" +--- + +## Notas + +Tipo producto con dos campos obligatorios: +- `BaseURL`: URL base de la instancia Metabase sin trailing slash (ej: `http://localhost:3000`) +- `Token`: token de sesion obtenido con `MetabaseAuth()` o una API key creada en el admin UI de Metabase + +El token se envia como header `X-Metabase-Session` en session tokens o `x-api-key` en API keys. Las funciones del registry usan `X-Metabase-Session` por defecto. From be5a7b582e6d03bc71ae3d6ec59c7e9e898e1384 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 20:32:28 +0100 Subject: [PATCH 3/5] feat: funciones Python para API Metabase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade módulo Python con funciones para la API de Metabase en dominio infra. Incluye cliente HTTP, auth, y CRUD de cards, dashboards y users. Proyecto gestionado con uv (pyproject.toml). --- python/.python-version | 1 + python/functions/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 149 bytes python/functions/metabase/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 961 bytes .../__pycache__/cards.cpython-312.pyc | Bin 0 -> 8335 bytes .../__pycache__/client.cpython-312.pyc | Bin 0 -> 3996 bytes .../__pycache__/dashboards.cpython-312.pyc | Bin 0 -> 5460 bytes .../__pycache__/users.cpython-312.pyc | Bin 0 -> 5470 bytes python/functions/metabase/cards.py | 227 ++++++++++++++++++ python/functions/metabase/client.py | 87 +++++++ python/functions/metabase/dashboards.py | 143 +++++++++++ python/functions/metabase/metabase_auth.md | 46 ++++ .../metabase/metabase_create_card.md | 35 +++ .../metabase/metabase_create_dashboard.md | 32 +++ .../metabase/metabase_create_user.md | 32 +++ .../metabase/metabase_deactivate_user.md | 31 +++ .../metabase/metabase_delete_card.md | 32 +++ .../metabase/metabase_delete_dashboard.md | 32 +++ .../metabase/metabase_execute_card.md | 34 +++ .../metabase/metabase_execute_query.md | 32 +++ .../functions/metabase/metabase_get_card.md | 32 +++ .../metabase/metabase_get_dashboard.md | 33 +++ .../functions/metabase/metabase_get_user.md | 32 +++ .../functions/metabase/metabase_list_cards.md | 32 +++ .../metabase/metabase_list_dashboards.md | 33 +++ .../functions/metabase/metabase_list_users.md | 33 +++ .../metabase/metabase_update_card.md | 31 +++ .../metabase/metabase_update_dashboard.md | 39 +++ .../metabase/metabase_update_user.md | 32 +++ python/functions/metabase/users.py | 153 ++++++++++++ python/pyproject.toml | 9 + python/uv.lock | 91 +++++++ 33 files changed, 1325 insertions(+) create mode 100644 python/.python-version create mode 100644 python/functions/__init__.py create mode 100644 python/functions/__pycache__/__init__.cpython-312.pyc create mode 100644 python/functions/metabase/__init__.py create mode 100644 python/functions/metabase/__pycache__/__init__.cpython-312.pyc create mode 100644 python/functions/metabase/__pycache__/cards.cpython-312.pyc create mode 100644 python/functions/metabase/__pycache__/client.cpython-312.pyc create mode 100644 python/functions/metabase/__pycache__/dashboards.cpython-312.pyc create mode 100644 python/functions/metabase/__pycache__/users.cpython-312.pyc create mode 100644 python/functions/metabase/cards.py create mode 100644 python/functions/metabase/client.py create mode 100644 python/functions/metabase/dashboards.py create mode 100644 python/functions/metabase/metabase_auth.md create mode 100644 python/functions/metabase/metabase_create_card.md create mode 100644 python/functions/metabase/metabase_create_dashboard.md create mode 100644 python/functions/metabase/metabase_create_user.md create mode 100644 python/functions/metabase/metabase_deactivate_user.md create mode 100644 python/functions/metabase/metabase_delete_card.md create mode 100644 python/functions/metabase/metabase_delete_dashboard.md create mode 100644 python/functions/metabase/metabase_execute_card.md create mode 100644 python/functions/metabase/metabase_execute_query.md create mode 100644 python/functions/metabase/metabase_get_card.md create mode 100644 python/functions/metabase/metabase_get_dashboard.md create mode 100644 python/functions/metabase/metabase_get_user.md create mode 100644 python/functions/metabase/metabase_list_cards.md create mode 100644 python/functions/metabase/metabase_list_dashboards.md create mode 100644 python/functions/metabase/metabase_list_users.md create mode 100644 python/functions/metabase/metabase_update_card.md create mode 100644 python/functions/metabase/metabase_update_dashboard.md create mode 100644 python/functions/metabase/metabase_update_user.md create mode 100644 python/functions/metabase/users.py create mode 100644 python/pyproject.toml create mode 100644 python/uv.lock diff --git a/python/.python-version b/python/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/python/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/python/functions/__init__.py b/python/functions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/functions/__pycache__/__init__.cpython-312.pyc b/python/functions/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14d1dde4e37d58b36de84ed4550759f7c3bedff0 GIT binary patch literal 149 zcmX@j%ge<81Sx7KGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!GSSb-&rQ|ODNRl+ z)=$feFG@|%EG{Xk)Gw$k$;i*sPb7v$Eqm);5Z(h+jcZ#}7e5 zqNZ61QBctV5sE~`j`2Cskw|RuX#CFZzT?^XI2!etUF!!QlJ_mfzS75TIdyQJ+u#k0 zSyfR)k8jTojqq znVB}?O8(W%qLCsk!(3K7uc*Z~Zq(b&-dKiFp1j&+d~e?`3U$2iN7|K}Yrj3U_S;iq zsidu~QkP8?@>E8}PCoocepyIe+E!eaU9}<(v-Q!Ys_*g)^`l9cJI!5nk=Lc_O0P@R zdwN zH+mVDx|sckrRg_6klko+JRw4*gm}l!Z8hxolVH6;8K@!(v*0)tTCVH>>N41t`FfKE z$3<#KXOm{ZuE~KANt)zBJlvM1`xrNIq2yD2&)!DkojtPn-r}5pX7|6avrp{o!eRWx bxv=>Y=SM|S!1-lkaDKMkf%DqackQ`9px_z* literal 0 HcmV?d00001 diff --git a/python/functions/metabase/__pycache__/cards.cpython-312.pyc b/python/functions/metabase/__pycache__/cards.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..419861642c5810e216752dcf079dad1293614130 GIT binary patch literal 8335 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 P3^+(;{@aO`6#oAJ8Y2|# literal 0 HcmV?d00001 diff --git a/python/functions/metabase/__pycache__/client.cpython-312.pyc b/python/functions/metabase/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7495427ae1be86bde8fd62c52670f65f314371d GIT binary patch literal 3996 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 DfY%JP literal 0 HcmV?d00001 diff --git a/python/functions/metabase/cards.py b/python/functions/metabase/cards.py new file mode 100644 index 00000000..de22cd3e --- /dev/null +++ b/python/functions/metabase/cards.py @@ -0,0 +1,227 @@ +"""CRUD de cards/preguntas de Metabase y ejecucion de queries.""" + +from .client import MetabaseClient + + +def metabase_list_cards( + client: MetabaseClient, + filter: str = "", + model_id: int = 0, +) -> list[dict]: + """Lista preguntas/cards de Metabase con filtro opcional. + + Endpoint: GET /api/card. No tiene paginacion offset/limit. + + Args: + client: Cliente autenticado. + filter: "all", "mine", "fav", "archived", "recent", "popular", + "database", "table". Vacio = todas. + model_id: ID de database/tabla. Solo aplica con filter "database" o "table". + + Returns: + Lista de dicts, cada uno con: id, name, description, display, + collection_id, database_id, creator_id, archived, dataset_query. + + Example: + >>> cards = metabase_list_cards(client, filter="mine") + >>> for c in cards: + ... print(c["id"], c["name"], c["display"]) + """ + params = {} + if filter: + params["f"] = filter + if model_id > 0: + params["model_id"] = model_id + return client.request("GET", "/api/card", params=params) + + +def metabase_get_card(client: MetabaseClient, card_id: int) -> dict: + """Obtiene los detalles completos de una card/pregunta. + + Endpoint: GET /api/card/:id. + + Args: + client: Cliente autenticado. + card_id: ID de la card. + + Returns: + Dict con: id, name, description, display, dataset_query, + visualization_settings, collection_id, database_id, archived, + creator, created_at, updated_at. + + Example: + >>> card = metabase_get_card(client, 42) + >>> print(card["name"], card["display"]) + >>> print(card["dataset_query"]["native"]["query"]) # SQL + """ + return client.request("GET", f"/api/card/{card_id}") + + +def metabase_create_card( + client: MetabaseClient, + name: str, + dataset_query: dict, + display: str = "table", + collection_id: int = 0, + description: str = "", +) -> dict: + """Crea una nueva card/pregunta en Metabase. + + Endpoint: POST /api/card. + + Args: + client: Cliente autenticado. + name: Nombre de la pregunta. + dataset_query: Query de la card. Estructura: + SQL nativo: {"database": 1, "type": "native", "native": {"query": "SELECT ..."}} + MBQL: {"database": 1, "type": "query", "query": {"source-table": 4, ...}} + display: Tipo de visualizacion: "table", "bar", "line", "pie", "scalar", + "area", "row", "combo", "funnel", "scatter", "waterfall", etc. + collection_id: ID de coleccion destino. 0 = root. + description: Descripcion opcional. + + Returns: + Dict con la card creada. + + Example: + >>> card = metabase_create_card(client, "Revenue by Month", { + ... "database": 1, + ... "type": "native", + ... "native": {"query": "SELECT date_trunc('month', created_at), SUM(total) FROM orders GROUP BY 1"}, + ... }, display="line", description="Monthly revenue trend") + """ + body: dict = { + "name": name, + "dataset_query": dataset_query, + "display": display, + "visualization_settings": {}, + } + if collection_id > 0: + body["collection_id"] = collection_id + if description: + body["description"] = description + return client.request("POST", "/api/card", json=body) + + +def metabase_update_card(client: MetabaseClient, card_id: int, **fields) -> dict: + """Actualiza campos de una card/pregunta en Metabase. + + Endpoint: PUT /api/card/:id. Solo se modifican los campos pasados. + + Args: + client: Cliente autenticado. + card_id: ID de la card. + **fields: Campos a actualizar. Validos: + name (str), description (str), display (str), + dataset_query (dict), visualization_settings (dict), + collection_id (int), archived (bool), + enable_embedding (bool), embedding_params (dict). + + Returns: + Dict con la card actualizada. + + Example: + >>> metabase_update_card(client, 42, name="Updated Name", archived=True) + >>> metabase_update_card(client, 42, dataset_query={ + ... "database": 1, "type": "native", + ... "native": {"query": "SELECT * FROM users LIMIT 100"}, + ... }) + """ + return client.request("PUT", f"/api/card/{card_id}", json=fields) + + +def metabase_delete_card(client: MetabaseClient, card_id: int) -> None: + """Elimina permanentemente una card/pregunta. + + Endpoint: DELETE /api/card/:id. IRREVERSIBLE. + Para soft-delete preferir: metabase_update_card(client, card_id, archived=True) + + Args: + client: Cliente autenticado. + card_id: ID de la card a eliminar. + + Example: + >>> metabase_delete_card(client, 42) + >>> # Preferir soft-delete: metabase_update_card(client, 42, archived=True) + """ + client.request("DELETE", f"/api/card/{card_id}") + + +def metabase_execute_card( + client: MetabaseClient, + card_id: int, + parameters: list[dict] | None = None, +) -> dict: + """Ejecuta la query de una card/pregunta guardada. + + Endpoint: POST /api/card/:id/query. + + Args: + client: Cliente autenticado. + card_id: ID de la card a ejecutar. + parameters: Parametros para queries parametrizadas. Cada parametro: + {"type": "category", "target": ["variable", ["template-tag", "tag"]], "value": "val"} + + Returns: + Dict con resultados: + - status: "completed" o "failed" + - row_count: numero de filas + - running_time: tiempo en ms + - data.columns: nombres de columnas + - data.rows: filas de datos (lista de listas) + - data.cols: metadata de columnas + - data.native_form.query: SQL ejecutado + + Example: + >>> result = metabase_execute_card(client, 42) + >>> for row in result["data"]["rows"]: + ... print(row) + >>> # Con parametros: + >>> result = metabase_execute_card(client, 42, parameters=[ + ... {"type": "category", "target": ["variable", ["template-tag", "status"]], "value": "active"}, + ... ]) + """ + body = {} + if parameters: + body["parameters"] = parameters + return client.request("POST", f"/api/card/{card_id}/query", json=body or None) + + +def metabase_execute_query( + client: MetabaseClient, + database_id: int, + sql: str, + max_results: int = 0, +) -> dict: + """Ejecuta una query SQL ad-hoc sin guardarla como card. + + Endpoint: POST /api/dataset. Util para exploracion rapida y consultas + que no necesitan persistirse. + + Args: + client: Cliente autenticado. + database_id: ID de la database en Metabase. + sql: Query SQL a ejecutar. + max_results: Limite de filas. 0 = default 2000. + + Returns: + Dict con misma estructura que metabase_execute_card: + data.columns, data.rows, row_count, running_time, status. + + Example: + >>> result = metabase_execute_query(client, 1, "SELECT * FROM users LIMIT 10") + >>> print(f"{result['row_count']} filas en {result['running_time']}ms") + >>> for row in result["data"]["rows"]: + ... print(row) + """ + body: dict = { + "database": database_id, + "type": "native", + "native": {"query": sql}, + } + if max_results > 0: + body["constraints"] = { + "max-results": max_results, + "max-results-bare-rows": max_results, + } + return client.request("POST", "/api/dataset", json=body) diff --git a/python/functions/metabase/client.py b/python/functions/metabase/client.py new file mode 100644 index 00000000..a3b4ceaf --- /dev/null +++ b/python/functions/metabase/client.py @@ -0,0 +1,87 @@ +"""Cliente base para la API REST de Metabase.""" + +import httpx + + +class MetabaseClient: + """Cliente HTTP para una instancia Metabase. + + Attributes: + base_url: URL base sin trailing slash (ej: "http://localhost:3000"). + token: Session token o API key. + _http: Cliente httpx reutilizable con headers de auth. + """ + + def __init__(self, base_url: str, token: str) -> None: + self.base_url = base_url.rstrip("/") + self.token = token + self._http = httpx.Client( + base_url=self.base_url, + headers={ + "Content-Type": "application/json", + "X-Metabase-Session": token, + }, + timeout=30.0, + ) + + def request(self, method: str, path: str, **kwargs) -> dict | list | None: + """Ejecuta una peticion HTTP contra la API de Metabase. + + Args: + method: HTTP method (GET, POST, PUT, DELETE). + path: Ruta relativa (ej: "/api/user"). + **kwargs: Argumentos extra para httpx (json, params, etc.). + + Returns: + Respuesta deserializada como dict/list, o None si el body esta vacio. + + Raises: + httpx.HTTPStatusError: Si el status code no es 2xx. + """ + resp = self._http.request(method, path, **kwargs) + resp.raise_for_status() + if not resp.content: + return None + return resp.json() + + def close(self) -> None: + """Cierra el cliente HTTP.""" + self._http.close() + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +def metabase_auth(base_url: str, email: str, password: str) -> MetabaseClient: + """Autentica contra Metabase con email y password. + + Crea una sesion via POST /api/session y retorna un MetabaseClient + con el session token listo para usar. El token expira en 14 dias + por defecto (configurable con MAX_SESSION_AGE en Metabase). + + Args: + base_url: URL base de la instancia (ej: "http://localhost:3000"). + email: Email del usuario Metabase. + password: Password del usuario. + + Returns: + MetabaseClient autenticado con session token. + + Raises: + httpx.HTTPStatusError: Si las credenciales son invalidas (401) + o hay rate limiting. + + Example: + >>> client = metabase_auth("http://localhost:3000", "admin@example.com", "pass") + >>> # client listo para usar con todas las funciones CRUD + """ + resp = httpx.post( + f"{base_url.rstrip('/')}/api/session", + json={"username": email, "password": password}, + ) + resp.raise_for_status() + token = resp.json()["id"] + return MetabaseClient(base_url, token) diff --git a/python/functions/metabase/dashboards.py b/python/functions/metabase/dashboards.py new file mode 100644 index 00000000..f298c7ae --- /dev/null +++ b/python/functions/metabase/dashboards.py @@ -0,0 +1,143 @@ +"""CRUD de dashboards de Metabase.""" + +from .client import MetabaseClient + + +def metabase_list_dashboards( + client: MetabaseClient, + filter: str = "", +) -> list[dict]: + """Lista dashboards de Metabase con filtro opcional. + + Endpoint: GET /api/dashboard. Retorna dashboards resumidos (sin dashcards). + + Args: + client: Cliente autenticado. + filter: "all", "mine" o "archived". Vacio = todas. + + Returns: + Lista de dicts con: id, name, description, collection_id, + creator_id, archived, created_at. + + Example: + >>> dashboards = metabase_list_dashboards(client, filter="mine") + >>> for d in dashboards: + ... print(d["id"], d["name"]) + """ + params = {} + if filter: + params["f"] = filter + return client.request("GET", "/api/dashboard", params=params) + + +def metabase_get_dashboard(client: MetabaseClient, dashboard_id: int) -> dict: + """Obtiene un dashboard completo incluyendo sus cards. + + Endpoint: GET /api/dashboard/:id. + + Args: + client: Cliente autenticado. + dashboard_id: ID del dashboard. + + Returns: + Dict con: id, name, description, dashcards (lista de cards posicionadas), + parameters (filtros), tabs, collection_id, archived. + + Cada dashcard tiene: id, card_id, card (objeto completo), size_x, size_y, + col, row, dashboard_tab_id, parameter_mappings, visualization_settings. + + Example: + >>> dash = metabase_get_dashboard(client, 1) + >>> for dc in dash["dashcards"]: + ... print(f"Card {dc['card_id']} at ({dc['col']}, {dc['row']})") + """ + return client.request("GET", f"/api/dashboard/{dashboard_id}") + + +def metabase_create_dashboard( + client: MetabaseClient, + name: str, + description: str = "", + collection_id: int = 0, +) -> dict: + """Crea un nuevo dashboard vacio en Metabase. + + Endpoint: POST /api/dashboard. + Para agregar cards usar metabase_update_dashboard con dashcards. + + Args: + client: Cliente autenticado. + name: Nombre del dashboard. + description: Descripcion opcional. + collection_id: Coleccion destino. 0 = root. + + Returns: + Dict con el dashboard creado. + + Example: + >>> dash = metabase_create_dashboard(client, "Sales Overview", "KPIs de ventas") + >>> # Agregar cards: + >>> metabase_update_dashboard(client, dash["id"], dashcards=[ + ... {"id": -1, "card_id": 42, "size_x": 6, "size_y": 4, "col": 0, "row": 0}, + ... ]) + """ + body: dict = {"name": name} + if description: + body["description"] = description + if collection_id > 0: + body["collection_id"] = collection_id + return client.request("POST", "/api/dashboard", json=body) + + +def metabase_update_dashboard(client: MetabaseClient, dashboard_id: int, **fields) -> dict: + """Actualiza un dashboard incluyendo metadata, cards y tabs. + + Endpoint: PUT /api/dashboard/:id. + + El campo dashcards representa el ESTADO COMPLETO DESEADO del dashboard: + - Agregar card: incluirla con ID negativo (-1, -2, etc.) + - Actualizar card existente: incluirla con su ID positivo + - Eliminar card: omitirla del array + + Args: + client: Cliente autenticado. + dashboard_id: ID del dashboard. + **fields: Campos a actualizar. Validos: + name (str), description (str), archived (bool), + dashcards (list[dict]), tabs (list[dict]), + parameters (list[dict]), collection_id (int). + + Returns: + Dict con el dashboard actualizado. + + Example: + >>> # Cambiar nombre + >>> metabase_update_dashboard(client, 1, name="Updated Name") + >>> + >>> # Agregar card (primero obtener existentes) + >>> dash = metabase_get_dashboard(client, 1) + >>> cards = list(dash["dashcards"]) + >>> cards.append({"id": -1, "card_id": 55, "size_x": 6, "size_y": 4, "col": 0, "row": 0}) + >>> metabase_update_dashboard(client, 1, dashcards=cards) + >>> + >>> # Archivar (soft-delete) + >>> metabase_update_dashboard(client, 1, archived=True) + """ + return client.request("PUT", f"/api/dashboard/{dashboard_id}", json=fields) + + +def metabase_delete_dashboard(client: MetabaseClient, dashboard_id: int) -> None: + """Elimina permanentemente un dashboard. + + Endpoint: DELETE /api/dashboard/:id. IRREVERSIBLE. + Para soft-delete preferir: metabase_update_dashboard(client, id, archived=True) + + Args: + client: Cliente autenticado. + dashboard_id: ID del dashboard a eliminar. + + Example: + >>> metabase_delete_dashboard(client, 1) + >>> # Preferir: metabase_update_dashboard(client, 1, archived=True) + """ + client.request("DELETE", f"/api/dashboard/{dashboard_id}") diff --git a/python/functions/metabase/metabase_auth.md b/python/functions/metabase/metabase_auth.md new file mode 100644 index 00000000..2b3ef8f9 --- /dev/null +++ b/python/functions/metabase/metabase_auth.md @@ -0,0 +1,46 @@ +--- +name: metabase_auth +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_auth(base_url: str, email: str, password: str) -> MetabaseClient" +description: "Autentica contra la API de Metabase con email y password. Retorna un MetabaseClient con session token valido por 14 dias. Endpoint: POST /api/session." +tags: [metabase, auth, session, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/client.py" +--- + +## Ejemplo + +```python +from functions.metabase import metabase_auth + +client = metabase_auth("http://localhost:3000", "admin@example.com", "pass") +# client listo para usar con todas las funciones CRUD + +# Alternativa con API key: +from functions.metabase import MetabaseClient +client = MetabaseClient("http://localhost:3000", "mb_api_key_xxxxx") +``` + +## Notas + +Dos formas de obtener un client: +- `metabase_auth()`: login con email/password, obtiene session token via POST /api/session +- `MetabaseClient(base_url, api_key)`: constructor directo con API key (recomendado para automatizacion) + +El client es un context manager: `with metabase_auth(...) as client:` + +Errores comunes: +- 401: credenciales invalidas +- Rate limiting en intentos fallidos de login diff --git a/python/functions/metabase/metabase_create_card.md b/python/functions/metabase/metabase_create_card.md new file mode 100644 index 00000000..c99fc347 --- /dev/null +++ b/python/functions/metabase/metabase_create_card.md @@ -0,0 +1,35 @@ +--- +name: metabase_create_card +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_create_card(client: MetabaseClient, name: str, dataset_query: dict, display: str = 'table', collection_id: int = 0, description: str = '') -> dict" +description: "Crea una card/pregunta en Metabase con query SQL nativa o MBQL. Endpoint: POST /api/card." +tags: [metabase, card, question, create, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +card = metabase_create_card(client, "Revenue", { + "database": 1, "type": "native", + "native": {"query": "SELECT SUM(total) FROM orders"}, +}, display="scalar") +``` + +## Notas + +dataset_query SQL nativo: `{"database": id, "type": "native", "native": {"query": "..."}}` +dataset_query MBQL: `{"database": id, "type": "query", "query": {"source-table": id, ...}}` diff --git a/python/functions/metabase/metabase_create_dashboard.md b/python/functions/metabase/metabase_create_dashboard.md new file mode 100644 index 00000000..d715227e --- /dev/null +++ b/python/functions/metabase/metabase_create_dashboard.md @@ -0,0 +1,32 @@ +--- +name: metabase_create_dashboard +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_create_dashboard(client: MetabaseClient, name: str, description: str = '', collection_id: int = 0) -> dict" +description: "Crea dashboard vacio en Metabase. Para agregar cards usar metabase_update_dashboard con dashcards. Endpoint: POST /api/dashboard." +tags: [metabase, dashboard, create, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/dashboards.py" +--- + +## Ejemplo + +```python +dash = metabase_create_dashboard(client, "Sales Overview", "KPIs") +# Agregar cards con metabase_update_dashboard +``` + +## Notas + +Se crea vacio. Agregar cards con metabase_update_dashboard(dashcards=[...]). diff --git a/python/functions/metabase/metabase_create_user.md b/python/functions/metabase/metabase_create_user.md new file mode 100644 index 00000000..688d0890 --- /dev/null +++ b/python/functions/metabase/metabase_create_user.md @@ -0,0 +1,32 @@ +--- +name: metabase_create_user +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_create_user(client: MetabaseClient, first_name: str, last_name: str, email: str, password: str = '', group_ids: list[int] | None = None) -> dict" +description: "Crea un nuevo usuario en Metabase. Sin password envia invitacion por email. Requiere superusuario. Endpoint: POST /api/user." +tags: [metabase, user, create, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/users.py" +--- + +## Ejemplo + +```python +user = metabase_create_user(client, "John", "Doe", "john@example.com", "pass123") +user = metabase_create_user(client, "Jane", "Smith", "jane@example.com", group_ids=[1, 3]) +``` + +## Notas + +Email debe ser unico. Error 400 si ya existe. diff --git a/python/functions/metabase/metabase_deactivate_user.md b/python/functions/metabase/metabase_deactivate_user.md new file mode 100644 index 00000000..21ccd678 --- /dev/null +++ b/python/functions/metabase/metabase_deactivate_user.md @@ -0,0 +1,31 @@ +--- +name: metabase_deactivate_user +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_deactivate_user(client: MetabaseClient, user_id: int) -> None" +description: "Desactiva (soft-delete) un usuario en Metabase. Reactivar con PUT /api/user/:id/reactivate. Endpoint: DELETE /api/user/:id." +tags: [metabase, user, delete, deactivate, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/users.py" +--- + +## Ejemplo + +```python +metabase_deactivate_user(client, 5) +``` + +## Notas + +Soft-delete. El usuario se puede reactivar. diff --git a/python/functions/metabase/metabase_delete_card.md b/python/functions/metabase/metabase_delete_card.md new file mode 100644 index 00000000..eea33cf3 --- /dev/null +++ b/python/functions/metabase/metabase_delete_card.md @@ -0,0 +1,32 @@ +--- +name: metabase_delete_card +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_delete_card(client: MetabaseClient, card_id: int) -> None" +description: "Elimina permanentemente una card/pregunta. IRREVERSIBLE. Preferir archived=True. Endpoint: DELETE /api/card/:id." +tags: [metabase, card, question, delete, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +metabase_delete_card(client, 42) +# Preferir: metabase_update_card(client, 42, archived=True) +``` + +## Notas + +IRREVERSIBLE. Preferir soft-delete con metabase_update_card(archived=True). diff --git a/python/functions/metabase/metabase_delete_dashboard.md b/python/functions/metabase/metabase_delete_dashboard.md new file mode 100644 index 00000000..c4bedb14 --- /dev/null +++ b/python/functions/metabase/metabase_delete_dashboard.md @@ -0,0 +1,32 @@ +--- +name: metabase_delete_dashboard +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_delete_dashboard(client: MetabaseClient, dashboard_id: int) -> None" +description: "Elimina permanentemente un dashboard. IRREVERSIBLE. Preferir archived=True. Endpoint: DELETE /api/dashboard/:id." +tags: [metabase, dashboard, delete, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/dashboards.py" +--- + +## Ejemplo + +```python +metabase_delete_dashboard(client, 1) +# Preferir: metabase_update_dashboard(client, 1, archived=True) +``` + +## Notas + +IRREVERSIBLE. Preferir soft-delete con metabase_update_dashboard(archived=True). diff --git a/python/functions/metabase/metabase_execute_card.md b/python/functions/metabase/metabase_execute_card.md new file mode 100644 index 00000000..0c2dbad7 --- /dev/null +++ b/python/functions/metabase/metabase_execute_card.md @@ -0,0 +1,34 @@ +--- +name: metabase_execute_card +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_execute_card(client: MetabaseClient, card_id: int, parameters: list[dict] | None = None) -> dict" +description: "Ejecuta la query de una card guardada y retorna resultados con columnas y filas. Soporta parametros. Endpoint: POST /api/card/:id/query." +tags: [metabase, card, question, execute, query, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +result = metabase_execute_card(client, 42) +for row in result["data"]["rows"]: + print(row) +``` + +## Notas + +Respuesta: status, row_count, running_time, data.columns, data.rows, data.cols. +Limite default: 2000 filas. Para ad-hoc sin card usar metabase_execute_query. diff --git a/python/functions/metabase/metabase_execute_query.md b/python/functions/metabase/metabase_execute_query.md new file mode 100644 index 00000000..5480c36f --- /dev/null +++ b/python/functions/metabase/metabase_execute_query.md @@ -0,0 +1,32 @@ +--- +name: metabase_execute_query +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_execute_query(client: MetabaseClient, database_id: int, sql: str, max_results: int = 0) -> dict" +description: "Ejecuta query SQL ad-hoc contra Metabase sin guardarla como card. Util para exploracion rapida. Endpoint: POST /api/dataset." +tags: [metabase, query, execute, sql, dataset, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +result = metabase_execute_query(client, 1, "SELECT * FROM users LIMIT 10") +print(f"{result['row_count']} filas en {result['running_time']}ms") +``` + +## Notas + +Misma respuesta que metabase_execute_card. Default 2000 filas, override con max_results. diff --git a/python/functions/metabase/metabase_get_card.md b/python/functions/metabase/metabase_get_card.md new file mode 100644 index 00000000..f568546d --- /dev/null +++ b/python/functions/metabase/metabase_get_card.md @@ -0,0 +1,32 @@ +--- +name: metabase_get_card +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_get_card(client: MetabaseClient, card_id: int) -> dict" +description: "Obtiene detalles completos de una card/pregunta de Metabase incluyendo query, visualizacion y metadata. Endpoint: GET /api/card/:id." +tags: [metabase, card, question, get, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +card = metabase_get_card(client, 42) +print(card["name"], card["display"]) +``` + +## Notas + +Error 404 si no existe. diff --git a/python/functions/metabase/metabase_get_dashboard.md b/python/functions/metabase/metabase_get_dashboard.md new file mode 100644 index 00000000..073d21df --- /dev/null +++ b/python/functions/metabase/metabase_get_dashboard.md @@ -0,0 +1,33 @@ +--- +name: metabase_get_dashboard +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_get_dashboard(client: MetabaseClient, dashboard_id: int) -> dict" +description: "Obtiene dashboard completo con dashcards (cards posicionadas), tabs y parametros. Endpoint: GET /api/dashboard/:id." +tags: [metabase, dashboard, get, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/dashboards.py" +--- + +## Ejemplo + +```python +dash = metabase_get_dashboard(client, 1) +for dc in dash["dashcards"]: + print(f"Card {dc['card_id']} at ({dc['col']}, {dc['row']})") +``` + +## Notas + +Cada dashcard tiene: id, card_id, card, size_x, size_y, col, row, dashboard_tab_id, parameter_mappings. diff --git a/python/functions/metabase/metabase_get_user.md b/python/functions/metabase/metabase_get_user.md new file mode 100644 index 00000000..8dcda611 --- /dev/null +++ b/python/functions/metabase/metabase_get_user.md @@ -0,0 +1,32 @@ +--- +name: metabase_get_user +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_get_user(client: MetabaseClient, user_id: int) -> dict" +description: "Obtiene los detalles de un usuario de Metabase por su ID. Endpoint: GET /api/user/:id." +tags: [metabase, user, get, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/users.py" +--- + +## Ejemplo + +```python +user = metabase_get_user(client, 1) +print(user["email"], user["is_superuser"]) +``` + +## Notas + +Error 404 si el usuario no existe. diff --git a/python/functions/metabase/metabase_list_cards.md b/python/functions/metabase/metabase_list_cards.md new file mode 100644 index 00000000..cb903582 --- /dev/null +++ b/python/functions/metabase/metabase_list_cards.md @@ -0,0 +1,32 @@ +--- +name: metabase_list_cards +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_list_cards(client: MetabaseClient, filter: str = '', model_id: int = 0) -> list[dict]" +description: "Lista preguntas/cards de Metabase. Filtros: all, mine, fav, archived, recent, popular, database, table. Endpoint: GET /api/card." +tags: [metabase, card, question, list, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +cards = metabase_list_cards(client, filter="mine") +cards = metabase_list_cards(client, filter="database", model_id=1) +``` + +## Notas + +No tiene paginacion offset/limit. Retorna todas las cards que coinciden. diff --git a/python/functions/metabase/metabase_list_dashboards.md b/python/functions/metabase/metabase_list_dashboards.md new file mode 100644 index 00000000..ae87396e --- /dev/null +++ b/python/functions/metabase/metabase_list_dashboards.md @@ -0,0 +1,33 @@ +--- +name: metabase_list_dashboards +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_list_dashboards(client: MetabaseClient, filter: str = '') -> list[dict]" +description: "Lista dashboards de Metabase. Filtros: all, mine, archived. Retorna resumen sin dashcards. Endpoint: GET /api/dashboard." +tags: [metabase, dashboard, list, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/dashboards.py" +--- + +## Ejemplo + +```python +dashboards = metabase_list_dashboards(client, filter="mine") +for d in dashboards: + print(d["id"], d["name"]) +``` + +## Notas + +Para ver cards de un dashboard usar metabase_get_dashboard. diff --git a/python/functions/metabase/metabase_list_users.md b/python/functions/metabase/metabase_list_users.md new file mode 100644 index 00000000..8a8361f5 --- /dev/null +++ b/python/functions/metabase/metabase_list_users.md @@ -0,0 +1,33 @@ +--- +name: metabase_list_users +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_list_users(client: MetabaseClient, status: str = '', query: str = '', limit: int = 0, offset: int = 0) -> dict" +description: "Lista usuarios de Metabase con filtros opcionales por estado, nombre/email y paginacion. Endpoint: GET /api/user." +tags: [metabase, user, list, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/users.py" +--- + +## Ejemplo + +```python +users = metabase_list_users(client, status="active", query="john@") +for u in users["data"]: + print(u["email"], u["first_name"]) +``` + +## Notas + +Retorna dict paginado con data, total, limit, offset. diff --git a/python/functions/metabase/metabase_update_card.md b/python/functions/metabase/metabase_update_card.md new file mode 100644 index 00000000..8c046148 --- /dev/null +++ b/python/functions/metabase/metabase_update_card.md @@ -0,0 +1,31 @@ +--- +name: metabase_update_card +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_update_card(client: MetabaseClient, card_id: int, **fields) -> dict" +description: "Actualiza campos de una card/pregunta via kwargs. Campos: name, description, display, dataset_query, collection_id, archived. Endpoint: PUT /api/card/:id." +tags: [metabase, card, question, update, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/cards.py" +--- + +## Ejemplo + +```python +metabase_update_card(client, 42, name="New Name", archived=True) +``` + +## Notas + +Soft-delete con `archived=True`. Para delete permanente usar metabase_delete_card. diff --git a/python/functions/metabase/metabase_update_dashboard.md b/python/functions/metabase/metabase_update_dashboard.md new file mode 100644 index 00000000..17e1379b --- /dev/null +++ b/python/functions/metabase/metabase_update_dashboard.md @@ -0,0 +1,39 @@ +--- +name: metabase_update_dashboard +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_update_dashboard(client: MetabaseClient, dashboard_id: int, **fields) -> dict" +description: "Actualiza dashboard incluyendo metadata, cards y tabs via kwargs. dashcards es el estado completo deseado: nuevas con ID negativo, existentes con positivo, omitidas se eliminan. Endpoint: PUT /api/dashboard/:id." +tags: [metabase, dashboard, update, cards, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/dashboards.py" +--- + +## Ejemplo + +```python +# Agregar card +dash = metabase_get_dashboard(client, 1) +cards = list(dash["dashcards"]) +cards.append({"id": -1, "card_id": 55, "size_x": 6, "size_y": 4, "col": 0, "row": 0}) +metabase_update_dashboard(client, 1, dashcards=cards) + +# Archivar +metabase_update_dashboard(client, 1, archived=True) +``` + +## Notas + +dashcards = estado completo. ID negativo = nueva, positivo = existente, omitida = eliminada. +Campos: name, description, archived, dashcards, tabs, parameters, collection_id. diff --git a/python/functions/metabase/metabase_update_user.md b/python/functions/metabase/metabase_update_user.md new file mode 100644 index 00000000..78e0f4c1 --- /dev/null +++ b/python/functions/metabase/metabase_update_user.md @@ -0,0 +1,32 @@ +--- +name: metabase_update_user +kind: function +lang: py +domain: infra +version: "1.0.0" +purity: impure +signature: "def metabase_update_user(client: MetabaseClient, user_id: int, **fields) -> dict" +description: "Actualiza campos de un usuario en Metabase via keyword arguments. Campos: first_name, last_name, email, is_superuser, group_ids, locale. Endpoint: PUT /api/user/:id." +tags: [metabase, user, update, api, python] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [httpx] +tested: false +tests: [] +test_file_path: "" +file_path: "python/functions/metabase/users.py" +--- + +## Ejemplo + +```python +metabase_update_user(client, 5, first_name="Jane", is_superuser=True) +metabase_update_user(client, 5, group_ids=[1, 3, 5]) +``` + +## Notas + +Solo se modifican los campos pasados como kwargs. diff --git a/python/functions/metabase/users.py b/python/functions/metabase/users.py new file mode 100644 index 00000000..4133b63b --- /dev/null +++ b/python/functions/metabase/users.py @@ -0,0 +1,153 @@ +"""CRUD de usuarios de Metabase.""" + +from .client import MetabaseClient + + +def metabase_list_users( + client: MetabaseClient, + status: str = "", + query: str = "", + limit: int = 0, + offset: int = 0, +) -> dict: + """Lista usuarios de Metabase con filtros opcionales. + + Endpoint: GET /api/user. Requiere permisos de superusuario. + + Args: + client: Cliente autenticado. + status: "active" (default), "deactivated" o "all". + query: Filtro por nombre o email. + limit: Tamanio de pagina (0 = default Metabase). + offset: Offset para paginacion. + + Returns: + Dict con estructura paginada: + - data: lista de usuarios (id, email, first_name, last_name, is_superuser, etc.) + - total: numero total de usuarios que coinciden + - limit: tamanio de pagina usado + - offset: offset usado + + Example: + >>> users = metabase_list_users(client, status="active", query="john@") + >>> for u in users["data"]: + ... print(u["email"], u["first_name"]) + """ + params = {} + if status: + params["status"] = status + if query: + params["query"] = query + if limit > 0: + params["limit"] = limit + if offset > 0: + params["offset"] = offset + return client.request("GET", "/api/user", params=params) + + +def metabase_get_user(client: MetabaseClient, user_id: int) -> dict: + """Obtiene un usuario de Metabase por su ID. + + Endpoint: GET /api/user/:id. + + Args: + client: Cliente autenticado. + user_id: ID numerico del usuario. + + Returns: + Dict con datos del usuario: id, email, first_name, last_name, + is_superuser, is_active, common_name, date_joined, last_login, + group_ids, locale. + + Raises: + httpx.HTTPStatusError: 404 si el usuario no existe. + + Example: + >>> user = metabase_get_user(client, 1) + >>> print(user["email"], user["is_superuser"]) + """ + return client.request("GET", f"/api/user/{user_id}") + + +def metabase_create_user( + client: MetabaseClient, + first_name: str, + last_name: str, + email: str, + password: str = "", + group_ids: list[int] | None = None, +) -> dict: + """Crea un nuevo usuario en Metabase. + + Endpoint: POST /api/user. Requiere permisos de superusuario. + + Args: + client: Cliente autenticado con permisos admin. + first_name: Nombre del usuario. + last_name: Apellido del usuario. + email: Email unico del usuario. + password: Password. Vacio = Metabase envia invitacion por email. + group_ids: IDs de grupos a asignar. None = solo grupo default. + + Returns: + Dict con el usuario creado (mismos campos que metabase_get_user). + + Raises: + httpx.HTTPStatusError: 400 si el email ya existe. + + Example: + >>> user = metabase_create_user(client, "John", "Doe", "john@example.com", "pass123") + >>> print(user["id"]) + """ + body: dict = { + "first_name": first_name, + "last_name": last_name, + "email": email, + } + if password: + body["password"] = password + if group_ids: + body["group_ids"] = group_ids + return client.request("POST", "/api/user", json=body) + + +def metabase_update_user(client: MetabaseClient, user_id: int, **fields) -> dict: + """Actualiza campos de un usuario en Metabase. + + Endpoint: PUT /api/user/:id. Requiere permisos de superusuario. + Solo se modifican los campos pasados como keyword arguments. + + Args: + client: Cliente autenticado con permisos admin. + user_id: ID del usuario a actualizar. + **fields: Campos a actualizar. Validos: + first_name (str), last_name (str), email (str), + is_superuser (bool), group_ids (list[int]), + locale (str), login_attributes (dict). + + Returns: + Dict con el usuario actualizado. + + Example: + >>> user = metabase_update_user(client, 5, first_name="Jane", is_superuser=True) + >>> user = metabase_update_user(client, 5, group_ids=[1, 3, 5]) + """ + return client.request("PUT", f"/api/user/{user_id}", json=fields) + + +def metabase_deactivate_user(client: MetabaseClient, user_id: int) -> None: + """Desactiva (soft-delete) un usuario en Metabase. + + Endpoint: DELETE /api/user/:id. Requiere permisos de superusuario. + El usuario no se elimina permanentemente, solo se marca como inactivo. + Para reactivar: PUT /api/user/:id/reactivate. + + Args: + client: Cliente autenticado con permisos admin. + user_id: ID del usuario a desactivar. + + Example: + >>> metabase_deactivate_user(client, 5) + >>> # Para ver desactivados: metabase_list_users(client, status="deactivated") + """ + client.request("DELETE", f"/api/user/{user_id}") diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 00000000..03b6a2b3 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "fn-registry-python" +version = "0.1.0" +description = "Funciones Python del fn-registry: Metabase API, ML, utilidades" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "httpx", +] diff --git a/python/uv.lock b/python/uv.lock new file mode 100644 index 00000000..4ab33db3 --- /dev/null +++ b/python/uv.lock @@ -0,0 +1,91 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "fn-registry-python" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "httpx" }, +] + +[package.metadata] +requires-dist = [{ name = "httpx" }] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From cd7fcd8cca1dc2afba05880768f49fde407dda7b Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 20:32:40 +0100 Subject: [PATCH 4/5] feat: setup frontend con pnpm, vite, react, tailwind y shadcn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inicializa directorio frontend/ con stack React moderno: pnpm + vite 8 + react 19 + tailwind v4 + shadcn v4 (base-nova). Estructura functions/core (TS puro) y functions/ui (componentes React). El indexer descubre frontend/functions/ y frontend/types/ automáticamente. Elimina functions/components/ (legacy) y actualiza referencias en CLAUDE.md y template de componentes. --- .claude/CLAUDE.md | 26 +- docs/templates/component.md | 2 +- frontend/components.json | 25 + .../geist-cyrillic-wght-normal-CHSlOQsW.woff2 | Bin 0 -> 14692 bytes ...geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 | Bin 0 -> 15308 bytes .../geist-latin-wght-normal-Dm3htQBi.woff2 | Bin 0 -> 28400 bytes frontend/dist/assets/index--tYiELXc.css | 2 + frontend/dist/assets/index-BnivQHi-.js | 1 + frontend/dist/index.html | 13 + frontend/index.html | 12 + frontend/package.json | 37 + frontend/pnpm-lock.yaml | 3609 +++++++++++++++++ frontend/src/components/ui/button.tsx | 58 + frontend/src/globals.css | 130 + frontend/src/lib/utils.ts | 6 + frontend/src/main.tsx | 1 + frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 25 + frontend/vite.config.ts | 13 + functions/components/.gitkeep | 0 20 files changed, 3954 insertions(+), 7 deletions(-) create mode 100644 frontend/components.json create mode 100644 frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 create mode 100644 frontend/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 create mode 100644 frontend/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 create mode 100644 frontend/dist/assets/index--tYiELXc.css create mode 100644 frontend/dist/assets/index-BnivQHi-.js create mode 100644 frontend/dist/index.html create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/globals.css create mode 100644 frontend/src/lib/utils.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts delete mode 100644 functions/components/.gitkeep diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 7d0c29f7..be1aa3e8 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -10,13 +10,23 @@ Registry personal de codigo reutilizable con busqueda FTS. Diseñado para compos --- -## Explorar el registry (USAR SIEMPRE) +## Explorar el registry (OBLIGATORIO) -Antes de escribir codigo, SIEMPRE consulta registry.db para evitar duplicados y descubrir funciones reutilizables. +**SIEMPRE** consulta registry.db antes de escribir codigo, crear funciones, o responder sobre el registry. No uses grep/glob sobre archivos .go/.md — la BD es la fuente de verdad. + +**La BD contiene el codigo y la documentacion completa** de cada funcion y tipo en los campos `code`, `documentation` y `notes`. Estos campos tambien estan indexados en FTS5, asi que puedes buscar dentro del codigo y la documentacion directamente. Para leer el codigo de una funcion: `SELECT code FROM functions WHERE id = '...'`. Para leer su documentacion: `SELECT documentation FROM functions WHERE id = '...'`. + +**Busquedas FTS5 obligatorias:** Usa SIEMPRE la tabla FTS5 para buscar tanto por `name` como por `description`. Esto encuentra coincidencias parciales y similares que una busqueda exacta perderia. Usa operadores FTS5: `OR` para ampliar, `*` para prefijos, `NEAR` para proximidad. ```bash -# FTS5 -sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'slice') ORDER BY name;" +# Busqueda FTS5 por nombre Y descripcion (USAR SIEMPRE ESTE PATRON) +sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:slice OR description:slice') ORDER BY name;" + +# FTS5 con prefijo (encuentra slice, slicing, sliced...) +sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:slic* OR description:slic*') ORDER BY name;" + +# FTS5 en tipos +sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE id IN (SELECT id FROM types_fts WHERE types_fts MATCH 'name:result OR description:result') ORDER BY name;" # Por dominio sqlite3 registry.db "SELECT id, purity, signature FROM functions WHERE domain = 'finance' ORDER BY name;" @@ -24,7 +34,7 @@ sqlite3 registry.db "SELECT id, purity, signature FROM functions WHERE domain = # Puras de un dominio sqlite3 registry.db "SELECT id, signature FROM functions WHERE domain = 'core' AND purity = 'pure' ORDER BY name;" -# Tipos +# Tipos por dominio sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE domain = 'cybersecurity';" # Dependencias @@ -37,6 +47,8 @@ sqlite3 registry.db "SELECT id, kind, status, title FROM proposals WHERE status sqlite3 registry.db ".schema" ``` +**Regla:** Si necesitas saber si algo existe o hay algo similar, haz la consulta FTS5 sobre la BD. No asumas que no existe sin consultar primero. + --- ## Estructura @@ -45,8 +57,10 @@ sqlite3 registry.db ".schema" fn-registry/ functions/{domain}/ # .go + .md por funcion (core, finance, datascience, cybersecurity) functions/pipelines/ # Composiciones, siempre impuras - functions/components/ # React (.tsx) types/{domain}/ # .go + .md por tipo + frontend/ # pnpm + vite + react + tailwind + shadcn + frontend/functions/ # .tsx/.ts + .md (core para TS puro, ui para componentes React) + frontend/types/ # .ts + .md por tipo registry/ # Paquete Go: modelos, SQLite, parser, indexer, validacion, migraciones fn_operations/ # Paquete Go: operations database (libreria) apps/ # Apps ejecutables (TUIs, CLIs) — modulos Go independientes, cada una con su operations.db diff --git a/docs/templates/component.md b/docs/templates/component.md index 7cfcff70..b1d97ec8 100644 --- a/docs/templates/component.md +++ b/docs/templates/component.md @@ -17,7 +17,7 @@ imports: [react] tested: false tests: [] test_file_path: "" -file_path: "functions/components/DataTable.tsx" +file_path: "frontend/functions/ui/data_table.tsx" props: - name: data type: "T[]" diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 00000000..6167d26f --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 b/frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2000e32fd286b53023a41142571f41e6cabf42f2 GIT binary patch literal 14692 zcmV-qIh)3JPew8T0RR9106Am;6aWAK0BvLd066ae0RR9100000000000000000000 z0000QgGU>mRvailNLE2ogatoMRzXrc24Fu^R6$gMH~@k`FMbgS3cN(GAPa&L05H^Q z0X7081B4a?AO(mb2Z<#N2OCRd6>OV!vD*Q-n>w3pM3T8$9f`1!i&JFw|M%l$3}FS^ zRIPppW<#8+uC6g_3^(K!yvCv`2vgXJOv{fiq{^-;rWX%Nf&K->I>ial|t+jLf=pkNYpN8C{d z97mltf{LP8y==Griq)@J{z&G}@aH1^9S)D1_7AJ1;uu*b$GM%C8;nydN;4*p9|_5fIdA}-&MNe|(oJ5m!nYfmkpJR(OZ z7kQpcdjPdyNs+R44_@dG!X}JL< zID5Q-v$p7*i{?E2wk|sG1EK(aas^NuKvG(f)CN*qok2=_2L;~<(gmf9RY(pYSATU5 z&|bpsCGDkD*|`cSRCkrfol6%@X}2!Q((wOr{di~Jp$Y?_MD~07-nFGL470$(m=nd| z-}i0#u9Rh%07tR@7Jf6oHKk5%N^MSeKJ9zD@mbfiP0zNJ>PkjwY-wd_RcS|A zUgqU!d1`rmdH;OF{L}@HSQFI+v5;O^v9M-gU~%=Lc5z@SH>68^Y2MN|ma%1R1+Pq5 zy;f$clauKiK3Pxl=mWYL{D06g;;;g9RR7JMNNEYE8 zUgk@DO`U8I)g_ft=c8*73vo~qN{a=tEY?It$b=9{wi8A1QkN=?zzP5X*xM|Z>Dc|j z(DD2LlUz}SZp6~g>Ildn00R6!D`Cov1q}X~7XXfvg8+u*Y!-3)te%9z$5_x0Y0sdi z8`9n_gS}9Q1TXVjNqIluk3A6Vh2*6s0rLwtw0S;M`=B4Uz0`dl;i@tiy$x5_8v2l55^ zfcy{s8~i)?8T>Flps^Xa-a@|S3L&$PWm5JT!`H+!;vW6u@n2xyz)oU2v3b}O3{MIA z5LA5cD{njPxbRq)A+bQBUQsuEb8aF8!;O+6?m)GDm*r^X)iK84o=q?;MGnpVFHZV3U)srEkN*0RwEF&iaY-VHcd!s`!& z$lt0X6BMS?pou|ToK;eqP1Z)43UDQ_PH-q!#i&7*-SfhFdwB^Reop479&!Ow5OdnK zoHum4{cG9icgVc{x^)$X;g^RULw|~gHy;!X{_bgPdVrg-T!bVJutM(xNOnpRdpS>$ z9+&~R@*Kz0Y18Eu+9hlBOMN!u!H{lAr_T!|``l8!MOkAsh+eT=A3Qhs)z;Bq4`F+z zPY#qTaryDnjo4`a;l9rW2$Q|WJEvb6x(>F=p7Iq0vPb;bW@)lBGAngUZ+n66_`P)y zzFXSO+hh?dhG48#L8kVJ%e#5O!~@Ztqja8dyuw$LX&UXx4Jj(>6si+pBna(yaTCtBDl>-v7W zL(-%lNsoO;DjU{jB!}MCmYSh!cWPbNV;-&Zaw}xWHl9LLq>6-uI!33~No3^-`cq4P zO2`AO>!`&$)3St%RcoPzuKk_HZGf=lXIF+0}2u*B1Bo*d(F;nf-?Q;B^Wfb%U~evz(Ioc+C*ncD)u8?K z3T>l@=w`Z)oFzk;94UR+%U#cuez!Yuxh60VQ$4Lwaa0Lgraa2;l=n*>gJbHWMLGJ)6 zsKA(G?AxY*Iz~_$QB%}6=68;Nq37%`@i$BWU*4%yymG7o7MZ-n7^4*jPqj&umyKSdCUH(} zaGPkMFf}-Ww1jqSX3DjKNf$daw8+~zN~IF8@j6Xx;v$@KvMhR}b?%6QcrYT2a3QVK zl(Hmg#!jaYv@rIeUZGmhc4k-=cFv072fHz?MwPT$33>UM0 z6u7z|X6$*rnMyZ2;st+K@SQMVFQ0rG0{=fhXwQ#(VDf9>s8qDCzt>v zfVj{6Na3TSPxqfpJ(=}f>`U!0O@?@;98=>$+k*G?vA6$!n~w@G|NDGg>g2idU43G%&k0GHy&k2~>8D)i(z z=L|e2cia#?yde8%B)*$p1m#*pW`p+uIh`S3-J_0)N`OVs9c_ZY^pJ0m0pLf|JSU6- z7!EHWVG~j_zfl`705M}9NIMq$XYbM|Ls2qPW6y#7h#p-TzhcMgbSzz4-?6@Amo}CS zeSjS-1lT9L^F=g^oE*s4JWs#yD!f*AdokqUjEJwA z6|H;fV|${z=IblB56L$mvtuy3QP};r$1wbtU>Z0LJn4Q%0XFjKlBJ!JikgO;f)+=O zCK)z%4o+?!0>3KNYQ@AQB&DP^%gM^a;0%mR2v#IJ2PYSrPp2;3{Cf236EJMTBpb>U zvmt{(=uKk)lW-W09GW+qBb$?MbzV%CLuoDAX28iCjgiPOhMCMv zz)Dq@!A{o~bJ)q8w+zo_la%pr*#wt;Dy1S!SaDi8rfcII+6xA92cy`NU0a2_Hhx)TlSk|h!)NH?z)F>P8UUyYx&1=m? zOh0j5S$&i0dyd+vmrCy%cRO5mWsIGz)1~w8NGDTHP2avSEs~7;RA1bvRjXtX=B=h9 z9&>)istgsvIH<`mDxMuWQy4tgMHxjlk#a~8V%#Y|@?fUz!vY2ION4zw{(xKZ|eaddWkJ}Lj8?6bv^H3lWEX6m|Pe^|1s3^hM3rdV(Q zmm=?wk?9I~LepAyg^qvI7(>FF##@^D7sX19b9;u%bj7`qrBWj@q#@{`r z=RN}kB_R^bKBS)wrBW0I%ka+Gsl2hUpVig%V=H>#yiezw>&-C~`zYgb2^~W3eD}M_ z?@cTn+~wIq11i?Ebkt7kx}3fD|88Y|H@$rAfSUN|fA6u1{o%ih%gnd>42o`Ti$-Y) zs{77!37l$S%F5EN1VoIbB!#iF!Be$N5VaytStkPsrkt=cjFPEO&~BmGD02|VRvV3` zP;K)q_O%O&bjRsZ-(bI4RCf6GzF)7kn8OZFHwcDsVb-SdQB04vz3uoR%oR?r8EDx; zGgo=iHX5L*_VJ;z3~W3~R&ehBsq0XHJ$yBLRqEIy+6hJ=#ug8psd6YM{e8M0CPL?1 z>z7Q>jjWIDjlz%I<2ox1WTBdSLWh_2t0yDuyqy(4UG+iCfL_&*1^0FJp%M(1cScgA zIYG?bgysbs2gcHfq|I@}0~)dqGXuae+EzIY8*ew8^981C7>C?%4MbDpZp{9^aUUn_ zsvHIq-_95wm+o$KEhgkQ86+ue+FS80-1e#;`KzCa*8ko=jn9(p;rHL0zOLw#uN-`@ zb$HI%9AN7A1S9AkdezVK*SyPS_8dW6dOYLuF((7|8F;QbJs*pgaX2I(l@$3_u_6oF z=Oowd+jQpx(d>$FENDfX2kA1a zFuV@y7fMW03|QUG7!L+xLy@7-x@LTevPte=5&NpWTS!v*^BU-~g(`c%XpW@lL0cSJ z5t_}jOurGeNB_5FMaToGRv#`!J6c_aU`2E?4cW55+OpJmuz$|EC9ZXnACPzXuhy1_Y)P$-WZ=fglP( zL7V^+%Q7ZW`<#1@(MYsv-kjo0FR>OEuNvI#(wjGX{hTPn6urbd@Tx zf@-BfD#%$fW#eo|icef-5=dpPKn2_ken}>hUj~77WAC+=j?)<@Gup2;EqOZ5=A6xe zui`AtIutb-DAu`LM5>V0{`hPXnScjrS4Vfy^En%3rQj8bvH78jO?z7?Q)WI^jTaZG z%L*UNa&E~f&_ZS$?ngwEBq^HkqqwB8RAk;}XKujiYA~~prp-RA1`}HLVW(I=efLNQgR+`p}ysdEt|VN+EpE6zJ3lhuQ+@6iILUat{Jj5rV>vSjaVf2h(* z>y7+km&is+3^iDt++e`PK(I+RM9kxyv_Et5$zt`QDBV~Ov1!N{=0{FoBSn*bL~Fu` zi4grVMFG?NaU-MuX(26^nA!~$Qy7*s4>Ogh*D~gwjh09Ap~$l% z97v4Qw3gQp0y}d?QHRvXbBj_$2e#@~P{sOEjn~zzaB%thDLd2scU^wHJ2!21@H(-= zSyW$IS%cDfzYxf^GhWg zW+D;6cPE36CF)@ELEFua%#~&L)rx>s)SA&cbkG>j2~nY(FVf(@Yf!U{<9w~N#KBa1 z6lSVTE2$_ouHA!y4TD@|%17MoR5gy;D!ZJ_7DVsoAb1~FW~nVNP-2`;c%7`~5CW;H zaiSVFQSFYl*$~y&7#NR#`&6w&rz#OD9?i|Wt5R5ksS1Rs);ZItn1R2ItwRTIQiuvg zwxU-J0U)Gs?fLBf;&b;W4kj0U6`_=vlt71~mQRK|2qbAvnrlr%GhU2pxbxJlry4E> zmkyP~9a{fUO~bU+nzUT^_Z-tS7R}6KOt;#nDo}@5g1T+Dp+f8uG2%E4&g~&G1a0$6 z)Y32W>H#w8Ro0uOLQ(Kp;ES>ynx+zQaBQOMXX3xdZEE>TOz=gAXfb@rvI=1)>5*}u?q*K@y0C7&<_1fmv|X^$@JhS;40N1E4s zXYQv`(@(xk+N{aqu&w!CdQ6c|ZBzq!7b)^q@K3Qkd!2?O!NH{ElY4BlQtJ@QN`ETR zJ4)-ZPTyuaOtZ)s)!&#x(k!!ecvhOKsV1wTgGJ6smor&rI)w~2+HgmhbvQ+{{u^B} zaig)tY8{%BlD@O|$Y9WQ-mUqj*(AjEli!mcSd0qF(E8!$ z38ykogfqkM&1gDNs!cL_aL27YCsPE}f7sVgnj0i)P}ae!cd zL3bNbfkA{u7+i=@DOnrsFacBdZ zT)3P)7^#(EA|Tj<_no=-%mAAl-Z|8{YiQWvTYsne*d^S8SJr*+M*U|2zF&AA{{Vk} z0c2~;6B2cgMyvK`$80ICU#qE8c&t>r4a3r2%J$OA38QT^{Gx8_6Z7~mes(1jZTCp; zgQv5eDJ#?HN_ez?qH7nqOC=+!D^&Rf2zMvR=02Uo$8j&)#(u6GfOqPzcc6X#Q9j==B2 zEfK{F4U0dad&0+dyRKv6+A;e6Uv;rv6CH{lzQw_`{*`#y}qxnsv0bzkbB%ZX}3QHyYecCMY7k zyyQ%k;A~0hIsVWLoc2~nD`m!R7&f|In_P}fk%{wqPq|*n9@2yeEkk1#!nVMG-fjPR zD(k)DAR;X|Ii!|zIQbtWr%Ourj=L?L#YgSR)pF@FP^kmi9aRvP=%!}#s%xEc(XrG@ z)m+KwWNLK_X08?=oe-6Cjf7lrOz&yHbtQBfqvOB%)IIv4BRlTgb&r2@+uG@S+;~S# zR0}J%=#Avs+kvIA)yaCa?B0Q!8I-%$*eP|7{;8pR>o}+ErW4txYUh+G>j@^8PdZQL$FT0GQG)#d zOtKM)LHKSg+-TJrFw)%RLlWB*lVt+(%!;OO+2k@gGZmfqvW4Yf8ZoxWU-dWoFa6k; z>$zAtQJldPwOC-5u^tHzV?%Sob8>~%GN62G%yq?u>Ak?!oL?A#In%>-5?$iR^$b1f zTK5DFV#^Y}fy)1NREg;@Z_zKYHaOvo>3Au`Y{}ugwZw#*%Hi^MS*Xegv>vSgwydUa zj<&zu)gEszG*8zOnc5Y1wVktjxeND8du89y`}_I+p8nzfO#fEz?R`Uw*Jo9pS<1Y_ zjObUit0Ao!hGX*_VqM{tblk}Sov@H8Sa)cgpvL%CKhxgJ7%FEC5l#%yfuAud(5jX*G=>=6JmAeKB#)BeLy!C=`2vRGkJy{jNk6Ge3-6VxlTUEw3@oOZ{pgEPF+ElAnnG5)>MycM70ZplIGD&xuxZ_JK5z-*$nXONLAxh z2thMED3=q2zF2bq(re3F?5{WDK*CG8b+({7CR_*zIaZKEE;-}CZm-kC2L9P7f*;kD zva|@B8WtCzH^~3RF%HDAuK1{O!>ZFD5K;DXj?g~k0aA&rpWX&Hk5 zKKcY`^LH;Pm65z1qks_}6Nq3;qsKYI^N1_erWDGKJ9NM5lH5vVD3pNa$gus!8RHLX zWe`M=G;XIxnan1Ie2>6fCUp^juwJGV39ADOR))#~*`#s1*2GN?IrPPz2;y3uRO%uB z&00Dd!fkW{wY(m+8T-+Teb^V$UnBeRy*Z{P-|VL+f3YiR2f^>-X@h>p4XVi#VS5@sFf-botE4nf4!HZ zTNmu}_&13J&#KCqQams_#+SU8EONcmY#gIeV!e5Av#rszaI0i9MrGwjd zn!=1QGHT%bD#p|l^lxeL(?E7b#RLklfn0MsghR9eToy7g zs|F1gi&jyoDT>Jv$wruYo=J&LfX2txnzdeScHfWlNq=q|;8Xjs z7Z3Y&zGEkS9a>cYO^UiJ%L9WD8rJKlSOIflj)fTQFU&rZQ8S2z14>6xf-P90Dzi@G zB5kBZxqg~946Bt2rBZ{7d?6R3fnfpg#m=uf0YmDeDcp3kr!F6Ski5gg_5#Z@J~qI~ zlN{fZN`@RD@d`G$YaFH{`XC?lA(DV#lwBMtnW7{|TqT|2MOgazvE9!5siYqn+- zqMdeE@G3!G$~GEX1Ud%flrcphR2ZWe(rG(Y$HXrC{SrFSBb=ew(Rmz?@R4A|ydfeL z_uBC$cTusR(7l=u%LkGgjx47pLn8{(p)6X3I+{zv#B&1lM2O;`V3kCX8JfO*#Nn`7 zbhtrf3?r-ykBy>IDY7Tct9s;V-*ZZRo_J{Dz|;ABASIpo;sG*U^yrTu9<6yDAFWaumU3g>Y9c!NqK zIX@`bH^6y#<=%$P*qZ4{fQgidom3v(+VQwH=s(oRzID1cddC6q<3M5^64-<}g%36N zt9X{VkSKssi~o`8bfFdp4m=2alnc2mW(Y-i=XA3^*6>Lz zWR{kg2;JDgfrUO`+8+G};w0WAt6!f4U%R7hD0tV2PXnG&X38N77L$F4J8YoSJq*qwEK@=V zPu^~|8&khO**`V?^mJc8*c+9h0%pWIhYFf^o?2fYwq9L{peuu_Y=dC(oM7twf{xW4 zzroC1qi=aTHU9T3=YM?t|L5w{_WAr2U-1#I{Ac;?)}eHJD{RO8a^L{J4IV}~ADY(j z@C3VXP`I^FVyqZ-#6^{lzBZ=gx*g5w7PC&n?`rK(gvDQzk(%&&++KnaCvzacJW^g9 z*jKIULsi3e7thZqMFNJX(8X#R8*vqZE6GWGcx#y&R*KIakUG~L^ko{=7K>3)cZ!Q+ z!+qA9(>Y4i zm7Iesng&6oWccrD&3cU)&bc2yyH2u}Xx{V%Y(fzhQIGYE5(MS?W6TYb+dW zaid){oWzK+&#Bu2TyQT*IlfKL%DW67o7#pO#Ih;#x2sm~+{n@Nd7N+rd7KP8- z^qr!K7a6bf0g=D01s?GXM+D9v!^Jf?{b-RuGWPnp!`tD3hvc=QfCxIy?dp2PYDWDa zVjYhvSjdtt1}k^w1QCf+G`(xrf2}9GwFO47S;QBr(XL+@pI_>|ZU0h?9tdVh{@2ke zQSJMHmY}I>3%%Nl#^AV#(9P=2Vz*#ZDcrseG@)ydB+TBwW7!C?%w85|w##7~yr_L3 zoVn-qo&1?Z)Af6mCi?4|WIufn(d4ti2ccu1xMsr008!=439D-4kdpLe( za+$*+;`e4tl_Lh&CWCj*U3LkfmNXA0BbGIA-$@W5{{k3~d0t_Z-)$E|Au1ycqf(x- zxtcxXFjXZb%t%3{1oLahmRRi**GSn(qrxf0ab`&^=6OgP&gD^Q_zm5!YMz{KzD2cw z;HH-6O8~QOhSq^eX%yImp6?>i5nbMAjFC$JAc(5=4@fH~dLxr^Gcbd>YTb-Lq_A|v8Xh!3^qHvf)NH)72yO(gRyt;MQPkkVfX(ev&#kICFN% z!GNd8jnio{22yF!#SfYM4BjQ*f0)ySe$2Z)VslL^9Gou; z$2akkfw&a+k@T0z2D@GC%|ztW{dZgQ(kkym6m+{q+Z7HHMA12WVqYu$xnm&`*LVWw|{6=Sq(ZZDks)gP)Gw z49K>p+qlB{5~lfoHZl(#wrJJDJma=nXR6A*21Rn0RI;vah6ko_E#;pHVs1uXl-gvC zMXzU&B_s(cOYg^)BD1SF%J*J5B<1Br+`_2FRZT-(z5lFHj;~9#$rPSKQVk7|6}eU@ zLd-3osxiRKo$;>TBl5kW%tAs+Y}vB=lfxKYm1}?|WMF`F%0M9^2Co)K{^R4zFoL!Z zAdW=HFpkgpCSG~(n$2dNcqGUdiqkrKb;9{Wa5hGU=-}Q=mKCij(d+UzS%g}SJ~&*f zZAkSRYg5UlXsuu5m&#os($rhM@G)5VM+$X}$j36iC7Lp#=fNfzejoSxR16&3PyiNC z>UsOdTPDn5D$F!F{>&4qXI_5G7NS)l+obW@nPbP&XfOW-6SrBg++jm#M@Mn8BpAkHHBDZA_AygE|Blt_jVt!a$tx4A<4zBJ#GQ=F zWI3uiUSLLMGg-{^#S%xuF<5aj95zxlRv!*0b+6NEefoX1yJ;=j8>|yg75rjH`I~9a zQVNArY(_(cT=?jXw|&QkfshPVxb8Y3$a#NL6Z<_q;a#T_1aSE$gqhoy&u0UBw-xnvAudDLN>zWnB_#b~?!iELbDdCE8WoDv!6SX_G}Nysp}?5Pj-$NpE6vS68=F z1U+mtjkmF!8DoqFPOFOgohHvPBA0PhQ&FX4xy2LElf4TzE($JEePGE2)!wcGdL2e5 z1Eqv;^ioJtm!g&_WX75Iwm|z(vU7b{LOg^=aXbMuk4#t*fdIhN6x}}9p^Ck=)jfHt zv?C1R0!;!M8HromH5MZyP(I7Du87MbQ5E}$kml@KS+Jm?lcX%APrGpgZKs|mnzj7` zV<4)N+JJN1Q38%5LQ8A9hF9Gp=-&na-bOJ}0rwI{hJbqU;9A8>*eaSOT5{bNo!W1B zFSnNx|AOZC{~?L&Nm=@1HB)=NZEd3#i`joPt(lPL+uGmt0w2ZTaiUC4YXh*(Fu=d2 z)$F0^j*()q%KFUj?fBuz;E=Ya@$jx3Rll-vsoi?OZd0A}@N5nVQ(G)YLPjItxG7){ z3x^F-zWo$vh2a6m#*yeDT-n|p=v=TMHmxH9*QsUz;N$cEKlTOj!6UanY#%HSJ$n10 zd@5}-{>|s$5kBG6uAI716Dz>|bMud~3q=sv;z!Yn%8zUBbb>seF+#;m0SPm(PM|XE z!7vp55k3_);|8W6NH}a~6GCPDY{=HU@MD zQS5SUVy%#au*h@uX_u=eDzVQj_k3xnVk$?DOn>BL!F3M7jAY#=1 zjAP}|2j5ev{lc4bIUIO^1D6(xHi3{fu&TJ7)3n8AvCOVn^OL6+uW#^s0IlZTowsEq zOnPc6)|X?^xh0|47r$#E8v9=lhMkdF@sH$3{eY%#wLZ9|=Y?>oW|)F%(I`mVcBq)` zD}C-No}pZ(*~)w|vO;%=E?-9^s|NIx;g#mdbsOmjAn-k~42fyKX$7w9 zt*Gd8_lGmMfd2@}>7ihjl!0N<&85}|xquULE!93@VPh{3{});biR7VYf`-{r0?+oP zFaZBe6bl^LCNM#aoX&iW(?PFQTv(ve%tHOw;)#XDgly3YBDmH)96@7V!0nigg?t+d zoAG4gxjX-{N)krScIaJE^l8@PJs{6k9xjy}$&?9=gjyw&j=Lgaq1vbQ1!tHgvJRs< zX_m0|-#|y4EC7{IE27Bx6;=kDYXUfl1H9&Nva^+U_zevAho*MW``5vKv=x~_v()I< z=@>SsvLXvoP(o}<-g)Hx0EiGtv=>kue+qx05LYU)+n^x^MOp1*hUI4uf(cL6&T|Yw z?R5lN($qE~iUg668LAa5fwPAdRG}6ZkB5YT4@n!QT40l`f7aXzoAA302y%cnSzyZg zLg?)X6Q*>Ufv&iI|Ah<;w!nR=KgcY9f3WvKhEQGC%!ktq!U_pQc$oKh$d?S@y-h}m zWQqVnnObMr(5;qjaSY}HY`C-?(NvBvP3@Eu6~mJstKo8Uqd8Pj&v1NdE}v>%fvOn~ z5(cUW2~2&RY$h76ehi=}X5bec|_gFSiA(qDh+~@bY2XsBjBerIc>J4J~i!2w-CS zu3RUGrQc<&-2H`e#AKU{@ZnS{3}_vsI~UjK`kL)mBJ({D)W#VWnOMs^E-^q|n{B-j zvemeuzs@EIHt5`7q>CvIlz(>me6GN843va%W+}5ZLDdMS#{!@>q=&@cNvYMk|K*6) zO4y^ex^j^qsD@%`I4Jk>D61ZbI9P5e39uIcCIsD5?x~$pfhkf{!}{#b_Fos1LSuoV zC`=Fz3xY6>hBPOn*A?}B-EYHIqvl$7y(Pp8Z;=2V&%*v-cANTj`nR;e6@%dNY@uX6o3zsjy@G29l z1`W-5L3nHzq0<^|l#LX*Cf92kch!Gis4|qNyhWfW(cc^qP+hecvcM?(LZN(j$JY~6 zuFNgK{y~8x9-U1Lc8XM4&-oIAhu0rGN%{Ri{?FK9su?D;8JJssQbsV*`7>gA)J*C-pCZQRDw0tcwA%)(ZpHJGwt5@tVzbv-)Cvdt%h*7(2Y zkkupYB`awCu4X?W$cXutbbPS zJL|7%=ZlO=qp9!0%ANP4m)H@XM(6&Mk$X)Fo@a8_JEJ5uAXnkM)3WD@MywL%-=Vk< zqI(gYYaJ-u-g>-tc_2%r=VTYvXHj{v?WzPonw#>!`109W@hE8;%NgN%-`cD^1fs6q zkVmU|T(Jdd2%5&rYLQ47K#Zv0N1V5_rG(i8gjt2$7?-)+oORZ&=cd!TI*l~z+$@?m zWg~I_V!3@*XR9GAGY%~bW|2^dEeV!^nvXbZs9FKA>uVeZc5NrSh5@`|RH7Z2*?jPN zad)e$RXD810Jxg8l*FJ+OyX1-mL$~I`$>|uPJ@cw2|Bjne3e)kUx5KI8p>o6CWOgk zToocE#!jxnDspMok*~m5i}m+pING znL}qNnae4b%!34y`HYMxh@n#{gn(NF^k~rwGnjYv>(oZWV-4ID%C(JXS<<}=)(Ra) zb0Me&D=@CoUIe)5i^XYcu#EQTL^@xThaR-wXh@F_)~)XC?p{8Fi_4y08@RYE{K61l z8SIUtb{-D9(T11oGe9V8oh+7T-Rjtm^cpl)8_-+u;6Ax=khilnP)c4yCKrOJ0YTo` zTFBPWzI#-?hS!okziy@NQ)_|H6ZH?C&*yMN_~@9RvU;|N`c#VIm0O-1kYE+t3ISbr zyzQ{mYRC>AkgwUw)z(-C4I8SjLt1g|UBf%i1;v9!SAYHJj~0g8^eBM~2N$OB{q_#9 z$-hDl0>5f?0)h(u#6~f3X?bc|I-i6B23S$tRznj8i^CI$Br=6cqoXlc9G*ZVkttLf zoxx1pgEeuXa&%nrpU}j-OvZ2^H zIJvmd8Z>IstVOFf?L0boLwq`Q>E_p?SD=aoJ;R=;q3^69%ec=11T6zgbMWF0u$8bk zm3EvgWP>pmxu~})B7!P4*To$qNJ>feW;05ZV-6ir2Q(N#*-L48DBTV$77?@zE?vW6 z06`={r8%6eoSlIXxu~}~B7!QlZWptI1W75$-fTvRa?GJ4>Ok0VguRrO2WQdZ2G+{Q mmA>*Uhxnb}zj68L`JW=6F)cSVcr?>9@7?&5#uIuF0002yYf< zECDtGBm;(Y1Rw>7A_tB#3dI#7xjh=_v2~f>tzptqqiO}N+gfAEs!NviAYTlIn z|DTdnWXz;M(rz8-)E^3DIZ9_zjiL<7FqE1?Ldq7r&~8^kMvr@-FdmW17&3-Seu?ff zx@n5$zz`Q%oJzsMQ(UAd+lPnZspu`%GG_(@EB(-(Vb^DV@ujMuVF!jC7>=~DbZ}uS zen-00k9E$U=*8OHPMtH4*9BDzi?Sgh@qgunqa9jtV$prZ43E&*C%Gi?&-?fFwf24e zdU`%IpnnD-f|WUPCO8z3&77a-xB2H1EiE$<6%`AcC~4t|fQSf)F%x5R(JtKPB3aaIq1>#Yb=t1M zs`$D5?kD!TTbiEu?_TFW^EIv5APH%J1euUb#0mJ)|EsC>3mYZ2ESaO&A$y46Hp`cD zT9V)H?YOfyMu*6mv1Jlqs~V^}0H9VAH$O=Q<)9+EO4+?!w#}v8%uTN6%GaWxYm`HH zgv#!BXBb~ytHL7D@P}@FFQSA5i8vZy#aMBv{+U(xyE9>%@{0QAI?-0dk#$xSe9@*! zcTPdFlN6Y?M_6rNfnZqb^|C$?4;w?05~6`A(-YtDt&MO=>+G~zXRwDC?u!& zFNc_qlEb~!Krs}ZUAi@k)-EbFrBhwUqBHa&>i(US0bA67OH-2?Bl@RpM%9#l=eTy~ z2w{;BBm@x=H+|fXsh`70_q3LOA%q~}1#y)4QvTZiQWAJv02u?a2u?PH1fifqqA($B zJcvL7L?{W8EDe$&1Cl8Vk|PgNpco<}hbXBbT1JRj8N|*BajSw<^Fx9{ka`V}@me5l zIv`!TArno3Ofv^E*F4Aq{g9=WK~`7|S!+FHqfL-4wn27y3G#~9A#ZvQ@_~;bpZXl~ zr9F^+_CtR3BV^DZyoe!qM~%Qk5HJivG$>-oQ6mt@k?%eD z5Lj@rC|1Rq*brNe>Z~|*_jaLO7!+Jrb#=GwR@@cuQD6Af=l<7Es6in(AL5XQ`EWV< zca=rexK|S~jkEDt*{hH0zKkW7RI2g;w1Ku8CfLOxj@ZO@hW0Q=OeO51IoO zv6xY2aGMM6a-YYXs_iykja606vwPmQx91&rC%UT#daO&G>x!=HMYhb@rkmBX1%c*_ z{J|ureB#&q-m?W*0ssccZOyQ3;kbSfMzO?{E2?hh@}dq|D9Hi}5b(dm5lJ-e8xY(J zegN2v9t0Rp$B(epHV!YOA2NGNH^dz=rL_y_cTcT;3)~5O8#9ZGwcj``>g~zPj>_(Aq)?bX+ zcRN3-m&&ziewx_S50;EOk#v$K@j?6%HlX{Q&!;n3?qBy_|8ce*u$5^}*_nc1sQQop z&VtX9B5)D#0w|#NLA#xg<8EM|L`{@_Lc|Vz28#r})|767h&BXs1kxgk8PpU6q^gOe zr8&5lQX81(St%O|rb&@-eMmCS;PKB~5JkKwWHj6jtjD--PQtN492N19i25sK^`Q~c zL(uCx;wNL`B4JVlqV0ELEI_RBDYGCvE5v1$2yHTAo5na>1kr3{+Fy-v8!T&R+i<@( zuno1swF#tIH>svYGGba`oCdZ|;>;;ZFFUw%0g#}L_tA`#f9XdA84tV^TdeO9*(>tM2WTMkJ@ zB9+R5&zp*a&c~t&&#TH^@bku@KqzoSpD=@U-(BY`2?T&~k6NEtm`?9ey?g7OimL4~0n5D(9X}b$+%P8l%xyzPj#g(hq?1%o>DYLj$AQpw35)F1g z-L%~Z+bl56V2q80lEHlT%WJTDM={3pl{*Sf3$tVTnQ<<;t)Vd^9=&~U2ILplct@GH z{m8G(_bs!OCb*(l(^*)4bnRTG6~Za9*LmaCXw4WPmq7D1tx;>ZUYW&{d}MgrWlh^> zuZqCgKL1hPw?yX0p;iy6B590?GKK_OgQt?*_XVECNF8kFt|?(+sBkMc$54@rn@vU; z`?InNex1wa$<_EJ{>y($&T8=l9^wUDkB$uTI`A&PgYUdrv>%OvPc@sMdc&pN27)amO|!w<;$5-)53lxoh{ec zHM0So`046E* z@(Vb6S*%)1Pdh5`+}Y~0TcNoA?v+y<;l08=#n$0W-9c*dOTELt?(2h9A)OteIDgxZ zYS;C7@MB0Rzc1N;<^nM~lIm{`>GXvPo!0uFw0Z;CNm(oIIh-H0(?J5SaNqOO2#lXo zMeB9omG{6oZhEfc@k)K}X4;?)`-ClgYsJ5`pwS%9^|t312vGdjUsd1AdL|zh?v0&) z8Z~*3*Eo0gp1E+>b6l@A*n8&UU5u3ep~Vx`nPX-+u2o*c!haI0@1K*Z{yu+y^4jOw zJN-3Yt90w$h>!R6dTiA8oYvv6gN>@6&Lg3($LbrXr?wr~U0?Z;>yzd_oKWjFSMTlq z!g>R}A=1=1@!=3d97%{sA)PF8$fE=$rI;uK!y)(o-Nif0-}<)2A@39fx*>-iR^n$4 zS{3FHc+tt8Bf6#cq!?8nD5pzJ9yG;!nGc6WERkiLU9p(akBLB-Yz!1oL@cDJ2CA5e zk01+JhLWjh=%$x8>afwM*&dYW0~*L>im)7bX8;ciSq__}%%WNC9w?*$i4M3?0ys(5 zbPon>yjtOblfDJ0)=MXo=vSTxiwbc(qF@FOl;UMZ$Uu`ls4)#9P=bg#Odyp9pE$L) z#Q}m$S~@_^cnFaXs`OIN1Zp%d$2bqNTAYF&p&RB#-Ut9$_|s8tp2dVu@?PfS!;c2w zK_K{|Ud%-$eaT4PhtX+Y!(TVRGbVpwK!XmmWJ3f84xHh%=-b8maZ|vsAb^7ajwmRQ zAc@!Y%(6cde0!P`fCFNnh}AO1Qc{wasgga@WCV>2MRm!1xy$jSe8{JvG8=j)Q z9v_mBcZn%c5L_3t>zQQba*S10l0a~Tx@3V@FT{Aw>)U`5a1bFft|X1eC1Cg2G{Hr2 z()BM)pxb$Kn+SwEyBu$O1$($EY8v*_C>{GKb?gbv-N0ux6$^}@4I~Mj!VOLnLZYVW z|BOJY$SUj5PJ=QpdA+lg)h@3+BX0vEoPU3$b5w}ArI2k22Nz5CM7;eZps!>k7H_en zC3=~+n@c`gWN|hwc@^y<>;d*D z{MZF&86IH3$1EIev^n9gm!R2_GmmVa273rOYcqFL0i;pY3+N9ALRqrTwuQR1{J@TP z51#!MM(6y7H-HO(@}=#whsgmy5Z%7IyRjw)(EW=@B5*CBHe3Xlf&g`%cLGMB0W;vv zI3hd^0r+8YUkXZt@QLJ5u<>ftZI*rrkd3r}r|}~ljE^n{bu%ilt;^Lj;;!-Chw&5V zubqsKpL(KT`t|EUs52cvR%v%ei0NINP%YooUFZ=pi5R@%$MDK7KY6-rsYqq!XCNCg zk}DNI`2w}CzNv$Cu0wbNo+A7Tmk3`H-V+9*HPL&bFGTx9-@bi$OX+%LK%}2&Ci&vSw z!s$Kb-e&iv3h%yzSbJ3CKo-#L#!61ixZ>@9 zp;RL1(Zxw6^^*yiQnJ@g@r|3R_k3gq1hw=D@EIUa0jGd|4`A2{VDMPbI~AY@*UlSj z$6L|ui31)-n-~{x&SfzUubN6#s9M;yw%T|lR5Q|fa8JC%E!J7G7bcq)t7=^4J0+ckSXVDl1>*U_7L}9;Gn*KVMk#EPl@*n;R47+^g#tdg435Oa zw2H{F)~sc@+?MbJkCByngomeePsq~f_#P*y(We)okR$<3ae@aXNgGF`E-nUy>~01+ zM%1T3!d0*%Q;U`M5S?1-PrKdIL*E^N8B{Q7%K>gI z=D}`->U#&&KA#2o`{hBewZ-Z^8_4vd&mP}mfoJDvSOaRL>8)70)zPwOHTeA*F(QK` z8+qkzTd!JIfow!UYk%SKd5j@@<45HAw3wN5JMVsvE<0;UuZ-8l2jS+XR^hYn`XvRE zeGP2P^9l1;!kG#R0n8D?7?5jFtBRA}aUPqv{J5huKFai@iwxR? z>)$#E9HS7F+lp)o?F!6r&bVEtdxHhnKs_G{^@qW?34$7Zr@~;;uQRuna^#^pkOkRi z6s4z10=*;=YgL&f8CK{fQ{jXzZwnsVAL&(iEE0B6&$+`cAy+83X%QF8)D3q?imtD> zGPwSw#lvS3yn4_~*6`i@;O_kX3sAwVG!3I-;mzH$(&Rf4w7S(aa>7S1?gDCW6ZH^T zFZ>C5TxM7;^V3A^{L01DlRJl*3Muyl*EO z>z#qp&=vYh^o_y~2)_=T+i$g4D#@#N#(7jw6qDudRszm!YVn=XL2v`$p4?p#xx~SE2 zez$c!(DT`Ez38-}UQv|i{7v;JbPnsT_^R=N)mKh;ztYNv^CoA~4wyg%C0qMTzy#E7ulK7wd)eh>TeKeK zeIMU~;7jl0(Cuwqr}sUQyH9CE)rvta1O-%ShRXAX0Dki)^-1kYmwI^qVe&f1v84|F z($`FNfIoKMKd5W#aMlm2Iq~+)5OlE`2K=r!r%rXfH4tc6xMpE=PmstgA72wNx0#iF z!Ju+Mo5@_)+Q{$(i3~^i)VX>HMb5tQ$=>(h`*6>ztE!gHo40h?iaEVxdjFN*dBYb% zp3tR9UT_vfC=Pi?jZCF)*Jx+@MK!wF?WLtzHsO3}kis{*8+wXOO#{J-wMDPGZDULIG_ARVVF(Zz=76uZvc)A`R8g7N*IMo> ztyL5>kdJs0+V#ZOzy02X)n~zkO;(S!`i=J0O}4k&!`Ryc&26(+_3^_2VsDPMLZbDw zsEcccRZ=AZMjjZzx4iEZ(Yrt`kqO?NT0vC%vA|LcZOB&xitNY!Y}&%rF&m3{-JCK`ChYdAyd4&8`w%1?%JgH~5z#P)!T<(aLu zb`#42#Fjj}J55<=i8Y9m|MO*e`IY>}f~>L$B9AD{F}Tc7@Dd84iUO)BYSpy{d+|QthkH13KN(Vk+sat|ndo zd}%4E$?=bRBMzJ^7|2g|<>~X1{Tf|zjb55(^@KyUo(>Sw%ezggxn8fTx6^F5bm&#R zf!Kuvue;h(Y|I}DkoeYV!JFnZqQg<9);clhI+6W6L)7oDtkqLJEgAP%`7tvyw#+$x7ANuB#)_L&y|KBSrm(yKG%{r(%=@(PGy<{q0gL%3N6wl_as#oF%n7>OCH3JzV)e zQE!!QJ^ifR5pU-DosXorETywh5>!`vwG}eELSIpVX~50(a)GcJ%Z_`TEcQyQf7lK` z$_bP(>G(f?UmF#;rQ2RguqI||oh9i(MYUI3ATV+z zTBifkLeyrgDp8!&>}|>x>y0T==4R*>((MykOsLwZ6YTbGQ=MwBdTzJL=CZr*Lv9v0 zf04k=#qdj}P)$aiqS##|alDhG2R6N|DOct$sv#~gC`>YaN=CGL(~9yapTv<>)*fs$ z_@#x8c{Rk^rwXyMHYKia+9h{XAVZl|ATx(jfon4xtRyc%{4CjlNr$jJ?B(~JKV;4^ zLwZKy5pU5EPX_i0!3+9F^_}(AUR{OErYtRUmub9Q_u>dkAF#sy*Eylyee?DO51Y*0 z7kZu3Ieo+QqR_S((WxPzD-gN%Q3|F4$)54DIpa_fL&odab3 z*uc4uc!egtNoCc{tfvV4y={$UZTWJ)miOu$4Hm!7)RH6jDEK`=4VI0q@7l@9wQ5uf zpIp~O9#^@d5i(|Di?FEKk+FSEZZ)f$dWu}{^26`C-6oU4ZE(K}OEY=ipJZi;nw%VE zS%T~**H4zo`KFLru8u>>dn4jtI-)+idpkhL_Wk}%;+=#NU%tiVw(O0t_g-qVZ%5+o z#BZiTc1x4L$ydy?Az?`{u%O$+9b`|kF>H_>^k~8=S)(`z*|%;*OKkZ{UKi_ z532{l#fvhT^St4F;lKy}T<3_9k6Sz%Ao5x>}=-NG$Jggn}0#HksFQb>^ z{Z1l^r&r%^pbvc#f<lPZ3316pM~J`TvZ1v|Y#P>Toip#4O$@CHC-DoUIO# z;q0!1sh8;#IY>GF+po7|elECxt5^HuU>>NzvMqHwf_+Ba&VIT^;OHoAK za5WV5FVWP_>n=KB7Z^A!tH=H7n(83GZ2;V0iqR8Cv3dNuwB?Uql{QD@0JafZ?c3c& zCldwu6OT9{2S*J6yYxd@rXNnKelVG8Bt_V(AJ(NGG+7_fs~+XU0sW96{UOcj0Y6lx z&r`(+HJ;LUc>gx$&_Sw^Lz(}$AIR37a-@U#nxotBVY+?75P&oHLciA3g-eA37X{4?oDDW4_ik9`G})fjYRoyTk1Z&NGKwFR!@sXV4qa zO0yi8cADr%Hkj+^0|3irTkPtlXcF+^$nbl3$&y2@m(?B10e^*;`$IDO5-oJlM>wVL z_=*<)<#V=NGasA;3^B;#8P?*@&;~SHuBxQV6j+=H@aEjmDNX0ldXeKpa{DIyiL$+A zC+M8m-yN2i0ldw9d7@@hjvPM=zY#rrC^JcxW4iK&JQ`3wTx32q955^3wZ#bWCOm*Y=2YJ2$4fi?qVI_%lZy% z^8dYi^LnPvJU0K)^*Lt-{{Ktw&hIw9HM0@GoA{tE?pA)K8VF4ovPtXxipXuZw>86% zYGBP?Ay8LnD7=dFXqpMq8=BDHq-9wpS8A(Jh@Gupd)bTp1RR%7t{irpLEGWl#kIi0 znVAS=of#=j>X$lA1~TK;GMQ|*V4w6AZfv%qIPzA)fJ#6rA5h54M)ER^TxHq%3ijnp z7|DZY$ib)y$&uY8G!POWUBC92y_f-NLKZ093m9#k4a0d+kwq;g$VxOJRFJJ9mLFiX zwbDWNKQoX4(tFi`7E;^nub1O%C0IRULOe_gX8Tx7=4j=jSve`OjD}3u*LALEii{ zCA|B8@}LYd7GBLJ`_iCO{&NS#m6;+>#%EF%WKAxYZWR@(u9{uTYir$wj$gn_@N#?^ zz8c?xpNe0IUx)t=e-wWUKT0qOVnPwYKyVT22y+N42-^s65zY{ki21});&S3v;#{!ng3N_-*{z{MG!o`9JaH=vmQ$ z==SKo=EMrB9Gm~gxH21>E^i~4)--|f~P`mw2hpbBJ6o6>h- zJ@`o*w+kU0VzlQWBHc6+-*tkTbos>T5wegQ*>&wAwyeB5h5`GCbR*FT$1h}|6!onU3_3o7f8i}16Wny^b8`7S?u-2)q z@#I8b{%>IC_Lcw?TS)5?lBAJ|-NeX65q}rJ%xC7`RbHaWp_cUu2Du5-$MZ+EsAHjl&|M$X@fGu=RzW;8=S@vP z-(B?guwug16uV1$SOk^Dbr6OM0GcDr>YR4r+!9^jJ=qyU0hQkLfIel?>-^pEy^{fL ziUj{X=-zx&;q;1j5veGIJ|QD(@L#+NtFQ%IE#{}c?4ul#4<>>a@ngu#c3$xdtmBn9vq?ypaiAa$*)*E%yA@-0RC|NdjUveuuSFZRs8lObun%b|0F^f^ zCyuxBxQGrDt~?_FqWM}a!v}cRL~-=)ETf@F9Gt5;-Q$+izn2!Df3QY-G(w2k-l&Eo6r*gE$z3PJ zO90{0p*%GpZUy8rjcTqIH)q@vhBw02TNYEioLAK`eG(gHhZF-XQ_S}(Zd@3+#}Pt` zIO2$8TS`(vZSj!fgx(M<1%x!JV4cJD=kS=C%gSUd5w{ZL5W*qgC6w+!yW5qyHP)5s z+S}HE1R}&hYi@G39%frf8$Iwq=7k$~TU*6CT1gd{U#k3u!(V#2%R0J%L}ZSZHllkT zA5dokdYC$ehd;-EHvt%6iqeWJLBxg`v!L+k>ofT?@Y^(oV4_lkt0{(?dh&>1cy;E~ zGEBpOLg`k@LX4XLfXw!Q(w(5_zT??;w}Vl0Ek>NSA@sd4ERWM@D-T~7KY0`kY(UY_L@iBGXj!$c319@jI~d0rD=nj+ zx?1~=?_wcUsJx6$pyEot_ubP&Qn;UpWSoL^@M13%eZ#AEx-vir=ZA)Rd~;XO7lncK zX?WxvyAh=X?G)3T{2M^AG??fWiyq<5Kgd+2?a>Z8nTRxy_Vo$?1eE?dE&j!>g%G3> z3=|ycAdS{tySZ>7`@jRhu;^v}sWYo~Dp#;K;c{pGc!emEw^U}}zxXYf(2P3n1En=r z)?ybIp9Wa1Xc_9akMh5Yk4Y+ngK}ZJtfX`GYCJp$S~a^tsWANQEc68@er}0E?^CEV z&BGu2#>?#oXQT8l)d!^MJFuO zov=Klt}zZytuh>m^Ogj8$lW5sV{xo299aV48D8qzq{P*arT&F4&qpx|0ffAQ^~s4V28Fc64y z$kN}`N&Q4ORioI5zK|L>T58e!v~60_-W|4e9k0)sGt263)ipX*ZnrA2ps&AdwaS!J zW$r-W;m{XaH~;j$E-hA$8_~SfG)lLx=I%|@Dnm4ZYkn7iqzK#3gwCVxehEJbGE1Dx zPa0gfi*V76$)SSM3@&fH2tR(1^dnlzZY3e^Cl=)d3lg|#&9SyfXrP_*(+-Pq5y?6X zswGXahs2>(*)klUgGCYD=!j_y(E|@;GY7V9Yf@hi@GS7}c!uRti5##{% z3XW#r`*|Ry=T=ond?SRqnDa>UUSCWqgi;T2i2+rj$tc#5V#AyXQ$F&7J!+W8|LNFE z-nWlSb+m0jQe<$#! z<&osMQlil#i7aIU#~BA;cA55ZR*Abih!7wqPJ+Ag^Ob9ahzwNX;z#%4-FjGss;dZ^ zJurCdzbh?Xn-;=XEqo@Cr7_?Js05!zFl z*Za3>?>{itC;VcDLLN_rT-qJwv&Gr*Ce=cZ=)R|$IaaX2hcHzYU7OsMrj)#_t{L$& zkF-Z&G^D*W3sni;E(;5fErGKu?J1|_3W%5hb6&CNGrSXT4ZWhCV8JMiIbAV}xm)q9 zx_en^!E{<2LAFM4)#b>sq~&00A*D6A3mR!G6=OLmRfenk=C}|@)aq_11hLV))3e#q zbnjttW4xI_hBRJb_M=vx5`1t0Q^r+YgzTp{2C91V(%`#lbsUXu*(eim5v#(NUU9Nw zvXaOo6k!;qodF??8-o#2G*vZ?W=Lb~C{xwkk-ZPu8G!B3-vdPaYyi$FE(gkLk`@wN z+r(gK=7jXqp*v93C!f5m>5C^9`i)ZI^jHJ1zyLLbGOp?H6yA)bgy)`X5LmDm)YH}G zwZYyXFr8O$>ph}n&}Z?KUOmPha?m5=Xd_I(WW5*o=`)ph0R&^;5;!cD&AM}96n0jv zRg3Kbkl%!^-1XEsskDQhcY_2@ez55(qS>pHMso=#XAqt{?i`37NtO@iL_N5YzMG3-kUJZs&LtEWp1 zEo666J#AA{ti~@;V5H(a*A{&Q*9)FPx)?`>V~IB+%N?0wqu8fgIt|H5VXM5-^95E` zU^X*ml{FRDyB2{JN4}vUEt~hNym3vwb_9WWpW1Qaj*TeF*opYs&o+80Ju=j;re_Zi zu(jeAXhjb+mDV-CueLmj-sBVtTCEPv3ii3Kc^`zmQ0%LU(U0Y}^`Wi6y$7Y^ryi4w zxgmVT>KbQ^G;*~lT2|pIdaWFrdYUd3mra0&xeo_m&kGW@@OwxtvKoZQh9TlQoJgZ6 zNtsn)%jFB2D!}c@7}5erutW%!y~ZidpOvb%VPOOqH&~Vm#3i1=mrDZP`k1n|%Zu2% zz_`BqlX_zoy$4av7LGkOa>s5y#p>*Z*`BYspt-0j~t!%R*c<^36%pm|?SS(4O`wZlwLQZ4H`wLI0UtWjzeXog17 zeH`^=HwSgUt$q9xv02by8hMW9Eu4OOn!>_Km)6QK{5!ZFn}AtfCKHezQ36Ly zR`Y4kUk(c2M=5B6?ys#GyG}ABof3s?SYPBt?7Zn_V28hu^zuzFw_Q&sJb6y9QIAfQ znl4d1xwFvZ3|2o%x_rfXPhI~R?wyDIU!uoC8HfGYIXvnWC+ip82|P@}3N=9}R;z*1 zD~oe+U20AXxp$-V^~rYJsuwlU^@9xS=+sJ^O0r$Fv{tOBRbBw4v4*?Q4a5Cy;g5n> zAYV*6iz8WrE{2MWL&(em8Nv7o1lh83qVoq_g^@ohF# zj%zr=lsaP+PkkmaTw;3jQodJrC-Oqw=Q!R0%0VtbH?BZAguz)u!q_z`#ITsAlc})= zWUvVTCz#!`J=Vi!)K=1wo_fYt8?5*HYyH0FK@K{@>c>J=2mR^UMiQBx)KG{W!4D4q z-c>;?;t3Kwl#BH&UlPXuKOmQi^&o7{3$VfF-WUtQy!k5Wk4R05^7*l1yAez{Oi>2H zJ_y7+p*E)~ez&eRa;j%Cz!-OA_A6T%}OoXs{t3hfH1+uX0@^y9M~h%a6)=VYpHFI#U$;wW2J0`qCtt* zNGmFb&R57LdG4wr7J0*#yQ%6+h=7yxeDA&ye=!jlV4Vf) znn{&#g??hSwt+8SW`ZAP{EhC+>-tizfx}b(Zzw_wZdqHd)%om=>OQEaZz1yA4v0N`wcJL$k_b0L-BR` zczOIB*D3~Lk%8%YwPR7AHdn;zmKSgK1(J+Iegrr4#QFn* zWf-18W&@CwYf6IPCl*yhO{5JKQB1JG0O$J%3+O*g?~XKy>g^be02)Cb=qq@z!&~`- zoYBXXt6TPzYR^ES3EFuXEM4{nk>2$iQmVnma zR#Rv=Oj3ZE;Ou0|Z9*T0rQ4}#E^Cs+IPxc%Hy?Sq&TRtEL1SphG~K}c{_uz&RcEw3S4mzc6{l*x<3%!p{r>i zuyj|oOyq^Ce=2U$H8KGhl!WA2b|gs$4(LE4s!vD-$K>Ej1R4m@vjA&R$f+#VGxgz& z@=Q^9*r#bDOH*N|Ekd@8S6%7RWi~=B1>Ev=mjKT&+Ij=lH0zGUCliTqSimb*bcq@n zX>R!V)+tD^9AANSJMZ3ZsCVdZf0gAe{#LI1wMpQCZ{bAcpCQl>N(ZyqXxHyJ!Cr9b zc6d4LCl@wGz1_5%?q;n(xV!}|YO*}`KOCNhr>P^x)N&x0oYR59i3Kw7M}l+-=-5$= zsNO6P%(icahSr0xRyGp{LJ2&}`1i5Vl-Y%583+Z( z$Hbd!C}9n;@m^5_7ojDRA$Fe*x3nWdAAWdnaHqG&L2`kGdP|)qh;;WHr)a8??8A0xi z%C(AF`QU*uTs~5!5i!*Zx&?+yYE@tEVWhBH@Md`b;y$TuwzwW8@G3Np>DC6`*sb%Pkd-&9cViY!ue|mVV-RG`oKyyo_yb-Qs zM!g5si8x`UP~<8|p3P3Zxwrpd@)8Z zkuQTK6VSK~_nTbq*1&F(e=DsMg3)8f)&XO>ur_PvOattJ|FhEXzGs&XOVPu2aXgg} zcZGDKE)@t!#6<*g5q_EZkzeKR8CZO*(8DHQ=*oY$Bb_ik%Mc`ff_!-f9B?ci^Z$eh zE)bG?Q?JFlx`Dg-dG|aLHa9QF_qfx_(9zk~)y>@8jpjW3GKziA&_O2C;R7LBS~_b1@ghi ztnfY0XnLxVoOLq@LNKs#Q$rnN+YOm=!s~<)avCAwFq{e@TCU}IP$AaqiieiT0}oiT z^LjZ0%At%IWbgc?eobq@&LMtUFHV&c>S7qQtY=wB@R*rt2h`CF-&$ zsj6<8UzW)*;tCXOrBY5oi&ezjD5{2jZAaN`N%1mN%DBFvT|-SKL#HR8DUO{ksX;3e zcY>-)Pgp>Rs5UzBJ@Xtom5vgoa?bSsX^JA0&4>J|T_i@=QISr=dra{uz_ zZ6+Y55y90+QZ4>Sl0(4!30c$f_AApaT=2=t6! z4KckMZ7cx^K+kNuUJn7!V&CK}cC|wQU;w~=_D=8V|35k3JvS;Loje-go4G$+0(`T{ zFH76gjXjid9{_zgfPn6|H8Fsp1Dj&CnVNE!oqzVv%1=_e@UN!+ysbE|-R7ik!P6gbAU}Rj4&UDCG!a-BZmpu}^67=9%;BlF-EeZPR3LsezOzO07BO1}}_!oAo zrY%SV*~zg0&V><` zIY7@zubgxu*Dq{Dk*6@n9(fS(nOWHC5g~By^opBLOcOlRqk7|Q6{jJzVrw|e!fAN< zIjIrEWiKLYut-ZnF-H^OYtxc(d9@TmGA$KNjwU7`Kxu^bR?^Xcqp zO0BO~&l9AOwr-by=T`)3vWH6Sn$n?f!97$t&MIX2$u7kr*t&`YcyU*9$U=A!EwV~n zm=F0VHV(UEH^(ZzWW5dSxt^th@hDQEh^ZMfgxGdt#yVT|4qowUOq$ zPjD#7`F6aZ)=50cUnTfs9Un|s z8-$z7SLx0{Jm4rDPG$ee*(YkO2te1S@B@Wm@VRrCQsm?~&p3sW22!O`CY3}u&7E_tNom0tlx3M;C(l4QxL zqDYx4HBB`Ba#zF1-k6x=@&)E%sa#<-8W^>vteydCquFYAV4igM`h&wG<;N#}OV7?P zF0VMRZ@6xQ(EmP(;$+A(8mpd6XMA%P#3wA(tk#%+eU+<5(82Kxo_GqBCeP(AJ+?U z)%JQ>A5-CPvTFL@_bf^Te0>e*FW&8eh~|m(yD?ew21~g_!m=c-LXpf>m&^o;)KRIT z(R}?B#u7y_{kg9lvxY4ii-8KSI4V{7@}8!Xrd`pVF0_7sTCG!EYi4X<5mzulxAnDt z2lGtO;9)1)n+n;N%5Cn%jkTrweLl^H8FJQ^fiIirm!v|F9WM>m9cC>!vdE4HLzq4q z!)3CH;eo@*1IOkgV{%kKWz;%VT-_+mozn12#x9%e7i zij*b>^JR}s<_*5wY2!;u?CQJV7ysO>I9kYK09s4qm;eEZC;=(hB|(xv2uUP!n6!ve zM>orph_qwnAWn$0lJr)jVd+n|vrIrY~pvja_LNQgSvQOL(-z)pXW74s~;*|14LaK0qg6j4)uEDR=rmUYeUfhw9 zVk6E?ZFplm9ao|9-$r+067$kiG_iB-ycPZ zrnSod{~1X~9JR1jcO9N*G!Y_XLCDI$k!ewy-C@jvfE8hbSyiLPP+Rh3wz}0*W$MVZ zff2H+rZJ`AXIc_9Aqk{a2dtUgf!s3ab_hw+EeNCa0>NdA&Tl;M!V?wp(kY`ypu=TC z*>6L;alkYF{^fi|yFG3={cpkCCM0qU_`)~IRfO+jsQVFPiy->cV>2WcQ$HRmd&9TSYU}>k<>=&DX|t= z23CL|W@F_?Yy|XSt;JXLDHdWj7AC{fbpC5;TA-z&2G!JJ(x~0V?vmVHl1scv8*M5g zTk$^_e=t$_`@r^@IR2e7QDMxO`OoXpdhe4y2xtI3#*i1$KI9g=%&O3;68Z224vyh6D`p)=NtBty-{U@5vp*cjyO-m@Y=#YIw8gtDrNTy2E` z6q@7F0oz*Q8_LaMCUFA6G~n&J$nR8T#kEGou!6_te{yR5q?VI9fOW7;8KAa2{9x{fFKQ_!lU(XYO6mp`E8aXB|;Q#OEb_=CI zCia;LE^nWpzmm({#C!9d7}1A_RVG!FuLHXR0RR6#*K6lwH|zbRloU|`3I`}(jjV8k zpYG?XOj=e1Br9uVHIgEaAWUn7t^fZ^D`~x{tus<@lC%z*an!~Lh(iv@fT`}QuiwAE zs`G^EkSA#RXwIx=9I{GZQmf;H*u5b|7D7N~2^;yCCV&iCYnPdtEt)gPrY6t;AX9E; zo;T0)06oI*)?qI|!QsfJJS*V--cq&w4+MlIeJyDn-}le`nG?QxCSdMPPL)YfX8OMi zz}y8$c>z++a&;{rIa$!8{Xx+VcsL>Gn^NKXYICtl?gxl*J}Eg`Npu3sIcVj+TAMk} z)=iVD%w5$fDou(ij z)Q3hWh=LhfhG_n>O8Pb@|HqvA?}SV;o==fdL(eM`a#4cRDj|qUSRob(Sw1`F+UfuQ zPFg=}{d_xg$Z&=rJ`|kDVUWod0YSiH zRty7YV8R_qfFtNFs&sxTLF14MW3O?*IBAZVQ+_}c<@Jl#uN~kGd*hVB8H4i%mu@&$PTx3t^H6nA z^{aXAY`UAx&H2sKH!t71dYhGGXb;`ti$9}LeKfOk(Jmg7@o9ViZqmu}aNJ^&Oxx27 zhWGw{#(3NbI;EBx7o>2COUIW*2i+dt#A7_cQ(Rz4H0i|(t03=1LNXeb^zy#b*xYb%Y50`;fwWm&M49h1{+r91r za}c|duS6QHw~O!^`{-P)1vGG9Ed#+o(VeZu2|BG^cmX`XQZ5oT2bH=kuM%N&QZ73S2M&6L6S+H;9dE0AQ@@V&sU*GwiGPJ+RrF#oi zQg%oSoMEUNUsg1m;1%*}=jTEivMZ}ooeAkFO&N+Ks_0)<5e{_M0*`w&pw_t;Fd5H+ za#CFtWzk0BGP8~mf^-m^BSAd`EMNlEKel`Wb<)QCP1gzX2SZ*+U7b9L?$Ps?215}yqGo1-ce49xq zz#f1t0BZnC0CT_{R3ch)A0-!^OWFo^;#S*BEP10<<{g^?4$~4r=3<-z1yw{uQKiGQ||LKLUgqm zoq-nD^@&SP{stIFw>oJ3zSS0tz8~2&sTqYuLXFxtrz^&~3eT|^^{7;=OnSRwuXp`; zL9@Mwv-Sc#=L8&#In|DTQi~`5$$`c<*j?^jq(%d(hNC|5(pu4R+97-WA8xpsX_nb4RGOno zwYh3EX?DU%|2o6sf{QL|)u!Do9XdVq$P2H1*X@U&e);W>zj_cT2m|b!Bh?03W0j7^ z%mHQ=soCTzSg9m7he#DnHQL-Jm>NCZc)uLdg5@-hGq@}SF5tOHsFlREmfe1k4v_9+ z>qL6c>_tO)0lwET-?w#st8QKh5-Vtg2#_Ml_-gE87=M`=4m@NT;j$U~=9x$v=Ze8t z%*MJc+}WwmMMC7F0G${%2L3!aK}gj$+3mUx_aPT2dlP_E&*R>w-wa2sGHhP6jNA+U zgx&Oq9|Gn-TEY-OEq4U~XMj*3fW8ma^*G=&hZA+gpWyQVj{@};k>V9{ld*rSooq<|@F`OepY6BT8r>MDU zT_B~AmF3}u!7n||a(R$W2H*MfhhGMKPbm13evhTbXjIu_y5bpFOeDOJ-Pve9zOloY zjIL4IthQqb;HPX`dkM&2_-uIc)&|YU2|mb(vPsmOE;FXcbb0W+=4dkg*2sOA?iiTV zzNx?O?Odx|9(O3!?*f*}fc@X>yYNqm)!kXuDaR_u3(u64hR$2zt8QnRIn9hl1UmKS z2(MPHP^Ah6fns_m)DDx7d_g=Ty5gXJ_CfDGEQW(02JhD3ONuq{qFayNfo7cail4Zv z?9`d_vc(RnyDCv5f@!lQ6KmzV>|(Hc#YUK?U>f5Ik`j9ya4k_DXPI>V-qT&^D(akO zW;5mj+o?FF;_aW>@Eot&TkfhggKrj`jvIRKN5}|JOTW0HabdnDfSyABtj42tO63lB zZ0*45b$doLrT^fll!zLr(WPO1{0ugeQb)iivAdhJrA~ZYG0H) zVf86XRqldfVt%EuHQ5}8f-m@D4joW-4qS8=8B=KmQfeC*%U#uew!7nvJsV~$ zC+)RmV&y&oCLsVH;(M{jHN7%c!*s<07Q=p3?2R32b`H9oRiwdJJ6Kug#tcgtcCGcz z4-e>`qT-ZFPTH9E4zqcBN`(V=QX|4K6s4T&6XmCT84S%n2v7c448#V#yx5S22GE%X&V?YJEyPhbB+J;b11crG>ykn~~oE42qC-YO$Abh+No084a`ZaeLYSN&9t;*#2I5OeF!!Xm~IqXKIJVpqsCpp zvNMq7oZiS*uIiDs_f9Emu;Mb#5ss|mz&jk(iZ8FhloBNuy=t5>SM|z;Px)=;DJ$O= z`RMazY~xQE{hPezIbEKHVKBx^5U&>l^v~9VPDuf|VsaW7s@@DNxhfdhk!&Sr-&k%> zZ~W`Q5i)dZ*6=ARPDWxGwjZiZi_L~KHwhF9vp}ZAzo$uw*BFYwBXx zyT~q7r0;=Y#iZ$zT&R}1Y{1#@&0M;a8%v5rdpWHdl?`l)Z3HqX*+LO@9)AbW1Pf$t zVlkf&{jFw?4K`Vy8bYt)%AUnstu?k&D&|n#MKG=Ms>@hqf*-HG;CYyqgQqJ3SaKj3 z%|zH~tOy6xFc!If^ZQ&XgHfK`a_KZSHc0MSOO1pmhzul~jvT^Plvk@`AvE;-3yPbN zdVbbr^L%Pxm@NQHBamA6KBvsiGWnNUCQVPo%B^o{+0)-I-P&;;A_cm)!0PJC??td# z+|6O7pGC)5(YPM7eaHSPqkPjCENCzk)6;2SsTfke*bIFoC3bDdTZ8S$kBE)YTp6tL zQO$8ZiMfa+lb3FC8a&mLPFp$XB{BKsE-;(6Nk1V9n|_3tqrLdq`JG8yU!5O6;$N7_g<~dRTnH7m5lyhx4 zk*%G?59$}ySgI`1A<7`p+HJ=M!SZsDO&b~aAs1E#Q~#M8kxZAJ8W6~(c?L;Tz5@v& zz1shKnA|EE(fLh`idv>%Z0xdHhZ_DmD4Hx6cgi3s}y)&;_j)X*=_B z?QUD)?wMEX@W>i3+BbTQZ}L4DY=&9(ER4rEl==?Xx5FV&lj8Q>uUOS*LC2480(I>=7pgi1alrMtm*8+s!Vpz$IPZ?(DVotDT{Xz>( zhepHTqd9afs_MTED+7D zP>dQwPK9Cx6-W}XMXD4qe^nVSp~xt?0J%^kSAjf*@)apis8E5TQ@LVeHW+I{vGIB; zm7DQBiQH=Cf07dW}d z#U+f(Y_)Q8E#0-dMdUUIcR0Gs!99*TXxyjON#j9Bn)ZqQ*yD`N!@P6{0}jj(1vqqa z-H!klQUMN%PMiyXPMxRSEDtd zZWwQLIN)fWS<#FDZF*0e-pw%B!eEOtt|+m45D(%Z3NSORIhl^-_YaLRcD7i4es?pJ z=@n2wii;v5CLtx`%7amis6KQ4^Wu*LM-!C+`p7}&dv*y1~jJfPfIB^%#sZGn`lW^zoUu6l>3+-!~{ zx4A5Jk9)!!(tEtl${UaLk~_hX*h%OFjq_%OL{0#LbE^E`Q>*Vu6_&tsx~RAiM*sDQ zouQ{cH+wXE^lx4Ou_}T6h{17%Wq=O1b)cy%PlFj4O$>t2x>2TIjP6!+b)RdwxO(+q*6aqs*jPe+!)f0GX(9gt) zKb|7w)cVMBLL7}Se;lXT4UiD>r$ykHb!Ziu8k)_I$vAU5hMGkUZqeTWtU7QTO8Tdh z;{psa&vKxo&^-C>5-}8~j{(Gks;6ouSEvPr)bB1$ZnTRsb$7eEPVuf67SL#7niiHe zQ;k%H7wr+jSXiP)pNSGmMM385>_N>AB;!#leDG_RsB=6oQnI+ZH$!BPQ z2~Lhug%6%zE?z3Lte^Nv4ustIRvN^j-tKkEp>+I3y*p93Qa^b5WdSoMmSJ$(&2*%k ze)Y6a5Q*3b2UjxD#Ks!csoNl-F^2L5Jzqu6peNw*L-%vmyXmff~qQSc%q?AMxJl5)45M7CA+DhzrIOSR9qVlx?Aa zkYUAC8FYf6oK26&pC5lo7m*2OR7m90WA0S7rin7cfShMm<+wXVI{!B^S17$aMm7-0 zJ_>=&0MiK}7zEt9= z#JBs~p1j6JRf3!3`=t zfzOc_$V=oan+oa(b%L6sp2$M>lHN)mqfgLp)1lm!+!Of+3xmaHiY}&&`IPf<{iU+f zA-=l4mp{V4&yNdKVN%#Dye3SEW%8IjDbL9d%1^3u>IL$k2y^6jJFJ^0-l->+_5xAE`~ z?&huAr*7Z6{pe3m{Bqmwy}#Z*yl{Bi@Y8?Xw0|pXLkBRTXVP25f~`GZZ`hAm_JiBy zMlAm(E`PJHyabs0?%zKL04F?_8;|%tFu6)|1W1)?z)}OcArKXvtFb^`)Fi;R;s?(G zBnh>3?lZt4fWv*w_P7gnfq!FmO7VC9-vMZTMUny618QbjfQ1lHYCaS&LII1xj(@ag zya0eNTF_d+_+n`cgFyX|y7Zg23IQ@p1|VBE+lBTyV}OQLY_j+r^(1B6y!is_=C7pd zE=XOsaKmg`w5ebOwABf~M%7ksL@~v7qDpe>^zSD{$y1PN?$Uz_c_pD|Yw6u~41^On zhZ!+SM$6bSPK*x|%tSH!=Iv~k%r9Gz_8?#(M!`0zV~EGM1Kzw~7NcO)>-T1Y`gUIM z`*Vv70RP{oJ=&liP>H|hPg&atz=y9rT=TH`;r54_C4_tY!B}pL{REI;U=MHm2FNv~ zwju8Y?DItn3gN9Kj|733hlM&-VA~_QF^Bb{;xjr;PBGfsMML^zPsA*G^V zLrKVvGY5`bIQdD>k2fE_f*BMt(F+y9C>+ej4dvt#}4nP<&WQXA~`u5MA`7vrudi80jn$d+_lrHi5=Sz}fS6ufyoydg@ld=mbD-8PL24(89)B zV-U{b6Ipqqg(C9EdIhF6$TlGp>U>l(1y&Z*#x=d(PqQ|vZ~Y=@_6Ii##=^e znc-cd><0kr6&xl^Z6KBm+d0G1O|l)vK3@r%)ETKS81Ia{D;3US7U5L3NS=S~Aui z74|49sB#EgIHWQ|j`<7$erS#8kS#C&#~P9F8tq5)m;e92a0Ea56yb;b{M$H6Snfwx zt&STYH|mrro9=zJg}AqXuW_+)Qlj_SJ%~(DW#qQk6($jT7*mf)&ja znb_`RrD{a+ZI#CN4HO6lx~NlZ_i^>jHy+FSb1ytpAM~H1{&TLr3(vcp)Ox3%iheo{ zQSEQm^vw9|Lq&7RL2q1mvsm+`Uq60dsker^yf#4G?aE2F9)-xnzH}?fTXn>zFDBKK z%Z)WqYOy_~L6mDTxr61GwG1>V^&zWRQL9nAy7jRCW%(tLNjMs<&4<-$p^OpTzvj+v zKmCcM`{(iBq50D3;az`tli#n7ROe^-b)BQ){sWkqG`mIe%+ zLbL5q6Wpcie%TF)^G1*G_LOY8STwlk2?r?sUy0b{x^Y^E(ktA}{H+1)Iq!C_)&H_= zK6&jv75||t?rfutUk{(!E$$G4$D~Jn>H9im=Wy#bpX*XCm9=k^n*R3NQqqoH6ZZ3H zuncFytNSk22?yP(@d_fu@-gG!3EvD+za>GMm)yJ)cp5dvj+s4#VpCb?_{nKJCDzW-H%o z>&O&wwDCH{LuZ3kqlaV5r_pf1QzRgvflA$B-}4UQF(Sz;D#|tMUhN5!MW%?~{&w-; zF=y_9{yA=mW^d>;A@)YmHiWJ$lmWEO$k1EWT6nfzSuuMnq`~3Omq7+Jb$% ze`}~Is*H=iQXQCgkZ!y2VJp1u*2dS)iH^`#uUgnNP7W>ApUxpx^d5dd*k+fq9AbR{ zDL?S2SGqb3CyX^}?wQFW{UP`ajiwWfM`x<)bZH-Wpu@b9z#!uU#tGWpMt{f{;DVL! zG`r|I*sbInbU@-BOBEN>j4jiXJ;PPRAE~D?tY*g5{<80kiwqH<^t7LZP_T^a0AUxe z`%kUsbN;gT9o|DrwFL)ew;H!ugT!pI=%O`nO7}8U^O`d_*_YFBtf<14lVNr7f2>|2 zSr&x&U4DUswdxun*XcR&ygNW!l|lajcI8BPJHwizp!N9QKgCPIcL>)@VgkHf99dScwRG0X@2jACOX0ZIeUeYu)OGt|v4MKTD<`=4$fRXfMqhH=DtI$V2z z&^OCeXI1$G@c}mSkOthR;h?p8KK3CPA9NVo=yi9QVhl1yhnuJ^=gcRP-X1J=_gD33 ziiI|*Ay+h1bvmC-pVGC5-`S7Zk;#as<57<62#qE@?jVycgTSC}g|{$W2Q6}|SUe(s zcB^6Y&cW`&bo&?UUmCvQJ3r}G^v5!I*(?UF^x!jA1PvL?SnYpz%zC_MZ&hf{Q&;M? zB`#d5%N*WakIGM3M$Ht>QKn|EZCd-x{)xpWO0IT2v)xs_{bE=Es{dRXKhe1DG49bQ z)pU6(2CC=I|C;FdNaxjkLXbxvZ{hmB4@xcLAh!11w1sF&$g(0#DJ9y|g>^4%2g{h#Su!mvpX2JWG(9DNJPDq zg)N<#t}<_gQs#1w&6_}M?agiXojm@JyH6b6U45C0^4K8kr`pA3`r zKWx_=w;f;ct{jhpJT-k)0zKN-=RCHzEA1cGTk>V*Fr~~A4n#d^TzG}sBU;Wlabr{^ zPLw?9ZOoY{x1akLjK;aZycP)GfA0QpAVX)i^QN$CfKG)n2GtO71S!B{51K8G&cM^d zkViS0HWC-8+nnRBDB(1lb#lgXfom=Sh+2xosRopS}#U(3QESwCMZQPDJSvQES zZF2TWHC<2#AGPv4Av3C_n5&)*R$S2L4NucX|7&)d^#(VDo&Y%2JEOPtQ|w_%T7(b` zwZvLyrnkNy;F8sz>Mk+YF4w6dc?p6IY0Q}tQzSPbpQ zuf~%bR^sUP?_pfRfaqfCOqrlU<6A15wlvZq^>0AI7e+@AfH)5_-YZJ-X#)eVu) zbw`=)BzT+W?Q&WyxH~{S(-vtk8?)zz)q3PiE%et~EXt3r4;53LBhg@HXmF_Rnn{B< zDPSRoEp!$vZaiLdRH$L?zF>3q!d^G0nF^jE2deESwhb$tX zEk*lb?L0|p=lU(EKF-y8b_Xe|h)RKYdSWpwei^S+{M$mnf-1;v24X z5p(T_5?^mb2K3r7oY0Qa45ADPjcBW%wRGj40URM>aW@f05Kq+a_xlpiO6~EVVHMKC z4hM;1&F|54H(fwmQBZqWoyyQ#?+#KPdR*^tsWkzPPb`Cb%$hN$OFf!3L#mLpfh4{( zC__0Ry2;{+15Ys>vv7qrSKRBx;(%BqIs0$=(OS<{V-j{m>nHVY1)?&!-KY-4R+5!~ zIEeDefN$rd)JS-f!*MwaHA}ZjBW;EAqt2}cj2}N_dIQG zV_O5xjqv4#d*D{S+AU;-O@0z~C>=^hstM}QlX|-t*D_KxSfp21Y`^~2#y(9PJtk*H zRQ}Hr#ap0CwjOh#C2c7(Fs#HcGDVPnnEFZWe9?g;#E}pg@2UZQ@D8kCgV%t#&8fqE48|Dz_SE zjlI0@H6^5^MI>@0NTpQ3Y8;uEX9lYPc92|uZl%E1!0AkFi0QZw7mQ$^KIIAV+Jk2E zPI#Zt@X;R-mFT){qZ?qzppp?%w(Aal7}#8DPYM)-G)P%8t6+txja8#Pu*G<9oYb4Ry)K%DkMj*yqg-^_V6r>RKY&Vy#cV4ki|D_$x=`T-RD^=xDLmdA z_mc*K0B(7Yen!+~lN{b|R!##gP z^k3@9g5>TC+b^UaS@*9^$^9?=^(Bx!i0T^;C0m>RQxd!7)U|8%UnVb|kV6oOjakvd z<2OHuf}5sffA9pO>cN_}f~Llw?f!MEn9ArtZ1BRS-5WOQTD~r=Zfb2#4!)^j)ibtw zD-KRoRQkYkN!j>bWqn4u!kwDNyLB^~j z3q0V9r4HQLvm6@Os{CiNEl%zB*`3m7YX>($)&-fV2DC7g_N++UHemqz0>Sgkt(BC ztXJApcAHH;7^ZB%XOg{+oKWf4LSBL>uMBL+(}A#LVw^*X3HR^@s4go_z=m?`LR zGI@5fnCY~0!NEc8>%&iBksHaMuux`nP5%5e5Kvu{BB;~qj;5UzRsTj^kV+NINUYYZ zL>W{;i0j!ZS4AZ4wnCi~B9{-3nP7t#4TpTF0gjol7-UzerBnw7HLuqVp_p-aQ0E8_d*pvjha0Xn7WL?jHZj8B+NENq%g&RC ztz3pvEMYoe7I33_#=?dpqoanSV-b(nPSPJ48%4q%pPxLiYd3jt|Cs)RyLb5yfC+Lt zd9nOf)OOUD_tb7P77giXX^T#!yY1eW{2$R+zcTi}(xP!gR@$RWUSAoJ7=!*u z07)(XNw#~mCNHl}UDsGwxAm=-=CZ19qh_tf_=~8G@pFT;RY@4Y2`KWA^w{@?uM`?Z9Db$w zo3m9cE-5+=!e)K_h@=-kr6Ks;$~-4*Spo>g$z2SQi{f9e+JKFt_ zC)Mhb6@J(8{$eHa`?2b;OM5zPGw~CXFXu#j=9D82&}*?>_W>+MRVd~1A-O`K0P!aN zisbif#xjR$+rSfUT-9$>tPlhIk&uA!Ceb=+4w2H{3A|6X6?aoI?2iKMku!O0!4-F`JG4d(+mChq%pVpDotjA^(f{nv4<%XvoX`BM$Ad6GOFX=J$8KN zF?r_ER)vzo>uoXZ_FboCLw9rR_{DX&v$(MCB~<68$J-kYH8Y1A?6cOQtA^zZVkk3h zn%<2Tn|q;NF#6?T|Cb))K2Zr+1C?VjB6;A}ffbnmykBuA z-kkg!_9F&T-Hp9;fF}laKl@f^%`?u$S6h!%lvz*ItZ{G*~C>}|; ze7yO0T79kW%=3J%ODJ50VTBM!9&?(#uNQo3@;hTPNmu~}Y@=6(7nnIRF?T;Lc)cdf zl0VJ?FT_+tcyEHnz&UU^cG-65*+Zawpk~?F3T}MqC~bJ9Zn{DSG-qzl-_W;9bH^`C zT)3fTrwe$_U;2(y6u@+*>NpXiY=}`dga|JJ-5F|W5&pY_58Q?n3b|rPF4qz4LacDj z>wcC!iHuYNgI3d+GuS%zUYu4t>iTFmi_3Eq2`U!93@|VSR}WMh#{NbI4v%Dw$0lbF zL&=e2!RT!RglNWRQ3{U;o}3kjH@0gD0mq|&Wdm`ONsbB+-cTGvsLxUCm0PB2rRu(T zr*&mJl=SXUj?T)i3U*qqxDt*lda55dyHE0v=Hj&Xv~UGaH@k$g4l7tk%}ykv!aAGI zY#I2D((H}}JaUB2ghgsNg3As+EzbH%O6f!YC9@gV6X_N`E`_%i-1`1%g^YKb-1p^g{~>PSzA<-@huS<5VFFv2OJihu%?C9BFL6nAfFHVH5) zT<&EBnfyPA7jD~&q?RZ{uZysh&WxY3U>B>Kzy4RX4sukvE5kgefPL-N*USldawx`V zR*HJtTWiruX2bs<*dl-2)&Mq?y0)n@GpcPaw`w($*`^xgUX`;|w@bB+tRlEJ%f~T( zk4~3F7R88Go8x0xm@v%X)JEmfE`vcDXF0VJj48J2+(rJYISyC+!QuRg=G7Np4{;Y^ z5`^G@UJt0#xG2g=s?|xLB{?_f=Ey^Ib9b5&8bwm8RdgrRCR3Lhqgcjdmxd2Q+2Izwon1JqHWUWF^k z+3xYMceqo%3dopIThryHFokOJ6B;P&6s&kXf|W1|8G_MG)+nXaV(HK8&;Ek$--v9x z0X?!20V?l^E@|9QRX2)uuTkMYRxVDL$Ag{*R@D&s@I9*!t#)2hgKoe*afD z20?HywuYU*23y0}ZI{DU9|GRS-mbZTot_6+f4=eW?;ae??rcRbm<%>aX_XBPl{9?A zrhI*I-7VOYYj&>{o_Okppqaet{*9MP^86Ed;0v7z(hWFZqmQGd`Ny%xOV43;I3V#^ z`#)Y#Whtfg56g#m?HLX8sr~!^R;4^DlPbW9`MwDH_g?^OajLb~skiIHvX3Xg56=4@ zcr3iDIB({BAsmSK1A#DX@rMI`e+1TZ_9*=KaP&9%J-+GOgQS6f$%_>E#^wNpTt1Y< zB1r5#;61Rjj@*k7GqHDB0q>y_@Y2apGLX^|6l{xxm*VCn!1J4PB&>y3gH;=v+Whu8 zgA)*qCr(w*+Hf0H<>yl&ip7FLnkX z!TK)J8=S6PWC3rh$mc<|&xVBqxv&~*p8|iM*K^fp_uL-H?TMitNTCJT4irKI_CAFe zp9jr88%_`9LhI%(vPA)vb+a?{f)jeNE%ahf=*8jopk8vd-Gm3912=TxjmC?yvsZCT zsrmv~7z+mx9gB}?@6TSpGCN#_bMj=1u}fm^B;tp1>?}sPEXF(6z`lG^t(be0nh=s`=qs%ly4Jx<>6{x{6FiTp!?NDU+YW@YR5B~%Izn`5tXTMYL9B>Xghv+bUppVFXlFc3MI(xreZy&G^+K24J z`bZyiT|ZO1qrBbr=j{I{8jm@UrB1yw$UeKV={P{&zmG9@QJUhz+=YY=t#~8hD?N0PeDr~U1Hf8f{fi1K} zmiTwN;ftTJvxmFRkGmr`FS-0Zu*b?A2zy{`5)}ZZ{RlLu3q}okPm9_o%SkqH{@4dU zy^#A1*Q&PDGMCoEJg`Vp1I{9vEOAH5VpEN0x<$k@w94b|FGkL&$t?O}BUz`Tp6D!^ zW-=Vnc6W;gQ-+6}eOUxP-xNV@Q7af`uJpDiI`^hKkDYG1dtA5xTVQ~N-2mrK`#C`H zWbWvBu}}P4+oyvx*u9wp|1EPgLh3f&S#ft}|UXQyOSB z73|8#C{TGWNly!rQaPD~$C;&!${sZEa4J{{vYHEdSX4S|WVTBXBIXBZ#WR=E8igtS zv(iz08l6a|xpqusWZ5NoSX4TSvbY2xq8>mCBT{RmT^6<#s6WYhURvg1Z{;V4 z&v#F005uItPuXola#hf*kRLy9-9c=wZBZIW=+uH7Ph{8PY9H9ypnRAP`aF&!n+(m2 z#EUDN>dDRywPo$yF`!LhK5RE_MRI+8Q)yIn-2c{0l2niNQZtvlq>GeJuB3#>qjnG&aH;&!o-S}-X#IPoE z5pn}^Ke3bDLx#~Wuo^@NyAk^&Sx(Lo?}=~4UQyN4<+S4$$M0hwc8>O7J#3HFv*kGQ zFS2pxN#Z7=qiSQv$s5TBsG(FQwM3U1S5voAkMUA@xx8{-rFq+YX-@PJeU)aN{u+Ou zyT>iJ9Iui%CvjEc_Qa!t6u~}EZhu!eLD)HTk^PitZSZ68e{g&VA2NnVIG?+3^D~=Y z-TcS!hj3STa(Hv(6900=f1{hD2a~^ye~9I|2djRaYQ)E?|CXK|r^bbGXS}h1EeMh& zRiuuzSaVIy9W{^CyioJgn%`AEtfUu)i}<1_+j3Q&l$W+#yXDR;|E?a76LBG4UTP}W zZvAtuxU8sm*Z#MDf9mS?#v|Ep=JZw*Ba{D#hk zzcj3C*xS(9aG~L~hD!~-2fX}6&10vu80GFA^pX>4utlW=Cu*!f3 zGH|+f5_MJmsr;YXOLK|!zVHYG_RGH^)p}}s%xrj)XgHt%0NVq|NFM&hZUT^?`wR4q zv>$XlnQUqI35K{K*(bXrwxLnLC~m?(16sNYp1I9Pi56Y1g)Ukjdu#;}KfnK<|E$m> z6np2%2`lh-n>+o3W6JdO+iM9P$ecDGcwp7n0U%rbh)ZJ&eJ|JvU!$dozTKN;^SOx? zwQKp_%W&6!KKkGN)*wvMCW*9!katDK%QQ)lbe42It#|Lj+C{X`Ay5Y>`dUFH&y5pY zb)vJhSivdzo|b7R;QLE4Ve-IKdCuW+C()A(6;juH?B+8b(iolx%&@PK0!OoH zs!-91MngFYIwo;C+}XUDaWM?mMoG}Sz%-@0V9}5-O-+t&4v``hA$ocx^&>ADAaidb zNmKR36Py9Ba>?h5u2NrXZ?)2eb$8TH6+At=G9!U0FTpJ&PNAtjBpRsp!|NXRI z?FV5r?^S@ajs5$7?#G0aC#ff6OCb+xD_7X{U@78!Q?ND6g6YBb3*6$NfKq={ED1AX zn=CA*RL$5<&GO;_yM&JRG8M<68Oy{99FbQ@>q>FD57*S;q&FF2v$B#pEGB5*1(~$E zU02766gX%&?Ro2quhviL-rcLFqiPF1p4yn4T((xRE4m@U92OP1RsqG9ytycDe+VWj5&k3Ji-9zd=8@DDq z97k~lQLPftWUT==~DD3 zfsZBRf#(x4krX%X-rp7&H6v`2jEzonc@#;ktu06$u`ll_TYHtAMgF-v(LWz>f{(Py6bS{S-B?<;dD&<30T2S4DxVn z`nGsForcoNLU3APtT8d+%=O21c0TeWkurF9c7=V`q@Z;Chy5^C8(;_X1CDrbIA#+? z;1oMFp~i9hY$u!IvpT3)bS)s}M>Yls&fD`oOs$+iNv4EGhRA+w^7cHWgs||T7!$&p zn_hr4@x8>uKkC*0EC5&<(8qxI4f(0X_u^SK*8FC+Zrgn;Deg~pGR{I!BlH*-H(i3; zW?YYhcnXWP#Zp`y7#{P9g4s<)8On9OA__@E8E-YML~|Czp5-byP{A@)gGF<^`@MDg z2S=pI$+y=M@gyd^Yu~gWUiCq%GMot{(N#gGgyw6uO)_KbK}RxK5rvewU1D>?IC{>ND1xGvT^|pm7^EW+4W#dlwW>%B|a~~O;V@elx5KiFB_RElQp25)(x`NsmM~D z8jkv{yh?nSQ9h4U)GX3pT(>ubi&K zLJ}RXIitQcTtbdoJ)XJ06f8N2unvr?nh69ChiW8P)Nzw7EbEGub?IAungsC#D zg|&+MTBQtsZ7HMpd?st0!C|iA3Q~4zVxa6fWHZp{5Vw)dtG1!gEpV|ZFlCmWNa|p6 z9?+nsN^H{?{ZvwpNzzA14L9k;vE53^b?Ck%-}xgzu^Y6vP826gGTM* z4-{~uD(pOi&DlLzwT>#)2hdxEf_A0q$s zn@8LkW@DhdANZF1a&W)&;c?@W#it?Kty1di#Z@y8fN?$f8*RF@zuq&dVoC(zyXf&Q z`QOH67y7}~<9|V&44{RZfC_Y8seVA_GZ$4(UbbL+N zbM91*B!Qt1CqH$u(4Q4ucs-j1F*0ZYrUEUl?<4H8C5I~hjhr9nOvN!lK$L8c?%dPO z$nwK5EhSZdO=pIIJvc9+$n-xrzE725qqa%>8%CChg(66$h+-p^jv|v{B1(6+#)^ul zR}@7^)Y+D7Hl*)F?<)idu^ue)I0%+o!cs9qbHPA4O-fHxfILI4?+KWyin@P>D#y!B z61@RO8!Tk8_YCCF)Q!>avgXo4NN6nP^}3Z_JI}P^HsD+8_89%rCn<#uII!11A|0(h zIU;7%i?(KFdQ-EZ!+0te#s_5yzEVbvZ+Z;--9~?U?7$$mt_TnIP`Yc53Q6xp=`MVX z=*{_EI@P17iv?bRH~qD-vg4pj4l5d>cT(wS*CbCzQibzst1<0-NCUAi7O8L?(TFM3 z^f{*9==z;8IOi@z?{F!01bVFXh%!)3L*F&0BVxqNT3{hDT=$mzbU_q}e3(rzT0wE* zrG>(&NW1KnVWnWJ1gjF-U4xPt5{BeGv+HR0rfAWyzP#8ODkmx1nH~)hd>z2`OywK5 zHElMQv8H2Ttr?%_Z@wYA^J1|b+Y{UfAnYPeW|HV+xH`{Hr!MKxV&bdc@)LA zX*a_5XH^pLWSCFvkw+5^p3%@SA5y)xTVXVet6DgbPiHa+k|d1L6p=S3Dt6Zti|opD zrk_@3dy9sHKAx5@BQkyOVp<1>U>Op!i8Q(}&lfi2;>Gvh5BvRI+%1NCYxj<`_M*EI zDA{;0A0JD)TPH`IBe^9I7&4PagUlGFLetueWiSFp26KZ{+BnlRSC1ua-vrJ!(mbjh zI-T5U%?cpK_Hr!in(4HlGMsYOQLB|kgSm(jIpC<~1UICU-{!N@LnMp=~x?qn>P$|pdYiiFpJ;hSXb3U!X!l^5iNp* zNY>NfJgD4@RjX(?*n&ri37|IDqK*Woo#;Zs4VWt8N$8vAzf!>LgCpC!rTR>6?!e8% z$1A|NP`Z;N`k|`JWtNUk_O6NSBL^JJgQe2yPOXfW1Es6b^Hos{ITqr{m_<9XNX?M6 zD8feR<|NSZ!mz)%UDkRY47vjM0qrJi#CYa2&w}{in%0I|&G-k7?VF&%wI-K|V30wV z%=xmp?D&@kDas(Wdv_k{W`Gm_7B@LOjUFX&=qe=kY(q{%LtsIbfOrMm(2Q;VpkWitB1MP= zwWXpoibLZ9Fv+ojOoV2J!a17;v4shn$ARC=I_QKE;WX{RVGM6C)g~kk8Mwg zq{5xG%;rjEB_mZs?X@g+YJycYeGpm#mXi$$;l{Ux>L3R}KCf#bU-i!cAm@B2y zR4YL_!iwyHTQ_suS+I*-Uz>&7fzh~LmRn;y^Y&6$K+fr%_b&ly^+csuv6j+ZZPeCs z@9LS>Gj*m3z|RaFI*6_93a@}00)aK@MiS(^1i=A;j9ta~Mh8W;CUDlx+wvB`K5mfi zjQSBblOt@-y@id`7wy`MGO?aDb1tUcq@&!DamvVfu#8+FiWW|qLnVFp@m%-M=b8f& z#^?hS6=|_}?Ofm{?~`os%bC1F(}!w{B|pI$i+HdFFN|!H_nky8<$9=bXp{<6Ay+bNv1nwJ#S~j7$j;KJiuw}^_;5|-xbj$AfmNy=kt)@3 zij7rx4YuC6xf)u>*LJmNUxK($mkY7Mx=*b&XcbbWL4;2;A2Pdp)a#KG45rG|A=S(VX+;H%z*j*(KxR4x?*m8#tM{6fd)(ebz#*=-k^_YVY#3!os+UG_Tb` zZ|DcZyw0GEC1o74XuG>9%{$dp<$&%1WDR{rRw1VbI+1u?syhDVS!6aDH`+EJ;oKP< zk8?I-XxGt+IaTkXzep2lU8le!J(TWRqyD6IQT8=hTZo8hm$`Zr?9v4tcA(dm;BJ@< z)6ivUsK9yG#SbxU^>OAp|+ z8@Y(Gz1O(R!3s^9e= zF>i{J2H`0L(?;t%X6g!>9=#Sa2A6^j!KuA5N)M74T~sNG<+1@wiZZE{-OIdZ1Sa7O zqC=yPKMh19@lYrl3LLA%MLhbJ+;pv{Y;&@}XQmA+rgdyvTU@-ps@G?;#+h_cVS|gU zP?hC9dsVCq2t998tb~Ri3W(?ws})Ozp+uk8E2<(ST$!i1>v?IRMR*HSiLaYZnH8#c zO_K@{?f#?DRE(|tb277Cv|WQb|=7;#8ovi2s}yx z*YP>A(@>ctq4Mb7e6p4rnY=AAb=oIb0E?Ve)LB_1TOvoL(piq@-R<|yZMWePSu|<& zVDm5;#JdWM$mIIVQdwp!pLV_Ed@$bQjVd`ztF?BE3O`?MVf8a+?_K2j0)h18lAiZs znuJft^lo&!?fj%yoW0cPWGoapbS(5VN}@P)Jyw?7OQ*s_+~_1mCg|qz)PEo1;U@=B zv<;l;x)0MA6&yz-6lg4V^b_JfjYkH#4m5|n>twPnOn`zA)-$__)|bPKJ_7IT#$H8G z3Ec`+6v&%YtvOxs8s9S`qcGhh~T<|D+6MmQ(%OUC5Au2wVkZAk-?0j9kQZe9-8f=Ny<0Ucpw+HSxtyQlS*4gqs`H?;@!bhN@c zEN}ToEve%I{0_FR`ai6J)dz%I!dnGZ+lP33bqTgNe2$C>IX0g!D0X>%)rDNGCQQGW zUy|~W$9g7}NtDhO!PGb)`&cJL%xCQ4(5{V^IVrfy7hY0aPzzmO? zwB%TrD9uA}=v9sH_+FaX<}fIy)f~FDonIZw14f>*Fa%}C+@{^_xI~q(AJ8b;s8yd` z%+;V-884_)b%KTt)Iv)Dpn(S#5nU*j*ii?kVHEnquuXG=>H)fwMl4X7>V#Ia%qljf z8(VJB0f2kG^GO2+m;JyT2HNcCoKJ`faaLlvQ}ayYT6=pqxo2;1ORr}Jx~DNH4FJpm z47^GG8{w>Nk>OWun7zyjaDubaHAd^gQ0MR_aAYD{*wljjJ(m#1KX5<) z`zbbx!8n5PFur<$bVHKYdsXmELI)()8?JP{*oBfHAB;)sxy7R91?5rzcD$GK6qpYWsk>up^ zBKvz&)assAiX>tJ>Nh<&3(H^`dUb5d)BA8^$GGFd^TRlnOBIyG<&cd}C!-=u#Wq=1 z_t9K~#5To(`AIRZjzo`~qCEpGzuBC~Q=WlBIVB;bQkV{|jkPURqQd(9H>Vg0++zkcv$(Eq8m_t7zM55kGR z0Sing({WfCS<2{rgw}2@#6jq^PL2@0$cHw*hGRL^3GxfysX^KdMTqC=!}Ng)v0lSb zgX2Q4e{iHI_*$;g(Br+8FgNDayBR~C*r0JRQ)pZh_$Ghai;~_y-Bu3;d<9f~5g%rK7V??CNXJwCJ6l5+2J&91-fx?A71!4M(22^ zlBKb;+|ablnWTu1<6)QpIF^+%txbOsQdRTSi8w&XAxEV~1kT%)PNP>7n&YvepG2<> zvbs`8nwaVGisf=do0E5hNuNXCowHcQ^qvtr!Pz|%PDhfG#J$DbfW@xC zy+1yV=RWQgl1sR~r|)j1Zfi_RZM0T<5e7aG7G)tPrR315Vk|wM2j(8~Fqb+(5YpcB z0;0!Sbs=-ZI>GGpb-^HL3oU&xcZ{X`j&JKJ3wFZJ;FhuBI#|uNC6Z&M)xeg;yB*@=L14z=3X^bE6fFqj%)Ik#Mo(zty~kA<0qKs)WLvWsD$7i*AK zh?lyw?jwaIB{tTE;Y=Hte5FdGyCBHSG+RMm|| zUs92S&0O{vqN|3Rmy^tV@ruL|=aBL#3(v%u@?`pDAx!Cb>xI!N1R%sw=WG0sPfeYnY9EcYakwGgm% zL@X8K(av1=&?#7hC;E4Ba8W_LU4R+s|Ks%V3|!{9LCDIFKs=BOMR~qZxn_ z*!oqt6}FPKz;yi`a0g7fNHH=XQ!uvQ6|EEe?v%O|fdIm_kZkV8!eAV^pJa4IPEr|> zk_?ZelJt%gk_?T+SM`rmK?Xp5OX7O<63l|xe&u59N3~`I2$UFS-Oz}mOv$|5zqb(sm*pc1e?yLj~;c7fviVJcXnY9>m zUBEyHEuo>lEhaje+i6Xfs2nPhQtPl07{_W&3oeWTybS3`U81TRV zQ=KvM!8Gvfy<@TJJZw(8fM}uV{Tr}HWZFFsSI$g2@jvkOd3bQ|$Ch4cFi#*L@aQxY^Gpe$-ey`KyEXuRz-eD?wIfm-@}r_(jO@#x zwCpTxXqQ3=6|M`U_r-{1em+%^1~80l_qzpQ# zn7}AVzFqE9d*% zUgRB+H_p}(DhA1ux#T?S18 zzobk$Z5o$RpSrDDdypS)6?-T{iIt+R^FpLQpRZv;6&waxtL4^w0x5l=k_kGo6iHNM zT@<3;DmfpQP~4rGql!|eOQ%c`E2L&h@QbFzLhGhe%a%NdO02XI0HHLWPYQwnd1--Q zN?cqRQVS(Bf;Iy~(1_twQU3)gbJRcv_mP^t2Kxb`OL*W z3NT3S0WB;Nh2cTc&+F|W>)2CD5&_3K`PX?1u!GSE<&lI(Dvi?@3agf)xWJv1K zSuCS$v1=u0-0Y|tFJlXlR7O7JqY$JU{Z%}U0TdR=S?P4>q&ZqS2No~pdabDOMO7Ri zGs*2pCWEk3ixkbP3b-P^hCLf*Tx72l54*{*8)y617Rgkj%rk`o!{_14c;N9qz(g2d zwTMzOLXL_?!>#;UG8!2Ns{G)OPmn2M4AI+r0EbON9$(3kTQMEqR7%WPGV`!KS&nMzWhahk4rbEBZ8Qag6|f!U@S4J1Kv9Jd@B zqf53c6zkPS8_iS)dp7Wcc*dMY6K~AF{kD%FBk}tB?C>D@%rnO3itXDJ&(`tld8&dZ zgIq4TPBLc0o?EaF)*LZiJn(qldkj$ICTJfxB!s(HF)a}tDQb*7&WlZZvgh{Cp8Ac9 zs7Et*?{*&sO5@|wjl5LdK^W--*CcSo4I7(YE-x1f>Y*R5u|j8cX8=g{7EH|UN_%#= z863lWRGr3vvyoxEAg!nF@I>A1#QAGGo_70*={Oa#1L|mFy?=JBPo>uzkPy`chqGfN zTlFQPm=V6n1?wxOgKzO(01za?X%kvPQt^x>4)*(WW{sV`H`cTEE)X_5k*w>PMDnL_QSW93 z0dCZ1c==lZVuaP^;;)}?PW%>ouK6ME1_yEY;C_IF{=wCK>ZPl2$Yz8B5rBK$ypWzf z-&KqTfM?l3LU|@V{z0S}nvM`x4HN@_mWWX6yGwr+F}ip)V31O7DTD1KmjkjvN%;_iZD@<#G<)*xWE%YGJ|@erPIe%&8x7;NzWSHF^WJKisLu;;oa!6bG&Jz zoVpNYAgSoA;gObT%vbD&JoV#`5$e#%BQi}`%9De28vyWpiuvjI<{F&<}=)oTwu+$)MCVNP2= zBvf421+Mf-+BkcPyuFkloj-@jh1X(?eTDq_eE?vI>3o%H{waNWsf#*s`5k~u*~I7i zG5a#kthX0wkvpq~01MhB!OOtc7De8LxvaxJi>1;s2#`)C2oB4SrCiFVKmOkz|M*7w zDtu`(=CiKB8DUMiDn|NWjr}SbUxEYhGJHK)bY4yXlic!x$@13^4^w!$3BV1}m$u={ zM7q6Cmatn*%OYR~bDy-}A~;CaZcJVXlRBAg5ZHPNw@6Q&f=RIh;D*o@g5#4>yZ%+a zHV!x}+D#wwmR?*A&ZH~U`Bm@S5c=l zX-S-l;o*Qx2w*d(6w1+N&OHLd&Q1Mo7o$l$$;C`bWzBc3uf9h z^guNwsagi*UULQN6m=u8p6^Z+oqRTfIn3cwwK+3+i9SbYadx}8lITr{f;hUA1h~g4 z38=pY>9pkHYQ8UKC07Wb46!k@#(c`FYo;xRgvE7tx{TrAExB*EJ@>HfpJt zZs0+9(a&yYT)3*0Vkl#qn+0N{A(rYOeT<-r_)z=T3j_<9oe>TT$2Fqp=4LVZZNgb7 zVznZXk{G2Fne<#-X{F48?DvoD^U6-s$-#$isd}-6$pNN#g81DBvZ`es&$~hIM1^LA zX?)VcrCBT34Ii)7c^%#&!z;BVaNE85EqDn&Z41q6K2R=>A9<(8Ss?C|d$f0D=kT{_ zAOlQt(B=vvg;IN4lI+WFZbP6s`|}q!qi)J}nk^wb`vNK6mykW38|&#}oqFVM46oGGG)E{7ScA0UIwapAaj=pGjw3EZ?zmMB&M22!$}|VR z-nN-Z2$jI&XxBp5`ekq-K9O+>g*~P^3gj>=`9#p}6?&yPds8a&i#|122&Q2{S@V)E zn$71kEbiEhRQxn~?TRyyXzZ85J+u_XCFYeUWrde%{CN}vMo!}>37wkucE~Jn_)QMI zARE4L6FET~!I=zEtyZ;!wYeE&J1P_Ez6s;Uz0}j>vLHrGBUolgm@`SkR!R{TSwLy9 zQ15i=S@tMF6dDR4E-od}e1DY&-Lg9ktKH08%4FqQy;W|eiJ}`3u2*wigU=^&c93xc z-Kx65kvKAfl=B~s$QU+jJ$MaS-TP+c(UzAxft)DH$xIlyN)QxC5%<%{jsbPp z0g$f%u<)bMa=S)E5naep6xxQIg-P{B&kOR3APW7oE2ZqCPB6Q{Ge!tSnlg=$3+Qr| z;9Ttaao;urL~4nsi8snds?to0rPOJ2=ZLQJ1X;jsP5^S}ZMSbKn@z<+H5OCG%v2+d zpeV6zfIBIN6e1Edmd3B7bUr094M!SIIws<1KIBL0Ofwg_UV5W>^D}Xka~P^N4@xNO zcu{rAtWw_9O2y(p75R%dftD~k$$sbr8CYO@6C+3mLcm3RRm^1~5JJC5ApmH|$zj=2 z8_{fXzzCx>6}QWl_h}3ne*kv~?%y^zeYrd4XX@`JW3dZDC5xg9iV<|{BxvCu;1Y1@ z7hX}J4Yp6}oC%CnroNKLjpY=}*QQj^gaS08f8b>`G44gwyq5I>(oHnm)y}piklonm zaPw$vBy@B%9np$0by~%*h|zYc7Ycfb|NknjRUr?p`X`gVnbLhrK(FbcPR8VaVz2^~ z&X!JD?jZ~yc8*!~SuP!S;a9L4j?efi`?r1o2MTnW4zh}tp|<@&W$4jAz+{;4zAa+* zsp2bQaFFTBoFh7p6CPY%B3`RfZg;f$YsY(ODR_6Wv+MgWk3sq!kqd1AFu37HE{m^H zb-UWmmYzb=%ToZ1B&&*+86lST_49+VX8AkaJsoT?o}zpf?OR_b#i(-6>C@KDK{qKQ z!rhHMeJZmqT3fZ$5aX47v!*|njBcteHblAPG3n@m|T~o^7h)1)Um!`yo+U3pB z(__Kaj39}Uz>B*=tEuieWHgx&X>eo~jzl3CJ%wYQtY~`t%pJ#!x$GIZaMDJX_FgQ#Ney*j*v0a&|I>m8YHf)!~LGFy|a+!VHdO zK`7O`*`en17OS~GS5IyU;BzY{<42kOYeamgrwg zb6z5s=3MnnT%9R53VB8g1zE~;6onFYpZ%0-C}=t5QqGiVr~g9eHdt_777)$Zozn%@ zem^UP86Dbjpc^GYiqeXnLe@=)TPq-EAkUQ7!K?zce7JHF&V`Z<#BG8I!dh}VT-@Jf zOsKhgMY|Ol!KKJ+?=+iB3m#KIbhUGK#;FI#525o!-MSA<%x!{^AC`!Jz`0Rm&y!V0df6Z(Pk|m1$!_h70Pm-e0ZOXpB*fQS?p(`S0+dS3pUsxsy`zpC$nwXB zNfUs(fSVVn&S)I0dW0PuEGV*;ff&C07;*zRuxeH)Sq$eLhC_~HKN#s=KF=LR-)oRi zRs;Ldq;IW$pPoMPBkCMu%M$AYd+-&s_=9`6o1901wW1r4a^)3Io|t~0J#_)N(M}sM z4$$dYd$is?W7jPUA>M6gQ^qYrHU+yLyoFFLtiVuhFf`vj6C`<+PgH@}fXJ1xAaa6f zg+v@B1jy2K2NjrU>3N(iqno<65JejyUuWOx? z%hfFogJlMr)@qqBWQLtut-*}2n1LS9H``p8ze-PTeh3SZU#wV7ZqWxHJkI$M*}x!q zVFijH+h6d<0U7kmJH<=1$#)YP7J&}Zv*9v`)H-J*#3JuZkB{iSv%w07ONB z$~vPl-EwLLs@N(`>vnO17HvLCBfH+4R8Q?qT?NH(4iU`*63Viy#-Uv1k!(S&zB9du z3r@{y7mT_8cB+}roOfY3lvnsWr0oy|L>-P&*Nv0(Bq67tj=tDSu4R298CEuxGnYFt){KV}>y+zF_D$ z?$B^7-BbcA+$y#D%i^(BIhyMT!jz1)>nf&gUlr}DDaRL?htwJjgn{bDdE3u;o`#kc z?U|-&T;ir_*)~B)Xu~2(-(zXP1X;#-Th2(;WG(FjX{)krVl}M|64CY(g(pa%<=IYZ zTS`Y0e=NfcKQV1;8@Xa&t12FGB_5@pp|{6QxiE6pA?OQzvD!sR#QU0AE{r8?WY**$ zQ{XVkLSq9L)2v9;OaV11r<}8_LogloYm_x)Em!hVC;?i-aC~pZ8ZT2_KW;f8Uh^d< zuz#A46*QYcTC;NU7H!bM=D}`qMY`W`9DfP;fo@yV(!1VLwh5$SU~EG z0GNHYN-kcQa?pCzRv=y=tbf34nYk8i3>?=_-l7k24)#KLz zgOPs|dNA3kBTy@Ba(H_&)OBXak5#Y)MB7pT3D7HvS4HMAHNL2fM)*z)(b(46(N|Pl zfSuQf&+zTdQg0D#5@wX;lUW`I($anbv_^b_!_knSs@k4nyTo_q16?yVm_8#6CO`{ZXadb9RnsWh zdaR||(X|c9XHYH=urbMImnGX!dLz)#HA3g?nFD8rYX^D=ueNr{Pf+0|4q{l$(H7Km zU+E&l%D6C}t6By(d8g~iAZRKOsyln|)T{Fr4V6l-g&LpsO2Y`Kp_%H2XyDxu0S zRSZqlP(`Fl2##?1erm-~8_pT%3@T-59fXyzHfMF`sM#nKU^g*m90d$h>tvo_OcToi z2LI7#a7uB&%zmp%%o^9(TSn>d{elO5Ic9xZRW(X7yp#GWEnYldtUd4}f3VR7*S-9zw5`vj>)&v;?@&_@6&c@qc9!m^N-Uij2z88c zV79*7cs*~w=gbc*=3+27FU{T3-D5FFPBO5(qL=AWmt5w;l^cUr|98bz?!mu>%`49$ z(dL?LFIZx~dOqIq<@W_`9{7fva=a`^u#hf%qt{vIvFh!gdcYykY=+of($BDM21X#? ze&)GfG1jE16u~&|P!RdWZvHr?dCizJZ`NT`-7mqH68Qz}GEB&#Wiw3|77-QmxkkfN z0xeF30~^$meajs0OWOSURn*}k3h@vh36KzpkT{Y^BMTj?$YUKvY@&=R>S&^k4*$m) zyV%Dej&aiLi?6=LIr_N7HEuD)Js$Cd#~818$A^T7hSpw$t?X=ZMil{)RPXr4kJv$n zL@32KLKlND|98$|M;vv`ZhM^cFBU5P2}odq*yE};$2>LWo1Nf<80n%4C$M8uX(gc# zK5D?QrL)Xve-|D-PD0Z=VKD~^980XR#U4kHpmD}!uSi$9?Y=wi>dfHvUu;g6Wsc9} zO$_E5J7ne#X@_`2Uf4gBUXU0+klolXkt#{HV$=F;BFp2;lZ|o46JzUq_HRm7GM*)e z1~d7t@hPe1d{0ByeD*IlWU)Ixo|YQRPvBpu2*lqx5o@Wze39m|;`5sM9D(Z~05m1A zpt%CCvcPl*3@mszu;ASW0)r>u^PT~?4!|G)0)tou006uy0D=Gj1Fyo4$9Z=sJ4#kT zYSEDdQd%GbNA4&bWnQIqEpta}&&+>VIL*kK`1O$Nl36O7%p_Cf<-e3MNdJkH?EWhe z8J?)x#9w6Wi+_BhFv3iY);C3z??oGFW-CmV<;m|inJ0hXqfh*W6vAo4@{;b+`?2zI zC0r3dzL5z7(he2utlaR&DZAg$QJ7ZkON|>V5#UGyg?Jn04deNOjP=80M@W?YQdJpf X`2dJ|TjfQ^PeO1*&{|}#0RR91V-XbY literal 0 HcmV?d00001 diff --git a/frontend/dist/assets/index--tYiELXc.css b/frontend/dist/assets/index--tYiELXc.css new file mode 100644 index 00000000..479d0be0 --- /dev/null +++ b/frontend/dist/assets/index--tYiELXc.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-outline-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:"Geist Variable", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--radius-md:calc(var(--radius) * .8);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"Geist Variable", sans-serif;--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body{background-color:var(--background);color:var(--foreground)}html{font-family:Geist Variable,sans-serif}}@layer components;@layer utilities{.inline-flex{display:inline-flex}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.shrink-0{flex-shrink:0}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.rounded-\[min\(var\(--radius-md\)\,10px\)\]{border-radius:min(var(--radius-md), 10px)}.rounded-\[min\(var\(--radius-md\)\,12px\)\]{border-radius:min(var(--radius-md), 12px)}.rounded-lg{border-radius:var(--radius)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border{border-color:var(--border)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--background)}.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-clip-padding{background-clip:padding-box}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.whitespace-nowrap{white-space:nowrap}.text-destructive{color:var(--destructive)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.underline-offset-4{text-underline-offset:4px}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-destructive\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/20:hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}}.focus-visible\:border-destructive\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:border-destructive\/40:focus-visible{border-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-3:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.active\:not-aria-\[haspopup\]\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}:where([data-slot=button-group]) .in-data-\[slot\=button-group\]\:rounded-lg{border-radius:var(--radius)}.has-data-\[icon\=inline-end\]\:pr-1\.5:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-end\]\:pr-2:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 2)}.has-data-\[icon\=inline-end\]\:pr-3:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 3)}.has-data-\[icon\=inline-start\]\:pl-1\.5:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-start\]\:pl-2:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 2)}.has-data-\[icon\=inline-start\]\:pl-3:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 3)}.aria-expanded\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.aria-expanded\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.aria-expanded\:text-foreground[aria-expanded=true]{color:var(--foreground)}.aria-expanded\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-destructive\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-destructive\/20:is(.dark *){background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}@media (hover:hover){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab, var(--destructive) 50%, transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3 svg:not([class*=size-]){width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*=size-]){width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}@media (hover:hover){.\[a\]\:hover\:bg-primary\/80:is(a):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.\[a\]\:hover\:bg-primary\/80:is(a):hover{background-color:color-mix(in oklab, var(--primary) 80%, transparent)}}}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-wght-normal-Dm3htQBi.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--radius:.625rem;--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} diff --git a/frontend/dist/assets/index-BnivQHi-.js b/frontend/dist/assets/index-BnivQHi-.js new file mode 100644 index 00000000..4ec36692 --- /dev/null +++ b/frontend/dist/assets/index-BnivQHi-.js @@ -0,0 +1 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})(); \ No newline at end of file diff --git a/frontend/dist/index.html b/frontend/dist/index.html new file mode 100644 index 00000000..66408911 --- /dev/null +++ b/frontend/dist/index.html @@ -0,0 +1,13 @@ + + + + + + fn-registry frontend + + + + +
+ + diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..143fb45d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + fn-registry frontend + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..0de59957 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,37 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "add": "pnpm dlx shadcn@latest add" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.17.0", + "dependencies": { + "@base-ui/react": "^1.3.0", + "@fontsource-variable/geist": "^5.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.7.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "shadcn": "^4.1.1", + "tailwind-merge": "^3.5.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.2.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2", + "vite": "^8.0.3" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 00000000..bdb9c5eb --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3609 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@base-ui/react': + specifier: ^1.3.0 + version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@fontsource-variable/geist': + specifier: ^5.2.8 + version: 5.2.8 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.7.0 + version: 1.7.0(react@19.2.4) + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + shadcn: + specifier: ^4.1.1 + version: 4.1.1(typescript@6.0.2) + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + devDependencies: + '@tailwindcss/vite': + specifier: ^4.2.2 + version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1)) + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1)) + tailwindcss: + specifier: ^4.2.2 + version: 4.2.2 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.3 + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@base-ui/react@1.3.0': + resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@base-ui/utils@0.2.6': + resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@dotenvx/dotenvx@1.59.0': + resolution: {integrity: sha512-+LthcJBVj18x5B1Quua4XditjxYdafOmrXT6xxq+wnUldFQ41hfv/vrP/Z4CZkAk1OTdQSqgFIlVc/pUU+pIzQ==} + hasBin: true + + '@ecies/ciphers@0.2.5': + resolution: {integrity: sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@fontsource-variable/geist@5.2.8': + resolution: {integrity: sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==} + + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@modelcontextprotocol/sdk@1.28.0': + resolution: {integrity: sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@mswjs/interceptors@0.41.3': + resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@1.1.2': + resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@ts-morph/common@0.27.0': + resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.12: + resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001781: + resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eciesjs@0.4.18: + resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.328: + resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + express-rate-limit@8.3.1: + resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + fuzzysort@3.1.0: + resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-own-enumerable-keys@1.0.0: + resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} + engines: {node: '>=14.16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + + hono@4.12.9: + resolution: {integrity: sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.7.0: + resolution: {integrity: sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.12.14: + resolution: {integrity: sha512-4KXa4nVBIBjbDbd7vfQNuQ25eFxug0aropCQFoI0JdOBuJWamkT1yLVIWReFI8SiTRc+H1hKzaNk+cLk2N9rtQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.4.0: + resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rettime@0.10.1: + resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shadcn@4.1.1: + resolution: {integrity: sha512-nBj+7LYC9kzV9v9QmRPpoOhfW4KctJVQejywdAt/K+K+z4RYlJOcO2a4AaF7elrRWkfCbgXeGK02liV0KB9HvQ==} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-object@5.0.0: + resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} + engines: {node: '>=14.16'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tldts-core@7.0.27: + resolution: {integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==} + + tldts@7.0.27: + resolution: {integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + ts-morph@26.0.0: + resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-fest@5.5.0: + resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==} + engines: {node: '>=20'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + engines: {node: '>=14.17'} + hasBin: true + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@8.0.3: + resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@base-ui/react@1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.2 + '@base-ui/utils': 0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + tabbable: 6.4.0 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@base-ui/utils@0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.2 + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@dotenvx/dotenvx@1.59.0': + dependencies: + commander: 11.1.0 + dotenv: 17.3.1 + eciesjs: 0.4.18 + execa: 5.1.1 + fdir: 6.5.0(picomatch@4.0.4) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.4 + which: 4.0.0 + + '@ecies/ciphers@0.2.5(@noble/ciphers@1.3.0)': + dependencies: + '@noble/ciphers': 1.3.0 + + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@floating-ui/utils@0.2.11': {} + + '@fontsource-variable/geist@5.2.8': {} + + '@hono/node-server@1.19.11(hono@4.12.9)': + dependencies: + hono: 4.12.9 + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/confirm@5.1.21': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/core@10.3.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/type@3.0.10': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.28.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.9) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.1(express@5.2.1) + hono: 4.12.9 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + '@mswjs/interceptors@0.41.3': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@oxc-project/types@0.122.0': {} + + '@rolldown/binding-android-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.12': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1) + + '@ts-morph/common@0.27.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 10.2.4 + path-browserify: 1.0.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/statuses@2.0.6': {} + + '@types/validate-npm-package-name@4.0.2': {} + + '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1) + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.12: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.12 + caniuse-lite: 1.0.30001781 + electron-to-chromium: 1.5.328 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001781: {} + + chalk@5.6.2: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + code-block-writer@13.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@11.1.0: {} + + commander@14.0.3: {} + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.1(typescript@6.0.2): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 6.0.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@1.7.2: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + diff@8.0.4: {} + + dotenv@17.3.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eciesjs@0.4.18: + dependencies: + '@ecies/ciphers': 0.2.5(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.328: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + esprima@4.0.1: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + express-rate-limit@8.3.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + fuzzysort@3.1.0: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-own-enumerable-keys@1.0.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphql@16.13.2: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@4.0.3: {} + + hono@4.12.9: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@8.0.1: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-node-process@1.2.0: {} + + is-number@7.0.0: {} + + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-regexp@3.1.0: {} + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + jiti@2.6.1: {} + + jose@6.2.2: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lines-and-columns@1.2.4: {} + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.7.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.5 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + msw@2.12.14(typescript@6.0.2): + dependencies: + '@inquirer/confirm': 5.1.21 + '@mswjs/interceptors': 0.41.3 + '@open-draft/deferred-promise': 2.2.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.13.2 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.10.1 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.5.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 6.0.2 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + + nanoid@3.3.11: {} + + negotiator@1.0.0: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-releases@2.0.36: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-treeify@1.1.33: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + outvariant@1.4.3: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@4.0.0: {} + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.4.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react@19.2.4: {} + + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + reselect@5.1.1: {} + + resolve-from@4.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rettime@0.10.1: {} + + reusify@1.1.0: {} + + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.0 + transitivePeerDependencies: + - supports-color + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shadcn@4.1.1(typescript@6.0.2): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@dotenvx/dotenvx': 1.59.0 + '@modelcontextprotocol/sdk': 1.28.0(zod@3.25.76) + '@types/validate-npm-package-name': 4.0.2 + browserslist: 4.28.1 + commander: 14.0.3 + cosmiconfig: 9.0.1(typescript@6.0.2) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.4 + fuzzysort: 3.1.0 + https-proxy-agent: 7.0.6 + kleur: 4.1.5 + msw: 2.12.14(typescript@6.0.2) + node-fetch: 3.3.2 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + prompts: 2.4.2 + recast: 0.23.11 + stringify-object: 5.0.0 + tailwind-merge: 3.5.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - babel-plugin-macros + - supports-color + - typescript + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + statuses@2.0.2: {} + + stdin-discarder@0.2.2: {} + + strict-event-emitter@0.5.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@4.0.0: {} + + tabbable@6.4.0: {} + + tagged-tag@1.0.0: {} + + tailwind-merge@3.5.0: {} + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tldts-core@7.0.27: {} + + tldts@7.0.27: + dependencies: + tldts-core: 7.0.27 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.27 + + ts-morph@26.0.0: + dependencies: + '@ts-morph/common': 0.27.0 + code-block-writer: 13.0.3 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + type-fest@5.5.0: + dependencies: + tagged-tag: 1.0.0 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@6.0.2: {} + + unicorn-magic@0.3.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + until-async@3.0.2: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + util-deprecate@1.0.2: {} + + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(jiti@2.6.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.8 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.6.1 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + web-streams-polyfill@3.3.3: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.5 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 00000000..444f4ef0 --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import { Button as ButtonPrimitive } from "@base-ui/react/button" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/frontend/src/globals.css b/frontend/src/globals.css new file mode 100644 index 00000000..fb3c7e98 --- /dev/null +++ b/frontend/src/globals.css @@ -0,0 +1,130 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; +@import "@fontsource-variable/geist"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --font-heading: var(--font-sans); + --font-sans: 'Geist Variable', sans-serif; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} \ No newline at end of file diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 00000000..bd0c391d --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 00000000..77f6fcb0 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1 @@ +import "./globals.css"; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..75de54ff --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "paths": { + "@/*": ["./src/*"] + }, + "ignoreDeprecations": "6.0" + }, + "include": ["src", "functions", "types"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..90dac2e4 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; +import path from "path"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +}); diff --git a/functions/components/.gitkeep b/functions/components/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 6982079b5f9f77d8578961b2d24ef677ed03f4e0 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 28 Mar 2026 20:33:04 +0100 Subject: [PATCH 5/5] chore: gitignore para node_modules, dist y __pycache__ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade .gitignore en frontend/ y python/ para excluir artefactos generados. Elimina node_modules/, dist/ y __pycache__/ del tracking. --- frontend/.gitignore | 2 ++ .../geist-cyrillic-wght-normal-CHSlOQsW.woff2 | Bin 14692 -> 0 bytes .../geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 | Bin 15308 -> 0 bytes .../geist-latin-wght-normal-Dm3htQBi.woff2 | Bin 28400 -> 0 bytes frontend/dist/assets/index--tYiELXc.css | 2 -- frontend/dist/assets/index-BnivQHi-.js | 1 - frontend/dist/index.html | 13 ------------- python/.gitignore | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 149 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 961 -> 0 bytes .../metabase/__pycache__/cards.cpython-312.pyc | Bin 8335 -> 0 bytes .../metabase/__pycache__/client.cpython-312.pyc | Bin 3996 -> 0 bytes .../__pycache__/dashboards.cpython-312.pyc | Bin 5460 -> 0 bytes .../metabase/__pycache__/users.cpython-312.pyc | Bin 5470 -> 0 bytes 14 files changed, 3 insertions(+), 16 deletions(-) create mode 100644 frontend/.gitignore delete mode 100644 frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 delete mode 100644 frontend/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 delete mode 100644 frontend/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 delete mode 100644 frontend/dist/assets/index--tYiELXc.css delete mode 100644 frontend/dist/assets/index-BnivQHi-.js delete mode 100644 frontend/dist/index.html create mode 100644 python/.gitignore delete mode 100644 python/functions/__pycache__/__init__.cpython-312.pyc delete mode 100644 python/functions/metabase/__pycache__/__init__.cpython-312.pyc delete mode 100644 python/functions/metabase/__pycache__/cards.cpython-312.pyc delete mode 100644 python/functions/metabase/__pycache__/client.cpython-312.pyc delete mode 100644 python/functions/metabase/__pycache__/dashboards.cpython-312.pyc delete mode 100644 python/functions/metabase/__pycache__/users.cpython-312.pyc diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..b9470778 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 b/frontend/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 deleted file mode 100644 index 2000e32fd286b53023a41142571f41e6cabf42f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14692 zcmV-qIh)3JPew8T0RR9106Am;6aWAK0BvLd066ae0RR9100000000000000000000 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 X`2dJ|TjfQ^PeO1*&{|}#0RR91V-XbY diff --git a/frontend/dist/assets/index--tYiELXc.css b/frontend/dist/assets/index--tYiELXc.css deleted file mode 100644 index 479d0be0..00000000 --- a/frontend/dist/assets/index--tYiELXc.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-outline-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:"Geist Variable", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--radius-md:calc(var(--radius) * .8);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"Geist Variable", sans-serif;--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body{background-color:var(--background);color:var(--foreground)}html{font-family:Geist Variable,sans-serif}}@layer components;@layer utilities{.inline-flex{display:inline-flex}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.shrink-0{flex-shrink:0}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.rounded-\[min\(var\(--radius-md\)\,10px\)\]{border-radius:min(var(--radius-md), 10px)}.rounded-\[min\(var\(--radius-md\)\,12px\)\]{border-radius:min(var(--radius-md), 12px)}.rounded-lg{border-radius:var(--radius)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border{border-color:var(--border)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--background)}.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-clip-padding{background-clip:padding-box}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.whitespace-nowrap{white-space:nowrap}.text-destructive{color:var(--destructive)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.underline-offset-4{text-underline-offset:4px}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-destructive\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/20:hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}}.focus-visible\:border-destructive\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:border-destructive\/40:focus-visible{border-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-3:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.active\:not-aria-\[haspopup\]\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}:where([data-slot=button-group]) .in-data-\[slot\=button-group\]\:rounded-lg{border-radius:var(--radius)}.has-data-\[icon\=inline-end\]\:pr-1\.5:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-end\]\:pr-2:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 2)}.has-data-\[icon\=inline-end\]\:pr-3:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 3)}.has-data-\[icon\=inline-start\]\:pl-1\.5:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-start\]\:pl-2:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 2)}.has-data-\[icon\=inline-start\]\:pl-3:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 3)}.aria-expanded\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.aria-expanded\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.aria-expanded\:text-foreground[aria-expanded=true]{color:var(--foreground)}.aria-expanded\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-destructive\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-destructive\/20:is(.dark *){background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}@media (hover:hover){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab, var(--destructive) 50%, transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3 svg:not([class*=size-]){width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*=size-]){width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}@media (hover:hover){.\[a\]\:hover\:bg-primary\/80:is(a):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.\[a\]\:hover\:bg-primary\/80:is(a):hover{background-color:color-mix(in oklab, var(--primary) 80%, transparent)}}}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-wght-normal-Dm3htQBi.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--radius:.625rem;--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} diff --git a/frontend/dist/assets/index-BnivQHi-.js b/frontend/dist/assets/index-BnivQHi-.js deleted file mode 100644 index 4ec36692..00000000 --- a/frontend/dist/assets/index-BnivQHi-.js +++ /dev/null @@ -1 +0,0 @@ -(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})(); \ No newline at end of file diff --git a/frontend/dist/index.html b/frontend/dist/index.html deleted file mode 100644 index 66408911..00000000 --- a/frontend/dist/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - fn-registry frontend - - - - -
- - diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/python/functions/__pycache__/__init__.cpython-312.pyc b/python/functions/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 14d1dde4e37d58b36de84ed4550759f7c3bedff0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149 zcmX@j%ge<81Sx7KGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!GSSb-&rQ|ODNRl+ z)=$feFG@|%EG{Xk)Gw$k$;i*sPb7v$Eqm);5Z(h+jcZ#}7e5 zqNZ61QBctV5sE~`j`2Cskw|RuX#CFZzT?^XI2!etUF!!QlJ_mfzS75TIdyQJ+u#k0 zSyfR)k8jTojqq znVB}?O8(W%qLCsk!(3K7uc*Z~Zq(b&-dKiFp1j&+d~e?`3U$2iN7|K}Yrj3U_S;iq zsidu~QkP8?@>E8}PCoocepyIe+E!eaU9}<(v-Q!Ys_*g)^`l9cJI!5nk=Lc_O0P@R zdwN zH+mVDx|sckrRg_6klko+JRw4*gm}l!Z8hxolVH6;8K@!(v*0)tTCVH>>N41t`FfKE z$3<#KXOm{ZuE~KANt)zBJlvM1`xrNIq2yD2&)!DkojtPn-r}5pX7|6avrp{o!eRWx bxv=>Y=SM|S!1-lkaDKMkf%DqackQ`9px_z* diff --git a/python/functions/metabase/__pycache__/cards.cpython-312.pyc b/python/functions/metabase/__pycache__/cards.cpython-312.pyc deleted file mode 100644 index 419861642c5810e216752dcf079dad1293614130..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8335 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 P3^+(;{@aO`6#oAJ8Y2|# diff --git a/python/functions/metabase/__pycache__/client.cpython-312.pyc b/python/functions/metabase/__pycache__/client.cpython-312.pyc deleted file mode 100644 index a7495427ae1be86bde8fd62c52670f65f314371d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3996 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 DfY%JP