diff --git a/.claude/reviews/readme-finish-spec.md b/.claude/reviews/readme-finish-spec.md new file mode 100644 index 0000000..3b71a63 --- /dev/null +++ b/.claude/reviews/readme-finish-spec.md @@ -0,0 +1,22 @@ +# Review: readme-finish-spec + +## P1 Findings (Breaking) + +- [README.md:1093] "Allowed characters are letters, digits, hyphens, underscores, dots, and colons" -- colons are NOT allowed. The source code (`_ILLEGAL_CHARS = set('<>:"|?*')` in `vault.py:450`) includes `:` as a rejected character. The README's own next sentence also lists `:` as a rejected Windows-illegal character, creating an internal contradiction. A user who follows the "allowed characters" claim and uses a colon in an agent_id will get a 400 error. + +## P2 Findings (Minor) + +- [README.md:32] Forbidden word "just" -- "find memories by meaning, not just exact keyword matches" violates Rule 2 (no "simply", "just", "obviously", "easily", "merely"). + +- [README.md:650-666] System Prompt Endpoint has no curl example -- Rule 5 requires every API endpoint to have a complete curl example with full request and full response body. This endpoint only shows `GET /agents/{agent_id}/system-prompt` without a curl command. + +- [README.md:664] Truncated output with "..." in example response body -- `"system_prompt_block": "## 0. MEMORY MANDATE\n\n..."` violates Rule 6 (no "..." or truncated output in examples). + +- [README.md:24] Glossary claims all REST endpoints live under `/agents/{agent_id}/memories/...` -- factually incorrect. The inject endpoint (`/agents/{agent_id}/inject`), system-prompt endpoint (`/agents/{agent_id}/system-prompt`), and shared inject endpoint (`/shared/inject`) do not live under `/memories/...`. Also uses `...` which violates Rule 6. + +- [README.md:859-954] Walkthrough section repeats full response shapes instead of cross-referencing the API Reference section, violating Rule 8 (cross-reference response shapes instead of repeating them). The write response (lines 859-865), read response (lines 878-889), search response (lines 910-919), and inject response (lines 938-954) all duplicate shapes already shown in the REST API Reference. + +- No troubleshooting entry for the "shared" reserved agent_id error -- Using `shared` as an agent_id returns `{"detail": "agent_id 'shared' is reserved"}` (400), but there is no troubleshooting entry for this specific error. The existing "agent_id contains illegal characters" entry does not cover this case. Violates Rule 3. + +## Verdict +REWORK (P1 found) diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..96e53e4 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(curl *)" + ] + } +} diff --git a/.claude/specs/consolidation.md b/.claude/specs/consolidation.md new file mode 100644 index 0000000..39faea6 --- /dev/null +++ b/.claude/specs/consolidation.md @@ -0,0 +1,37 @@ +# Spec — feat/consolidation + +## Branch Scope + +Background Memory Consolidation: config fields, Consolidator class, app lifecycle, tests. + +## Source Files + +| File | Change | +| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/memstack/core/config.py` | Add `consolidation_enabled`, `consolidation_interval`, `consolidation_batch_size`, `consolidation_model` fields; add `effective_consolidation_model` property; add validators: `consolidation_interval >= 60`, `1 <= consolidation_batch_size <= 100` | +| `src/memstack/intelligence/consolidation.py` | New module with `Consolidator` class: `run()` async loop, `_consolidate_all_agents()`, `_consolidate_agent(agent_id)`, `stop()`; supports rewrite/merge/split/enrich operations; uses VaultStore methods for all file ops; skips `shared/` directory; skips operations on missing IDs; graceful on LLM failure | +| `src/memstack/intelligence/__init__.py` | Export `Consolidator` | +| `src/memstack/interfaces/rest/app.py` | Add `app.state.consolidator`; start consolidator in startup event if `consolidation_enabled`; stop in shutdown event | +| `tests/test_consolidation.py` | 15 tests: batch selection, fewer memories than batch, skip empty agents, skip shared, rewrite/enrich/merge/split operations, skip on missing ID, LLM failure continues, stop cancels iteration, shared mode merge/split, interval validation, batch_size validation | +| `tests/test_config.py` | Add assertions for all 4 consolidation config fields, defaults, overrides, and validators | +| `.env.example` | Add `MEMSTACK_CONSOLIDATION_ENABLED`, `MEMSTACK_CONSOLIDATION_INTERVAL`, `MEMSTACK_CONSOLIDATION_BATCH_SIZE`, `MEMSTACK_CONSOLIDATION_MODEL` | + +## Out of Scope + +- Synthesis feature +- Version bump +- README/CHANGELOCK narrative updates +- Changes to vault format/search/pipeline logic + +## Acceptance Criteria + +1. When `consolidation_enabled=True`, background task runs every `consolidation_interval` seconds +2. Supports 4 operations: rewrite, merge, split, enrich — all via VaultStore methods +3. Rewrite/enrich preserve ID, update only body + `updated` +4. Merge creates new memory with averaged importance, deletes originals +5. Split creates new memories with original importance, deletes original +6. Missing memory ID -> skip entire operation, log warning +7. `consolidator.stop()` cancels next iteration cleanly +8. `shared/` directory never processed +9. Interval < 60 and batch_size outside 1-100 rejected by Settings +10. All consolidation tests pass, ruff lint clean, >=90% coverage of new code diff --git a/.claude/specs/dimension-validation.md b/.claude/specs/dimension-validation.md new file mode 100644 index 0000000..361393f --- /dev/null +++ b/.claude/specs/dimension-validation.md @@ -0,0 +1,40 @@ +# Spec: feat/dimension-validation + +## Branch Scope + +Dimension validation + auto-reindex at startup. When the embedding model changes (e.g., from ollama/nomic-embed-text to fastembed/BAAI/bge-small-en-v1.5), the vector dimension in LanceDB may mismatch. This branch adds a `validate_dimension()` method that detects mismatches and triggers a full reindex. + +## Files to Change + +1. `src/memstack/search/index.py` — add `validate_dimension()` method +2. `src/memstack/interfaces/rest/app.py` — call `validate_dimension()` after SearchIndex creation +3. `src/memstack/interfaces/mcp/server.py` — call `validate_dimension()` after SearchIndex creation +4. `tests/test_search_index.py` — add 4 tests + +## Implementation Details + +### validate_dimension() method on SearchIndex + +- Read the existing LanceDB table schema to get the current vector dimension +- If no table exists yet (`_table is None` and `open_table` raises), skip gracefully (no warning, no reindex) +- If no embedding provider (`_embedding_provider is None`), skip gracefully (no warning, no reindex) +- Embed a test string with the current provider to get the provider's dimension +- Compare provider dimension with table dimension +- If they match → no action, no log +- If they mismatch → log a warning naming both dimensions, then call `self.reindex()` + +### App startup calls + +- REST app (`app.py`): call `search_index.validate_dimension()` after SearchIndex creation, before vault scan +- MCP server (`server.py`): call `search_index.validate_dimension()` after SearchIndex creation in `mcp_lifespan` + +### Tests + +1. `test_validate_dimension_no_table` — no existing table → no reindex, no warning +2. `test_validate_dimension_matching` — matching dimensions → no reindex, no warning +3. `test_validate_dimension_mismatch_triggers_reindex` — mismatch → warning logged, reindex called +4. `test_validate_dimension_no_provider` — no embedding provider → skip gracefully + +## Out of Scope + +- Embedding config changes, .env.example, config.py defaults, autofallback removal, version bump, CHANGELOG diff --git a/.claude/specs/synthesis.md b/.claude/specs/synthesis.md new file mode 100644 index 0000000..f2cea50 --- /dev/null +++ b/.claude/specs/synthesis.md @@ -0,0 +1,28 @@ +# Spec: feat/synthesis — LLM Memory Synthesis + +## Branch Scope + +From `reference/branch-plan.md`: + +- `src/memstack/core/config.py` — add `synthesis_enabled: bool = False`, `synthesis_model: str = ""` fields, add `effective_synthesis_model` property +- `src/memstack/intelligence/synthesis.py` — new module with `synthesize()` function +- `src/memstack/intelligence/__init__.py` — export `synthesize` +- `src/memstack/interfaces/rest/memories.py` — add synthesis check in `create_memory()` +- `tests/test_synthesis.py` — 7 tests +- `tests/test_config.py` — add assertions for synthesis config +- `tests/test_routes_memories.py` — add 4 tests for synthesis in routes +- `.env.example` — add synthesis env vars + +## Acceptance Criteria + +- When `synthesis_enabled=True` and `"auto-capture"` in tags, LLM extracts statements; each enters pipeline independently +- When `synthesis_enabled=False` or no `"auto-capture"` tag, write passes through unchanged +- LLM failure, unparseable JSON, or empty statements → `[content]` fallback (never loses data) +- HTTP response returns first statement's decision; subsequent statements logged at INFO +- All synthesis tests pass, ruff lint clean on changed files, ≥90% coverage of new code + +## Out of Scope + +- Consolidation feature (separate branch) +- Version bump, README/CHANGELOG narrative updates +- Changes to vault format, search, pipeline logic diff --git a/.claude/specs/thresholds.md b/.claude/specs/thresholds.md new file mode 100644 index 0000000..b02fdf0 --- /dev/null +++ b/.claude/specs/thresholds.md @@ -0,0 +1,27 @@ +# Spec: feat/thresholds + +## Branch Scope + +Bug 3: Lower similarity threshold defaults, add similarity_ignore_enabled toggle. + +## Files In Scope + +- `src/memstack/core/config.py` — lower `similarity_add_threshold` 0.3→0.25, lower `similarity_ignore_threshold` 0.92→0.85, add `similarity_ignore_enabled: bool = False` +- `.env.example` — update threshold values, add `MEMSTACK_SIMILARITY_IGNORE_ENABLED=false`, update version header to 1.4.3 +- `README.md` — update config table values, add new row for `MEMSTACK_SIMILARITY_IGNORE_ENABLED`, update smart write pipeline description, update troubleshooting +- `tests/test_config.py` — update threshold default assertions, add `similarity_ignore_enabled` default and override assertions +- `tests/test_pipeline.py` — update `_make_settings` helper threshold values to 0.25/0.85 + +## Files Out of Scope + +- Pipeline logic changes (feat/merge-pipeline) +- LLM prompt changes (feat/merge-pipeline) +- VaultStore.merge() (feat/merge-pipeline) +- Version bump in **init**.py/pyproject.toml/MCP server (feat/integration at merge time) + +## Acceptance Criteria + +1. `Settings()` returns `similarity_add_threshold=0.25`, `similarity_ignore_threshold=0.85`, `similarity_ignore_enabled=False` +2. `.env.example` documents all three with correct defaults +3. README config table reflects new values +4. All existing tests pass with updated assertions diff --git a/.env.example b/.env.example index 59923a9..35f45a8 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# MemStack v1.4.4 Configuration +# MemStack v1.4.5 Configuration # Copy this file to .env and fill in the values # Required: Path to the vault directory where memory files are stored @@ -12,11 +12,6 @@ MEMSTACK_HOST=127.0.0.1 # Default: 7777 MEMSTACK_PORT=7777 -# Whether to format memory files for Obsidian compatibility -# Reserved: accepted but unused in current version -# Default: true -MEMSTACK_OBSIDIAN_MODE=true - # Enable shared mode (multiple agents read/write a common pool) # Default: false MEMSTACK_SHARED_MODE=false @@ -46,16 +41,12 @@ MEMSTACK_LOG_RETENTION=7 days MEMSTACK_STATE_FILE=~/.memstack/state.json # Embedding provider: ollama or fastembed -# Default: ollama -MEMSTACK_EMBEDDING_PROVIDER=ollama +# Default: fastembed +MEMSTACK_EMBEDDING_PROVIDER=fastembed # Embedding model name (provider-specific) -# Default: nomic-embed-text -MEMSTACK_EMBEDDING_MODEL=nomic-embed-text - -# Auto-fallback from ollama to fastembed if ollama is unavailable -# Default: true -MEMSTACK_EMBEDDING_AUTOFALLBACK=true +# Default: BAAI/bge-small-en-v1.5 +MEMSTACK_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 # Maximum tokens per chunk for semantic chunking # Default: 512 @@ -81,16 +72,10 @@ MEMSTACK_INDEX_PATH=~/.memstack/index # Default: 0.25 MEMSTACK_SIMILARITY_ADD_THRESHOLD=0.25 -# Similarity threshold above which memories are ignored as duplicates (0.0 to 1.0) +# Similarity threshold for high-similarity matches (0.0 to 1.0) # Default: 0.85 MEMSTACK_SIMILARITY_IGNORE_THRESHOLD=0.85 -# Enable auto-ignore for scores at or above the ignore threshold (true/false) -# When false (default), all scores at or above the add threshold go to the LLM -# When true, the old auto-ignore behavior is restored for high-similarity matches -# Default: false -MEMSTACK_SIMILARITY_IGNORE_ENABLED=false - # Half-life in days for importance score decay # Default: 7.0 MEMSTACK_IMPORTANCE_DECAY_HALFLIFE=7.0 diff --git a/.gitignore b/.gitignore index 86bdd19..2a7c37d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,18 +48,8 @@ coverage.xml # Node.js node_modules/ -openclaw-bridge/dist/ .tsbuildinfo -# Dev-only files — not for release -.claude/ -reference/ +# Dev-only files — not for release (kept on feat/integration, excluded from main via .gitattributes) evals/ -tests/ -CLAUDE.md -CONTEXT.md -STATUS.md -progress.md -ARCHITECTURE.md -CONTRIBUTING.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..b629af9 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,379 @@ +# MemStack Architecture + +Internal architecture: module connections, data flows, and system design. This document is for contributors who need to understand how MemStack works internally. For user-facing documentation, see README.md. + +## Module Map + +``` +src/memstack/ + __init__.py -- version string + __main__.py -- delegates to cli.cli:app + cli/ + __init__.py + cli.py -- Typer CLI: start, stop commands + core/ + __init__.py + config.py -- Settings (pydantic-settings) + models.py -- Pydantic data models + slug.py -- memory ID slug generation + vault.py -- VaultStore (file I/O, caching, shared copies) + intelligence/ + __init__.py -- public exports + pipeline.py -- SmartWritePipeline + synthesis.py -- synthesize() -- LLM extraction of memory statements + consolidation.py -- Consolidator -- background LLM-driven consolidation + decay.py -- compute_decay, apply_hit_increment, compute_updated_importance + llm.py -- consult_llm() -- LLM decision for add/merge/update/ignore + interfaces/ + __init__.py + rest/ + __init__.py + app.py -- create_app() -- FastAPI factory + lifespan + health.py -- GET /health + memories.py -- POST/GET/GET-by-id/DELETE /agents/{agent_id}/memories + search.py -- GET /agents/{agent_id}/memories/search + inject.py -- GET /agents/{agent_id}/inject, GET /shared/inject + system_prompt.py -- GET /agents/{agent_id}/system-prompt + mcp/ + __init__.py -- exports create_mcp_server + server.py -- FastMCP server with 7 tools + standalone.py -- Starlette + uvicorn wrapper for standalone MCP + watcher.py -- VaultWatcher (watchfiles) + scan_vault() + search/ + __init__.py + embeddings.py -- EmbeddingProvider protocol, OllamaProvider, FastembedProvider + index.py -- SearchIndex (LanceDB) + chunker.py -- chunk_text() -- semantic text chunking + reranker.py -- ImportanceReranker (RRF + importance fusion) +``` + +## Core (`memstack.core`) + +### config.py — Settings + +Single `pydantic_settings.BaseSettings` subclass. All configuration from environment variables with `MEMSTACK_` prefix, or `.env` file. `get_settings()` is a cached singleton via `functools.lru_cache`. Uses `utf-8-sig` encoding for `.env` files to transparently handle UTF-8 BOM. + +Two computed properties: `effective_synthesis_model` and `effective_consolidation_model` both fall back to `llm_model` if their specific model is not set. + +### models.py — Data Models + +| Model | Purpose | Key Fields | +| ---------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `MemoryWrite` | Incoming write request | `content`, `agent_id`, `tags`, `importance` | +| `MemoryRead` | Outgoing read response | `id`, `agent`, `type`, `importance`, `tags`, `created`, `updated`, `importance_updated`, `body` | +| `Memory` | Full internal model with YAML frontmatter serialization | all fields from MemoryRead plus `to_frontmatter()` and `from_frontmatter()` class methods | +| `SearchResult` | Single search hit | `id`, `score`, `importance`, `chunk`, `agent`, `created` | +| `MemoryWriteResponse` | Smart write response | `decision` ("added" | "merged" | "updated" | "ignored"), `id`, `similarity_score` | +| `InjectionMemory` | Single injected memory | `id`, `body`, `importance`, `score`, `agent`, `tags`, `created` | +| `InjectionResponse` | Inject endpoint response | `memories`, `count`, `query` | +| `SystemPromptResponse` | System prompt response | `agent_id`, `version`, `system_prompt_block` | + +`build_memory(write: MemoryWrite, default_importance: float) -> Memory` constructs a `Memory` from a `MemoryWrite`, generating the ID (via `generate_slug`) and timestamps. + +### slug.py — generate_slug + +`generate_slug(text: str, agent_id: str) -> str` — format: `{slug}-{agent_id}-{YYYY-MM-DD}`. The slug is derived from the first non-empty line of `text`: lowercased, whitespace to hyphens, non-alphanumeric stripped, truncated to 80 chars. Falls back to `"memory"` if no meaningful content is found. + +### vault.py — VaultStore + +Primary persistence layer. Memories are stored as individual `.md` files: + +``` +{vault_path}/memstack/{agent_id}/{memory_id}.md +{vault_path}/memstack/shared/{shared_slug}.md (when shared_mode enabled) +``` + +Key methods: `write()`, `read()`, `update()`, `merge()`, `delete()`, `update_importance()`, `list_memories()`. + +Thread safety: `_atomic_write()` uses per-path `threading.Lock` to write via temp file + `os.replace()`. Two `OrderedDict` LRU caches (`_read_cache`, `_list_cache`) protected by `_cache_lock`. All mutations call `_invalidate_caches()`. + +Self-write suppression: when a watcher is registered, `register_write(path)` is called before writing, and `unregister_write(path)` fires after `2 * watcher_debounce_ms` via a daemon timer. + +Shared mode: every private write/update/merge/delete also creates/maintains a corresponding copy in `memstack/shared/` with a slug generated as `generate_slug(body, "shared")`. Shared operations are best-effort (log warnings on failure, never raise). + +## Intelligence (`memstack.intelligence`) + +### pipeline.py — SmartWritePipeline + +Orchestrates the deduplication logic for memory writes. Injected with `VaultStore`, `SearchIndex`, and `Settings`. + +`process(write: MemoryWrite) -> SmartWriteResult` — the core flow: + +1. Find similar via `search_index.find_similar()` +2. Threshold gate: below add threshold → add, above ignore threshold + enabled → ignore +3. LLM consultation via `consult_llm()` +4. Execute decision: add/merge/update/ignore + +### llm.py — consult_llm + +`consult_llm(incoming_content, similar_memories, model, host) -> LLMConsultResult` — sends a structured prompt to Ollama asking for a decision: `"add"`, `"merge"`, `"update"`, or `"ignore"`. Falls back to `"add"` on any failure. Uses a module-level singleton client (`_get_ollama_client()`). + +### synthesis.py — synthesize + +`synthesize(content: str, model: str, host: str) -> list[str]` — extracts discrete memory statements from a raw exchange via LLM. The LLM prompt asks for a JSON array of self-contained factual statements. Falls back to `[content]` on any failure. + +### consolidation.py — Consolidator + +Background task that periodically reviews stale memories via LLM. + +`run()` — async main loop: sleeps for `consolidation_interval` seconds, then calls `_consolidate_all_agents()`. Uses `asyncio.Event` for stop signaling. + +`_consolidate_agent(agent_id)` — selects the `consolidation_batch_size` oldest memories (sorted by `updated`), sends them to `_consult_llm()`, then applies each returned operation via `run_in_executor`. + +Operations: + +- **rewrite**: preserves ID, replaces body, updates timestamp +- **enrich**: preserves ID, adds context to body, updates timestamp +- **merge**: creates a new memory with averaged importance and union of tags, deletes originals +- **split**: creates new memories for each body, deletes original only if at least one write succeeded + +### decay.py — Importance Scoring + +- `compute_decay(score, days_since_access, halflife)` — exponential half-life decay: `score * 0.5 ** (days / halflife)` +- `apply_hit_increment(score, increment)` — adds increment, clamped to [0.0, 1.0] +- `compute_updated_importance(score, importance_updated, halflife, hit_increment, now)` — combines decay + hit increment + +## Search (`memstack.search`) + +### embeddings.py — Embedding Providers + +`EmbeddingProvider` protocol with `embed()` and `check_available()`. Two implementations: + +- `OllamaProvider` — uses `ollama.Client.embed()`. Lazy client init. LRU cache keyed by `tuple(texts)`. +- `FastembedProvider` — uses `fastembed.TextEmbedding` (model: `BAAI/bge-small-en-v1.5`). Lazy model load. + +`create_embedding_provider(settings)` — factory that creates the configured provider; raises RuntimeError immediately if the provider is unavailable. + +### chunker.py — chunk_text + +`chunk_text(body, max_tokens=512, overlap_tokens=50) -> list[str]` — three-stage splitting: paragraphs (double-newline) → sentences (. ! ?) → hard token split. Uses `tiktoken` with `cl100k_base` encoding. Chunks assembled with overlap from the tail of the previous chunk. + +### index.py — SearchIndex + +LanceDB-backed index storing chunked memories with vectors. + +Schema (PyArrow): `memory_id`, `agent_id`, `chunk_text`, `chunk_index`, `vector`, `importance`, `created`, `updated`, `importance_updated`, `tags`. + +Key methods: `add()`, `delete()`, `search()`, `find_similar()`, `update_importance()`, `reindex()`, `is_available()`. + +`search()` performs hybrid search (vector + FTS via LanceDB), filters by agent_id, reranks via `ImportanceReranker`. Results cached with TTL. `find_similar()` is vector-only for deduplication. + +### reranker.py — ImportanceReranker + +`rerank(results) -> list[dict]` — RRF with importance re-weighting: + +``` +combined = (1 - importance_weight) * (1 / (rrf_k + rank + 1)) + importance_weight * importance +``` + +Default: `rrf_k=10`, `importance_weight=0.3`. Sorts descending by combined score. + +## Interfaces (`memstack.interfaces`) + +### rest/app.py — create_app() + +FastAPI application factory. Startup sequence: + +1. Load `Settings` (from env / `.env`) +2. Configure loguru (stderr + file) +3. Create `VaultStore` (ensures `memstack_dir` exists, plus `shared_dir` if shared mode) +4. Create `EmbeddingProvider` (via `create_embedding_provider`) +5. Create `SearchIndex` (with embedding provider) +6. Create `SmartWritePipeline` (wired to vault + search_index + settings) +7. Run `scan_vault()` — incremental or full reindex based on `last_indexed` timestamp +8. If `watcher_enabled`: create `VaultWatcher`, attach to vault via `set_watcher()` +9. If `consolidation_enabled`: create `Consolidator` +10. Register routers: health, inject, search, memories, system_prompt +11. Lifespan: `_app_lifespan` async context manager starts watcher (`asyncio.create_task`) and consolidator (`asyncio.create_task`) on entry, stops both on exit + +State on `app.state`: `settings`, `vault`, `embedding_provider`, `search_index`, `smart_write_pipeline`, `watcher`, `consolidator`. + +### REST Endpoints + +| Route | Method | Handler | +| ----------------------------------------- | ------ | ------------------------ | +| `/health` | GET | `health_check` | +| `/agents/{agent_id}/memories` | POST | `create_memory` | +| `/agents/{agent_id}/memories` | GET | `list_memories` | +| `/agents/{agent_id}/memories/{memory_id}` | GET | `read_memory` | +| `/agents/{agent_id}/memories/{memory_id}` | DELETE | `delete_memory` | +| `/agents/{agent_id}/memories/search` | GET | `search_memories` | +| `/agents/{agent_id}/inject` | GET | `inject_memories` | +| `/shared/inject` | GET | `inject_shared_memories` | +| `/agents/{agent_id}/system-prompt` | GET | `get_system_prompt` | + +### mcp/server.py — create_mcp_server + +`FastMCP` server with 7 tools: `memory_write`, `memory_search`, `memory_read`, `memory_delete`, `memory_list`, `memory_inject`, `memory_get_system_prompt`. All tools access shared objects via `ctx.lifespan_context`. + +### mcp/standalone.py + +Wraps `create_mcp_server()` in a Starlette app with a `/health` route. Runnable standalone or embedded in the CLI. + +### watcher.py — VaultWatcher + scan_vault + +`VaultWatcher` — uses `watchfiles.awatch()` on the `memstack/` directory. Self-writes suppressed via `register_write`/`unregister_write`. Delete events remove from index. Add/modify events parse the file, chunk it, and add to index. + +`scan_vault()` — called at startup. Reads `last_indexed` from state file. If no timestamp exists, does a full `reindex()`. Otherwise, scans for `.md` files with mtime > `last_indexed_dt` and incrementally re-indexes them. + +## Data Flows + +### Write Flow + +``` +POST /agents/{agent_id}/memories + │ + ▼ +create_memory() + │ + ├─ if synthesis_enabled and "auto-capture" in tags: + │ synthesize(content) → list of statements + │ for each statement: pipeline.process(stmt_write) + ├─ else: + │ pipeline.process(write) + │ + ▼ +SmartWritePipeline.process(write) + │ + ├─ search_index.find_similar(write.content, agent_id) [vector search] + ├─ threshold gate → add/ignore + ├─ consult_llm(incoming, similar) → decision + │ ├─ "add": _add_memory() + │ ├─ "merge": _merge_memory() + │ ├─ "update": _update_memory() + │ └─ "ignore": return "ignored" + │ + ▼ (for "add") +vault.write(write) → Memory + ├─ build_memory() → generate slug, set timestamps + ├─ _atomic_write() → temp file + os.replace() + ├─ if shared_mode: _write_shared_copy() + │ + ▼ +chunk_text(memory.body) → list of chunks + │ + ▼ +search_index.add(memory, chunks) + ├─ embed chunks via EmbeddingProvider + ├─ add rows to LanceDB table + ├─ if shared_mode: _index_shared_copy() + │ + ▼ +MemoryWriteResponse to client +``` + +### Search Flow + +``` +GET /agents/{agent_id}/memories/search?q=...&limit=... + │ + ▼ +search_memories() + │ + ▼ +search_index.search(query, agent_id, limit) + ├─ check result cache (TTL-based) + ├─ embed query via EmbeddingProvider + ├─ ensure FTS index exists on chunk_text + ├─ LanceDB hybrid search (vector + full-text), filter by agent_id + │ + ▼ +ImportanceReranker.rerank(results) + ├─ RRF score = 1 / (rrf_k + rank + 1) + ├─ combined = (1 - weight) * rrf_score + weight * importance + ├─ sort descending + ├─ cache results + │ + ▼ +Deferred importance update (background task): + ├─ compute_updated_importance() [decay + hit increment] + ├─ search_index.update_importance() + ├─ vault.update_importance() + │ + ▼ +list[SearchResult] to client +``` + +### Inject Flow + +``` +GET /agents/{agent_id}/inject?q=...&limit=... + │ + ▼ +inject_memories() + ├─ if shared_mode: agent_ids = [agent_id, "shared"] + ├─ else: agent_ids = [agent_id] + │ + ▼ +_search_and_inject(search_index, settings, query, agent_ids, limit) + ├─ for each agent_id: search_index.search() + ├─ merge all results + ├─ sort by (score + importance) descending + ├─ filter: score >= injection_min_score + ├─ take top injection_top_n + │ + ▼ +Deferred importance update (same as search flow) + │ + ▼ +InjectionResponse to client +``` + +### Consolidation Flow + +``` +Consolidator.run() [async loop, every consolidation_interval seconds] + │ + ▼ +_consolidate_all_agents() + ├─ iterate agent directories in memstack/ + │ + ▼ +_consolidate_agent(agent_id) + ├─ vault.list_memories(agent_id) + ├─ sort by 'updated', take batch_size oldest + ├─ _consult_llm(batch) → list of operations + │ ├─ Ollama chat with structured JSON prompt + │ └─ operations: rewrite, enrich, merge, split + │ + ▼ +for each operation (via run_in_executor): + ├─ "rewrite": read memory, model_copy with new body/timestamp, _atomic_write + ├─ "enrich": same as rewrite (different LLM intent) + ├─ "merge": vault.write(new MemoryWrite with averaged importance), vault.delete originals + ├─ "split": vault.write per body, vault.delete original +``` + +## CLI (`memstack.cli`) + +`memstack start [--host] [--port] [--daemon/-d]`: + +- Checks state file for existing running process +- Daemon mode: launches REST server and optionally MCP server as subprocesses, writes PID to state file, polls until ports are bound +- Foreground mode: writes PID to state file, starts MCP in a daemon thread, runs uvicorn with `create_app` factory, removes state on exit + +`memstack stop`: reads PID from state file, kills REST + MCP processes, removes state file + +State file: `~/.memstack/state.json` (configurable). Contains: `pid`, `host`, `port`, `mcp_pid`, `mcp_port`, `last_indexed`. + +## Architectural Patterns + +1. **Local-first persistence** — all memories are markdown files with YAML frontmatter on disk. LanceDB index is a secondary structure that can be fully rebuilt from the vault. + +2. **Smart write pipeline** — every write goes through similarity search → threshold gating → optional LLM consultation → vault + index update. Prevents duplicate or near-duplicate memories. + +3. **Synthesis (opt-in)** — when enabled and tagged "auto-capture", a single write is decomposed by LLM into multiple discrete memory statements, each processed independently through the pipeline. + +4. **Importance decay + reinforcement** — every search/inject retrieval triggers a background importance update: score decays by half-life since last access, then gets a hit increment. Naturally surfaces frequently accessed memories. + +5. **Hybrid search** — LanceDB `hybrid` search combines vector similarity with full-text search, then `ImportanceReranker` fuses rank-based scores with importance weights. + +6. **Dual interface (REST + MCP)** — the same core services are exposed both as REST endpoints and MCP tools. MCP can run standalone or embedded in the FastAPI process. + +7. **Watcher for external edits** — `VaultWatcher` uses `watchfiles` to detect external changes to the vault (e.g., from Obsidian), automatically re-indexing changed files while suppressing self-writes. + +8. **Shared namespace (opt-in)** — when `shared_mode` is enabled, every private memory also gets a copy in `memstack/shared/`, enabling cross-agent memory sharing. Shared copies are best-effort and never block the primary operation. + +9. **Thread-safe I/O** — `_atomic_write` uses per-path locks and write-to-temp-then-rename. VaultStore caches protected by `threading.Lock`. Async endpoints offload sync work to `run_in_executor`. + +10. **Graceful degradation** — if the embedding provider is unavailable, the system still runs (vault reads/writes work) but search is disabled (503). The default provider is fastembed (in-process, no server needed). If the configured provider fails, a RuntimeError is raised immediately with a clear message. diff --git a/CHANGELOG.md b/CHANGELOG.md index dbcd494..3be6ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,65 @@ # Changelog +## 1.5.0 + +### Removed + +- OpenClaw TypeScript bridge plugin (`openclaw-bridge/`) — memory integration is now MCP-native only +- `.claude/specs/auto-capture.md` — spec for removed bridge feature + +### Changed + +- `docs/connect-openclaw.md` — rewritten for MCP-native connection (no bridge plugin, no hooks, no auto-capture/auto-recall) +- `README.md` — removed Node.js prerequisite, updated integrations section, updated synthesis description +- `CONTRIBUTING.md` — removed bridge development setup, TypeScript test section, and bridge scope +- `.gitignore` — removed `openclaw-bridge/dist/` entry + +### Note + +The MemStack server is unchanged. Synthesis triggered by the "auto-capture" tag in `memory_write` still works — agents can pass `tags: ["auto-capture"]` explicitly. The agent now calls memory tools directly via MCP rather than through bridge hooks. + +## 1.4.5 + +### Added + +- Pagination for `GET /agents/{agent_id}/memories` and MCP `memory_list` tool — `limit` (default 50) and `offset` (default 0) query parameters; `limit=0` returns all memories +- `MemoryListResponse` model with `memories`, `total`, `limit`, `offset` fields +- `classify_content()` in `intelligence/classify.py` — categorises incoming memories into one of 10 categories (food, fitness, work, entertainment, tools, relationships, health, travel, education, other) using LLM, with fallback to "other" +- Category tag (`cat:food`, `cat:fitness`, etc.) automatically appended to every memory written through the smart pipeline +- `SearchIndex.find_by_tag()` — finds memories by tag using LanceDB LIKE filter, deduplicated by `memory_id` +- Category-matched memories injected into LLM consultation alongside vector-similarity results, ensuring same-category memories are always considered as merge candidates +- `incoming_category` parameter added to `consult_llm()` — includes the classified category in the LLM prompt +- `validate_dimension()` method on `SearchIndex` — detects vector dimension mismatches between the existing LanceDB table and the current embedding provider, and triggers a full reindex from vault when a mismatch is found +- Dimension validation runs at startup in both REST and MCP server paths + +### Changed + +- `VaultStore.list_memories()` returns `MemoryListResponse` instead of `list[MemoryRead]`; callers must access `.memories` for the list and `.total` for count +- `Consolidator._consolidate_agent()` now passes `limit=0` to `list_memories()` to retrieve all memories +- LLM consultation prompt includes `incoming_category` and `tags` from similar memories when available +- LLM consultation prompt now guides categorical grouping for "merge" decisions (e.g., both are food preferences, both are about fitness) and explicitly handles contradictions for "update" decisions (e.g., a preference changed, a correction) +- LLM consultation now requests `merged_content` when the decision is "merge" — the LLM rewrites both memories into a single coherent paragraph instead of raw concatenation +- `LLMConsultResult` dataclass now includes an optional `merged_content` field +- `VaultStore.merge()` accepts an optional `merged_body` parameter; when provided, the rewritten content is used instead of raw append with `\n\n` +- Synthesis prompt updated to preserve specifics that pin down the user's preference or fact (exact values, names, identifiers) while discarding general knowledge not tied to the preference +- Removed `embedding_autofallback: bool = True` from `Settings` — autofallback logic deleted; if the configured provider is unavailable, a RuntimeError is raised immediately with a clear message +- Changed `embedding_provider` default from `"ollama"` to `"fastembed"` — fastembed is the new default (no server needed) +- Changed `embedding_model` default from `"nomic-embed-text"` to `"BAAI/bge-small-en-v1.5"` — matches the new fastembed default +- Removed `MEMSTACK_EMBEDDING_AUTOFALLBACK` from `.env.example` +- `create_embedding_provider()` no longer accepts or uses an `autofallback` parameter — both provider branches raise RuntimeError when unavailable +- Removed unused `obsidian_mode` config field from `Settings`, `.env.example`, and all test fixtures — the field was declared but never read by any code path +- Watcher and consolidator startup/shutdown moved from deprecated `@app.on_event` to `_app_lifespan` async context manager +- Replaced 6 hardcoded version string assertions with `__version__` import (tests will no longer break on version bumps) +- Renamed `test_health_version_is_1_4_2` to `test_health_version_matches_package` and `test_mcp_server_version_is_1_4_2` to `test_mcp_server_version_matches_package` + +### Fixed + +- Watcher `_handle_add_or_update()` now always deletes existing index entries before re-indexing a file — previously only deleted on filename change, causing stale chunks to coexist with updated chunks after external vault edits +- `scan_vault()` now removes orphaned index entries whose vault files no longer exist — fixes stale recall after files are deleted while the server is offline +- `SearchIndex.list_memory_ids()` added to return all distinct `memory_id` values for a given agent, used by orphan cleanup +- `.env` files with a UTF-8 BOM (`\xef\xbb\xbf`) no longer cause `MEMSTACK_VAULT_PATH` validation errors — `Settings` now uses `utf-8-sig` codec which strips the BOM transparently +- OpenClaw bridge `agent_end` handler now logs auto-capture POST failures (network errors and non-2xx responses) instead of silently swallowing them + ## 1.4.4 ### Added diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bcda528 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,133 @@ +# **Global Claude Code Rules** + +These rules are non-negotiable and apply to every session, every agent, and every task without exception. Project-level CLAUDE.md files add on top of these — they do not override them. + +## **CLAUDE.md is Frozen — Absolute Rule** + +CLAUDE.md is permanently read-only. It must never be edited, modified, appended to, or overwritten — by any agent, in any session, for any reason. + +* Do not edit CLAUDE.md under any circumstance +* Do not treat CLAUDE.md as a continuity document or session log +* Do not add, remove, or update any line in CLAUDE.md +* If a task appears to require editing CLAUDE.md, stop and flag it to the user — do not proceed + +## **Communication** + +* Be blunt and direct. No filler, no excessive explanation. +* Give the answer first. Context and caveats come after if needed. +* Never pad responses with boilerplate or unnecessary commentary. +* If something is wrong or a bad idea, say so plainly. +* If a task is ambiguous, ask one clarifying question — do not assume and proceed. + +## **Feature Freeze — Absolute Rules** + +The current version of any project is feature-frozen unless explicitly told otherwise. + +**What is banned:** + +* Adding any new feature, option, flag, parameter, or behaviour not already present in the codebase +* Expanding the scope of an existing feature under the guise of fixing it +* Adding "nice to have" improvements, convenience wrappers, or quality-of-life changes +* Introducing new dependencies to support anything not already in the design +* Restructuring, renaming, or reorganising anything not directly broken + +**What is allowed:** + +* Fixing bugs — behaviour that is broken relative to the existing design +* Correcting documentation to match existing behaviour +* Removing dead code with zero behavioural impact + +**If you identify something that looks like a necessary feature (not a bug):** + +1. Stop. Do not implement it. +2. Write a clearly labelled block: \#\# FEATURE FLAG — NOT A BUG followed by: what it is, why it is necessary, and what breaks without it. +3. Present it to the user and wait for explicit approval. +4. A feature is necessary only if existing functionality is non-deliverable without it. + +**Bugs vs features — the line:** + +* **Bug:** the code does not do what the existing design says it should do +* **Feature:** the code does what the design says, but the design does not cover this case + +If you are unsure which category something falls into, it is a feature. Flag it, do not implement it. + +Never mix bugs and features in the same PR, commit, or session. + +## **Reference Folder** + +A /reference/ folder may exist at the project root. It contains important project documents — what exactly is in it varies per project. Treat everything inside as authoritative context. + +* Never guess at project-specific patterns or conventions — check reference/ first +* If a task clearly calls for a reference document that is missing, flag it instead of improvising +* Match the structure, tone, and conventions shown in reference files exactly + +## **Continuity Documents** + +Three files maintain context across sessions. All live at the project root. + +* CONTEXT.md — project rules and architecture context +* STATUS.md — live implementation status; updated as work progresses +* progress.md — dev journal; decisions made, approaches rejected, open questions + +**These files MUST be committed and pushed during feature development.** This ensures all branches can see the latest project state. They are only removed from the codebase via .gitignore during the final merge to main. + +## **Branch Rules** + +* Never commit to main +* Never push to main +* Never open a PR targeting main during feature work +* main is the canonical, production-ready source of truth — treat it as read-only +* When main and the integration branch disagree, main is authoritative +* All PRs from feature branches must target the project's integration branch +* Exception: if currently on the integration branch, PRs targeting main are allowed — with explicit user approval only +* Always work on the current feature branch +* Every commit must be pushed to the existing open PR — do not open a new PR per commit +* Do not push commits directly to the integration branch + +## **Pull Request Rules** + +* Only open one PR per feature branch +* PR title format: \[scope\] brief description +* PR description must include: what was built, what was changed, and any known limitations +* Never merge a PR without explicit user approval +* Do not auto-merge under any circumstance +* After completing work, open the PR and stop — wait for the user to review and approve + +## **Commit Rules** + +* Commits must be scoped to the current worktree only +* Commit messages must follow conventional commits: type(scope): description + * Types: feat, fix, chore, docs, test, refactor +* Never commit secrets, API keys, tokens, or hardcoded credentials — halt immediately if found +* Never commit directly to the integration branch or main + +## **Scope Rules** + +* Only write files inside the directory explicitly assigned to the current task +* Reading sibling directories for reference is allowed — modifying them is not +* If a task requires changes in another directory, stop and flag it to the user + +## **Security Rules** + +* Zero hardcoded secrets — all credentials via environment variables only +* If a secret is detected anywhere, halt execution immediately and report it +* Never log or print sensitive values +* All new endpoints must validate input before processing + +## **Completion Rules** + +* Zero TODOs, FIXMEs, HACKs, or XXX markers in any file you touched +* Zero dead code: unused imports, unreachable branches, stub functions +* Zero linter errors or warnings in changed files +* All affected tests pass — run them, do not assume +* README and CHANGELOG updated if any public API or behaviour changed +* No regression in any previously passing test +* Verifier must sign off before declaring done + +## **Tool Discipline** + +* Run pwd before any file operation in a new session +* Read a file fully before editing it — never assume content from memory +* Prefer targeted edits over full rewrites +* Use grep/glob to find all usages before renaming anything +* Never modify files not explicitly part of the current task diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..e0bc76d --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,45 @@ +# Context — feat/integration + +## Architecture + +MemStack is a local-first memory server for AI agents. Every memory is a markdown file in a vault the user owns. The vault is ground truth — the search index is derived and always rebuildable. + +## v1.0 Scope + +Working vault only. No search, no intelligence, no file watcher, no MCP. CLI limited to `memstack start` and `memstack stop`. + +## Branch Strategy + +| Branch | Scope | Status | +| ---------------- | ------------------------------------------------------------ | ----------- | +| feat/scaffold | Project skeleton | Merged | +| feat/core | Data layer: config, models, slug, vault + tests | Merged | +| feat/api | REST interface: app factory, health, memories routes + tests | Merged | +| feat/cli | CLI: start/stop commands + tests | Merging | +| feat/integration | Merge hub | This branch | + +## Dependencies + +- pydantic — data validation +- pydantic-settings — configuration from env vars +- python-frontmatter — YAML frontmatter parsing +- loguru — structured logging +- fastapi — REST API framework +- uvicorn — ASGI server +- typer — CLI framework +- pytest / pytest-cov / httpx — testing +- ruff — linting + +## Environment Variables (v1.0) + +| Variable | Required | Default | +| ------------------------------- | -------- | ------------------------- | +| MEMSTACK_VAULT_PATH | Yes | — | +| MEMSTACK_OBSIDIAN_MODE | No | true | +| MEMSTACK_SHARED_MODE | No | false | +| MEMSTACK_IMPORTANCE_INITIAL_SCORE | No | 0.5 | +| MEMSTACK_LOG_LEVEL | No | INFO | +| MEMSTACK_LOG_FILE | No | ~/.memstack/logs/memstack.log | +| MEMSTACK_LOG_ROTATION | No | 10 MB | +| MEMSTACK_LOG_RETENTION | No | 7 days | +| MEMSTACK_STATE_FILE | No | ~/.memstack/state.json | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b3a480d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing to MemStack + +## Development Setup + +### Prerequisites + +- Python 3.11 or later +- [uv](https://docs.astral.sh/uv/) (Python package manager) +- Ollama (for integration tests that require an LLM) + +### Install Dependencies + +This command works on all platforms: + +```bash +uv sync --extra dev +``` + +It creates a virtual environment and installs MemStack with all dependencies, including dev dependencies (pytest, pytest-cov, httpx, ruff, hypothesis). Do not create a virtual environment manually — `uv` manages its own `.venv`. + +## Running Tests + +### Python Tests + +```bash +uv run pytest --cov=memstack -v +``` + +593 tests pass with over 90% coverage. The coverage gate is enforced at 90% via `--cov-fail-under=90`. + +Run a specific test file: + +```bash +uv run pytest tests/test_pipeline.py -v +``` + +Run a specific test: + +```bash +uv run pytest tests/test_pipeline.py::test_add_memory -v +``` + +### Lint and Format + +```bash +uv run ruff check src/ tests/ evals/ +uv run ruff format src/ tests/ evals/ +``` + +Both must pass clean before committing. + +## Project Structure + +``` +src/memstack/ -- Python source + cli/ -- Typer CLI (start, stop) + core/ -- Settings, models, vault store, slug generation + intelligence/ -- Smart write pipeline, LLM consultation, synthesis, consolidation, decay + interfaces/rest/ -- FastAPI routes and app factory + interfaces/mcp/ -- MCP server (7 tools) and standalone wrapper + interfaces/watcher.py -- File watcher for vault sync + search/ -- Embeddings, LanceDB index, chunker, reranker +tests/ -- Python test suite +evals/ -- Retrieval evaluation framework +``` + +## Commit Conventions + +Commits must follow conventional commits: + +``` +type(scope): description +``` + +Types: `feat`, `fix`, `chore`, `docs`, `test`, `refactor`. + +Scopes match the module being changed: `core`, `intelligence`, `search`, `rest`, `mcp`, `cli`, `config`, etc. + +## Pull Request Rules + +- Only one PR per feature branch +- PR title format: `[scope] brief description` +- PR description must include: what was built, what was changed, and any known limitations +- Never merge a PR without explicit user approval +- All PRs from feature branches must target `feat/integration`, not `main` + +## Branch Rules + +- Never commit to `main` +- All feature branches must follow the naming pattern `feat/...` and must be cut from `feat/integration` +- When `main` and the integration branch disagree, `main` is authoritative + +## Completion Checklist + +Before marking work as done: + +- [ ] No provisional task-marker comments left in changed files +- [ ] Zero dead code: unused imports, unreachable branches, stub functions +- [ ] Zero linter errors or warnings in changed files +- [ ] All affected tests pass +- [ ] README and CHANGELOG updated if any public API or behaviour changed +- [ ] No regression in any previously passing test diff --git a/README.md b/README.md index d3f76e4..9e2dbab 100644 --- a/README.md +++ b/README.md @@ -4,35 +4,119 @@ Persistent semantic memory for AI agents — local files, no cloud. AI agents forget everything between sessions. MemStack gives them persistent memory through a REST API and MCP server — write once, search by meaning, store as plain Markdown. No cloud, no API keys, no database. - +--- + +## Glossary + +- **Vault** — A directory on your computer where MemStack stores memory files. You choose the location. Each memory is a Markdown file with YAML frontmatter. +- **Agent** — An AI assistant or program that uses MemStack. Each agent gets its own namespace inside the vault. +- **agent_id** — A string identifier for an agent (for example, `my-agent`). Used to scope memories so different agents don't see each other's data unless shared mode is enabled. +- **Memory** — A single piece of information stored in the vault. Has content, tags, importance, timestamps, and a unique ID. +- **Slug** — The unique ID for a memory, generated from the first line of content plus the agent ID and date (for example, `deployed-v2-to-production-my-agent-2026-05-14`). +- **Frontmatter** — YAML metadata at the top of each memory file, containing the ID, agent, importance, tags, and timestamps. +- **Daemon** — A background process. MemStack can run in the foreground or as a daemon. +- **Endpoint** — A URL path that accepts HTTP requests (for example, `GET /health`). + +--- + +## Requirements + +- Python 3.11 or later +- [uv](https://docs.astral.sh/uv/) package manager +- [Ollama](https://ollama.ai) (for smart write, synthesis, and consolidation features) --- -## Quick Start +## Install -Linux/macOS only. Windows users can check `docs/windows.md`. +This command works on macOS, Linux, and Windows: ```bash -# 1. Install uv sync --extra dev +``` -# 2. Configure (copy example env and set vault path) +This creates a virtual environment and installs MemStack with all dependencies. Do not run `uv pip install` separately — `uv sync` handles everything. + +> **Known warning:** `uv sync` may print a `VIRTUAL_ENV` mismatch warning. This is harmless — `uv run` uses its own managed environment regardless. + +--- + +## Configure + +Create a `.env` file in your project directory. The only required variable is `MEMSTACK_VAULT_PATH`: + +```bash cp .env.example .env -# Then edit .env and set MEMSTACK_VAULT_PATH to your vault directory +``` + +Then edit `.env` and set `MEMSTACK_VAULT_PATH` to the directory where you want MemStack to store memory files. + +Example vault paths: + +- macOS/Linux: `MEMSTACK_VAULT_PATH=/home/user/memstack-vault` +- Windows: `MEMSTACK_VAULT_PATH=C:\Users\user\memstack-vault` +- macOS/Linux: `MEMSTACK_VAULT_PATH=~/.memstack/vault` + +You must create the vault directory before starting the server: + +**macOS / Linux:** + +```bash +mkdir -p /home/user/memstack-vault +``` + +**Windows CMD:** + +```cmd +mkdir C:\Users\user\memstack-vault +``` + +**Windows PowerShell:** + +```powershell +New-Item -ItemType Directory -Path C:\Users\user\memstack-vault +``` + +If you plan to use Obsidian to browse your vault, you can point `MEMSTACK_VAULT_PATH` at an existing Obsidian vault — MemStack stores its files in a `memstack/` subdirectory inside the vault, so it won't interfere with existing notes. -# 3. Start +--- + +## Start + +**Foreground** (press Ctrl+C to stop — works on all platforms): + +```bash uv run memstack start +``` + +**Daemon mode** (runs in the background): + +```bash +uv run memstack start --daemon +``` + +Override host and port: + +```bash +uv run memstack start --host 0.0.0.0 --port 8080 +``` + +--- -# 4. Verify +## Verify + +Check that the server is running: + +```bash curl http://127.0.0.1:7777/health ``` -Expected output: +Expected response: ```json { "status": "healthy", - "version": "1.4.4", + "version": "1.4.5", "components": { "vault": "healthy", "lancedb": "healthy", @@ -44,85 +128,325 @@ Expected output: } ``` ---- +The `mcp_port` field shows the port number when MCP is enabled, or `"disabled"` when `MEMSTACK_MCP_ENABLED=false`. The `shared` field shows `"disabled"` when `MEMSTACK_SHARED_MODE=false`, `"healthy"` when shared mode is on and the shared directory exists, or `"unhealthy"` when shared mode is on but the directory is missing. -## How It Works +If the health endpoint returns 503, the server started but one or more components failed. Check `MEMSTACK_LOG_FILE` (default: `~/.memstack/logs/memstack.log`) for details. -Every write goes through a deduplication pipeline: similarity check, then LLM consultation for ambiguous cases. The pipeline decides add (new), merge (append facts), update (replace), or ignore (duplicate). Memories are stored as Markdown files with YAML frontmatter — human-readable, version-controllable. Search uses vector similarity + keyword matching, reranked by importance with time-based decay. Each agent gets its own namespace; shared mode copies private writes to a shared pool. +--- -**Write pipeline:** similarity thresholds filter obvious matches, then Ollama decides add/merge/update/ignore for ambiguous cases; falls back to "add" if LLM is unavailable. +## Stop -**Storage:** Markdown files with YAML frontmatter in a vault directory — human-readable, editable, version-controllable. +```bash +uv run memstack stop +``` -**Search:** vector + keyword hybrid via LanceDB, importance-weighted reranking with RRF fusion and time-based decay. +On Windows, this uses `taskkill /F /PID`. On macOS and Linux, it sends SIGTERM. + +If the state file is stale (the process is no longer running), `memstack stop` cleans it up and reports that no server was found. --- -## Features +## Memory Files -- **Persistent memory** — text memories that survive across sessions, stored as Markdown files -- **Smart deduplication** — add, merge, update, or ignore based on similarity + LLM consultation -- **Semantic search** — find memories by meaning, not just keywords, with importance-weighted reranking -- **Multi-agent namespaces** — isolated memory per agent, plus optional shared namespace -- **MCP server** — 7 memory tools for AI agents, runs as a separate process on its own port -- **Local-first and private** — no cloud, no API keys, no telemetry; data stays on your machine -- **Graceful degradation** — CRUD works without embeddings; search returns 503 until provider available -- **Background consolidation** — LLM-driven review of stale memories (rewrite, enrich, merge, split) +Every memory is a Markdown file with YAML frontmatter stored in the vault: ---- +``` +{vault_path}/memstack/{agent_id}/{memory_id}.md +``` -## Memory Files +When shared mode is enabled, a copy also lives at: + +``` +{vault_path}/memstack/shared/{shared_slug}.md +``` -Every memory is a Markdown file with YAML frontmatter. You can edit them in any text editor, put the vault under version control, or browse them with tools like Obsidian. +Example file: ```markdown --- agent: my-agent -created: "2026-05-06T04:39:16.923211+00:00" -id: deployed-v2-to-production-my-agent-2026-05-07 +created: "2026-05-14T16:15:25.534134+00:00" +id: deployed-v2-to-production-my-agent-2026-05-14 importance: 0.9 -importance_updated: "2026-05-06T04:39:16.923211+00:00" +importance_updated: "2026-05-14T16:15:25.534134+00:00" tags: - deploy - production type: memory -updated: "2026-05-06T04:39:16.923211+00:00" +updated: "2026-05-14T16:15:25.534134+00:00" --- Deployed v2 to production on Saturday ``` +You can edit these files directly in any text editor or browse them with Obsidian. The file watcher automatically re-indexes changes made outside MemStack. + --- ## REST API -| Method | Endpoint | Description | -| -------- | --------------------------------- | ----------------------- | -| `POST` | `/agents/{id}/memories` | Write a memory | -| `GET` | `/agents/{id}/memories` | List memories | -| `GET` | `/agents/{id}/memories/{mem_id}` | Read a memory | -| `DELETE` | `/agents/{id}/memories/{mem_id}` | Delete a memory | -| `GET` | `/agents/{id}/memories/search?q=` | Search by meaning | -| `GET` | `/agents/{id}/inject?q=` | Inject context | -| `GET` | `/agents/{id}/system-prompt` | Get agent system prompt | -| `GET` | `/health` | Server health | +### Write a Memory ```bash -# Write a memory curl -X POST http://127.0.0.1:7777/agents/my-agent/memories \ -H "Content-Type: application/json" \ -d '{"content":"Deployed v2 to production on Saturday","tags":["deploy","production"],"importance":0.9}' ``` +**Windows CMD:** + +```cmd +curl -X POST http://127.0.0.1:7777/agents/my-agent/memories -H "Content-Type: application/json" -d "{\"content\":\"Deployed v2 to production on Saturday\",\"tags\":[\"deploy\",\"production\"],\"importance\":0.9}" +``` + +Returns status 201 when the memory is added, or 200 when it is merged, updated, or ignored: + ```json { "decision": "added", - "id": "deployed-v2-to-production-my-agent-2026-05-07", + "id": "deployed-v2-to-production-my-agent-2026-05-14", "similarity_score": null } ``` -> Full API reference: [docs/api.md](docs/api.md) +The `decision` field is one of: + +- `"added"` — new memory created (status 201) +- `"merged"` — content merged into an existing similar memory (status 200). When the LLM provides rewritten content, it replaces the raw append; otherwise content is appended +- `"updated"` — existing memory replaced with new content (status 200) +- `"ignored"` — similar memory already exists (status 200) + +The `similarity_score` field shows how similar the new content was to the closest existing memory, or `null` if no similar memory was found. The `id` field will differ from the example — it is generated from the content, agent ID, and current date. + +**Request body fields:** + +| Field | Type | Required | Description | +| ------------ | -------------- | -------- | --------------------------------------- | +| `content` | string | Yes | The memory content (minimum 1 char) | +| `agent_id` | string | No | Agent namespace (injected from URL) | +| `tags` | list of string | No | Tags for categorization | +| `importance` | float | No | Importance score, 0.0–1.0 (default 0.5) | + +### List Memories + +```bash +curl http://127.0.0.1:7777/agents/my-agent/memories +``` + +Returns the same format as Read Memory, but as an array. + +### Read a Memory + +```bash +curl http://127.0.0.1:7777/agents/my-agent/memories/deployed-v2-to-production-my-agent-2026-05-14 +``` + +```json +{ + "id": "deployed-v2-to-production-my-agent-2026-05-14", + "agent": "my-agent", + "type": "memory", + "importance": 0.9, + "tags": ["deploy", "production"], + "created": "2026-05-14T16:15:25.534134+00:00", + "updated": "2026-05-14T16:15:25.534134+00:00", + "importance_updated": "2026-05-14T16:15:25.534134+00:00", + "body": "Deployed v2 to production on Saturday" +} +``` + +### Delete a Memory + +```bash +curl -X DELETE http://127.0.0.1:7777/agents/my-agent/memories/deployed-v2-to-production-my-agent-2026-05-14 +``` + +Returns status 204 with no body on success. + +### Search Memories + +```bash +curl "http://127.0.0.1:7777/agents/my-agent/memories/search?q=deployment&limit=5" +``` + +**Windows CMD:** + +```cmd +curl "http://127.0.0.1:7777/agents/my-agent/memories/search?q=deployment&limit=5" +``` + +Returns an array of search results: + +```json +[ + { + "id": "deployed-v2-to-production-my-agent-2026-05-14", + "score": 0.3336, + "importance": 0.9, + "chunk": "Deployed v2 to production on Saturday", + "agent": "my-agent", + "created": "2026-05-14T16:15:25.534134+00:00" + } +] +``` + +Query parameters: + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ---------------------------------- | +| `q` | string | Yes | Search query (minimum 1 character) | +| `limit` | int | No | Max results, 1–100 (default 10) | + +### Inject Context + +```bash +curl "http://127.0.0.1:7777/agents/my-agent/inject?q=deployment&limit=5" +``` + +Returns relevant memories filtered by minimum score and top-N limit: + +```json +{ + "memories": [ + { + "id": "deployed-v2-to-production-my-agent-2026-05-14", + "body": "Deployed v2 to production on Saturday", + "importance": 0.95, + "score": 0.3486, + "agent": "my-agent", + "tags": ["deploy", "production"], + "created": "2026-05-14T16:15:25.534134+00:00" + } + ], + "count": 1, + "query": "deployment" +} +``` + +When shared mode is enabled, the agent-scoped endpoint searches both the agent's namespace and the shared namespace. The `shared/inject` endpoint searches only the shared namespace: + +```bash +curl "http://127.0.0.1:7777/shared/inject?q=deployment&limit=5" +``` + +Query parameters: + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ---------------------------------- | +| `q` | string | Yes | Search query (minimum 1 character) | +| `limit` | int | No | Max results, 1–100 (default 5) | + +### System Prompt + +```bash +curl http://127.0.0.1:7777/agents/my-agent/system-prompt +``` + +Returns a behavioral mandate and tool descriptions for the agent: + +```json +{ + "agent_id": "my-agent", + "version": "1.4.5", + "system_prompt_block": "## 0. MEMORY MANDATE\n\nMemStack is the memory system for this session..." +} +``` + +The `system_prompt_block` field contains the full mandate text — 8 sections covering behavioral rules and MCP tool descriptions. The value will be much longer than shown here; it includes a MEMORY MANDATE, usage guidelines, and parameter descriptions for all 7 tools. + +### Health Check + +```bash +curl http://127.0.0.1:7777/health +``` + +Returns the same format shown in the Verify section above. Returns status 200 when healthy, 503 when any enabled component is unhealthy. + +--- + +## End-to-End Walkthrough + +This walkthrough creates a memory, reads it back, searches for it, injects it into context, and then deletes it. Run each step in order — some steps reference the output of previous steps. + +### Step 1: Start the server + +```bash +uv run memstack start --daemon +``` + +Verify: + +```bash +curl http://127.0.0.1:7777/health +``` + +Expected: JSON response with `"status": "healthy"`. + +### Step 2: Create a vault directory and set the path + +If you haven't already set `MEMSTACK_VAULT_PATH` in your `.env` file, set it before starting the server. The walkthrough assumes `my-agent` as the agent ID. + +### Step 3: Write a memory + +```bash +curl -X POST http://127.0.0.1:7777/agents/my-agent/memories \ + -H "Content-Type: application/json" \ + -d '{"content":"Deployed v2 to production on Saturday","tags":["deploy","production"],"importance":0.9}' +``` + +Note the `id` field in the response — you will use it in subsequent steps. The ID will differ from the examples below because it includes the current date. + +### Step 4: Read the memory + +Replace `{memory_id}` with the `id` from step 3: + +```bash +curl http://127.0.0.1:7777/agents/my-agent/memories/{memory_id} +``` + +### Step 5: List all memories + +```bash +curl http://127.0.0.1:7777/agents/my-agent/memories +``` + +### Step 6: Search for the memory + +```bash +curl "http://127.0.0.1:7777/agents/my-agent/memories/search?q=deployment&limit=5" +``` + +### Step 7: Inject the memory into context + +```bash +curl "http://127.0.0.1:7777/agents/my-agent/inject?q=deployment&limit=5" +``` + +### Step 8: Get the system prompt + +```bash +curl http://127.0.0.1:7777/agents/my-agent/system-prompt +``` + +### Step 9: Delete the memory + +Replace `{memory_id}` with the `id` from step 3: + +```bash +curl -X DELETE http://127.0.0.1:7777/agents/my-agent/memories/{memory_id} +``` + +Verify deletion (should return 404): + +```bash +curl http://127.0.0.1:7777/agents/my-agent/memories/{memory_id} +``` + +### Step 10: Stop the server + +```bash +uv run memstack stop +``` --- @@ -140,68 +464,113 @@ The MCP server exposes 7 memory operations as tools, running as a separate proce | `memory_inject` | Inject context | | `memory_get_system_prompt` | Get system prompt block | +All tools accept the same parameters as their REST equivalents. The MCP server also has a health endpoint at `http://127.0.0.1:7778/health`. + +For connecting the MCP server to an OpenClaw agent, see [Connecting OpenClaw to MemStack](docs/connect-openclaw.md). + --- ## Configuration +All configuration is through environment variables with the `MEMSTACK_` prefix, or via a `.env` file in the working directory. + **Required:** | Variable | Default | Description | | --------------------- | ------- | -------------------------------------- | -| `MEMSTACK_VAULT_PATH` | — | **(Required)** Path to vault directory | - -**Optional:** - -| Variable | Default | Description | -| -------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------- | -| `MEMSTACK_HOST` | `127.0.0.1` | Server bind address | -| `MEMSTACK_PORT` | `7777` | Server bind port | -| `MEMSTACK_IMPORTANCE_INITIAL_SCORE` | `0.5` | Default importance for new memories | -| `MEMSTACK_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | -| `MEMSTACK_LOG_FILE` | `~/.memstack/logs/memstack.log` | Log file path | -| `MEMSTACK_LOG_ROTATION` | `10 MB` | Log rotation size threshold | -| `MEMSTACK_LOG_RETENTION` | `7 days` | Log retention period | -| `MEMSTACK_STATE_FILE` | `~/.memstack/state.json` | PID state file path | -| `MEMSTACK_EMBEDDING_PROVIDER` | `ollama` | Embedding provider: `ollama` or `fastembed` | -| `MEMSTACK_EMBEDDING_MODEL` | `nomic-embed-text` | Embedding model name (provider-specific) | -| `MEMSTACK_EMBEDDING_AUTOFALLBACK` | `true` | Auto-fallback to fastembed if Ollama unavailable | -| `MEMSTACK_CHUNK_MAX_TOKENS` | `512` | Max tokens per chunk for semantic chunking | -| `MEMSTACK_CHUNK_OVERLAP_TOKENS` | `50` | Overlap tokens between adjacent chunks | -| `MEMSTACK_RRF_K` | `10` | RRF constant for hybrid search fusion | -| `MEMSTACK_IMPORTANCE_RERANK_WEIGHT` | `0.3` | Weight for importance score in reranking (0.0–1.0) | -| `MEMSTACK_INDEX_PATH` | `~/.memstack/index` | LanceDB index directory | -| `MEMSTACK_SIMILARITY_ADD_THRESHOLD` | `0.25` | Below this, always add as new memory | -| `MEMSTACK_SIMILARITY_IGNORE_THRESHOLD` | `0.85` | At or above this, treat as duplicate (if ignore enabled) | -| `MEMSTACK_SIMILARITY_IGNORE_ENABLED` | `false` | Auto-ignore duplicates above threshold (default: off — ambiguous cases go to LLM) | -| `MEMSTACK_IMPORTANCE_DECAY_HALFLIFE` | `7.0` | Half-life in days for importance decay | -| `MEMSTACK_IMPORTANCE_HIT_INCREMENT` | `0.05` | Importance bump on each retrieval | -| `MEMSTACK_LLM_MODEL` | `llama3` | Ollama model for smart write consultation | -| `MEMSTACK_LLM_HOST` | `http://localhost:11434` | Ollama host URL | -| `MEMSTACK_MCP_ENABLED` | `true` | Enable MCP server (separate process) | -| `MEMSTACK_MCP_PORT` | `7778` | Port for the standalone MCP server | -| `MEMSTACK_WATCHER_ENABLED` | `true` | Enable file watcher for automatic vault sync | -| `MEMSTACK_WATCHER_DEBOUNCE_MS` | `2000` | Debounce time in ms for file watcher events | -| `MEMSTACK_SHARED_MODE` | `false` | Enable shared mode — private writes also copied to shared namespace | -| `MEMSTACK_INJECTION_MIN_SCORE` | `0.3` | Minimum score for inject endpoint results | -| `MEMSTACK_INJECTION_TOP_N` | `5` | Max results returned by inject endpoint | -| `MEMSTACK_EMBEDDING_CACHE_SIZE` | `1024` | LRU cache size for embedding vectors | -| `MEMSTACK_FTS_REBUILD_INTERVAL` | `50` | Adds before FTS index rebuild | -| `MEMSTACK_SEARCH_CACHE_TTL` | `30` | TTL in seconds for search result cache | -| `MEMSTACK_VAULT_CACHE_SIZE` | `512` | LRU cache size for vault read/list operations | -| `MEMSTACK_SYNTHESIS_ENABLED` | `false` | Enable LLM synthesis for auto-capture writes | -| `MEMSTACK_SYNTHESIS_MODEL` | _(empty)_ | Ollama model for synthesis (falls back to `MEMSTACK_LLM_MODEL`) | -| `MEMSTACK_CONSOLIDATION_ENABLED` | `false` | Enable background memory consolidation | -| `MEMSTACK_CONSOLIDATION_INTERVAL` | `3600` | Seconds between consolidation runs (min: 60) | -| `MEMSTACK_CONSOLIDATION_BATCH_SIZE` | `20` | Max memories per agent per run (1–100) | -| `MEMSTACK_CONSOLIDATION_MODEL` | _(empty)_ | Ollama model for consolidation (falls back to `MEMSTACK_LLM_MODEL`) | - -> All variables can be set in a `.env` file. See [`.env.example`](.env.example) for the full reference. +| `MEMSTACK_VAULT_PATH` | — | Path to the vault directory (required) | + +**Server:** + +| Variable | Default | Description | +| ------------------------------ | ------------------------------- | ------------------------------------------------ | +| `MEMSTACK_HOST` | `127.0.0.1` | Server bind address | +| `MEMSTACK_PORT` | `7777` | Server bind port | +| `MEMSTACK_MCP_ENABLED` | `true` | Enable MCP server (separate process) | +| `MEMSTACK_MCP_PORT` | `7778` | Port for the standalone MCP server | +| `MEMSTACK_MCP_PATH` | `/mcp` | Reserved (accepted but unused) | +| `MEMSTACK_WATCHER_ENABLED` | `true` | Enable file watcher for vault sync | +| `MEMSTACK_WATCHER_DEBOUNCE_MS` | `2000` | Debounce time in ms for watcher events | +| `MEMSTACK_LOG_LEVEL` | `INFO` | Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL | +| `MEMSTACK_LOG_FILE` | `~/.memstack/logs/memstack.log` | Log file path | +| `MEMSTACK_LOG_ROTATION` | `10 MB` | Log rotation size threshold | +| `MEMSTACK_LOG_RETENTION` | `7 days` | Log retention period | +| `MEMSTACK_STATE_FILE` | `~/.memstack/state.json` | PID state file path | + +**Smart Write Pipeline:** + +| Variable | Default | Description | +| -------------------------------------- | ------------------------ | ------------------------------------------------ | +| `MEMSTACK_SIMILARITY_ADD_THRESHOLD` | `0.25` | Below this score, always add as new memory | +| `MEMSTACK_SIMILARITY_IGNORE_THRESHOLD` | `0.85` | Similarity threshold for high-similarity matches | +| `MEMSTACK_IMPORTANCE_INITIAL_SCORE` | `0.5` | Default importance for new memories | +| `MEMSTACK_IMPORTANCE_DECAY_HALFLIFE` | `7.0` | Half-life in days for importance decay | +| `MEMSTACK_IMPORTANCE_HIT_INCREMENT` | `0.05` | Importance bump on each retrieval | +| `MEMSTACK_LLM_MODEL` | `llama3` | Ollama model for smart write consultation | +| `MEMSTACK_LLM_HOST` | `http://localhost:11434` | Ollama host URL | + +**Search and Index:** + +| Variable | Default | Description | +| ----------------------------------- | ------------------------ | -------------------------------------------------- | +| `MEMSTACK_EMBEDDING_PROVIDER` | `fastembed` | Embedding provider: `ollama` or `fastembed` | +| `MEMSTACK_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` | Embedding model name (provider-specific) | +| `MEMSTACK_CHUNK_MAX_TOKENS` | `512` | Max tokens per chunk for semantic chunking | +| `MEMSTACK_CHUNK_OVERLAP_TOKENS` | `50` | Overlap tokens between adjacent chunks | +| `MEMSTACK_RRF_K` | `10` | RRF constant for hybrid search fusion | +| `MEMSTACK_IMPORTANCE_RERANK_WEIGHT` | `0.3` | Weight for importance score in reranking (0.0–1.0) | +| `MEMSTACK_INDEX_PATH` | `~/.memstack/index` | LanceDB index directory | +| `MEMSTACK_EMBEDDING_CACHE_SIZE` | `1024` | LRU cache size for embedding vectors | +| `MEMSTACK_FTS_REBUILD_INTERVAL` | `50` | Adds before FTS index rebuild | +| `MEMSTACK_SEARCH_CACHE_TTL` | `30` | TTL in seconds for search result cache | +| `MEMSTACK_VAULT_CACHE_SIZE` | `512` | LRU cache size for vault read/list operations | + +**Shared Mode:** + +| Variable | Default | Description | +| ---------------------- | ------- | ------------------------------------------------------------------- | +| `MEMSTACK_SHARED_MODE` | `false` | Enable shared mode — private writes also copied to shared namespace | + +**Inject:** + +| Variable | Default | Description | +| ------------------------------ | ------- | ----------------------------------------- | +| `MEMSTACK_INJECTION_MIN_SCORE` | `0.3` | Minimum score for inject endpoint results | +| `MEMSTACK_INJECTION_TOP_N` | `5` | Max results returned by inject endpoint | + +**Synthesis:** + +| Variable | Default | Description | +| ---------------------------- | --------- | -------------------------------------------------------------------------- | +| `MEMSTACK_SYNTHESIS_ENABLED` | `false` | Enable LLM synthesis for tagged writes (triggered by "auto-capture" tag) | +| `MEMSTACK_SYNTHESIS_MODEL` | _(empty)_ | Ollama model for synthesis (falls back to `MEMSTACK_LLM_MODEL` when empty) | + +**Consolidation:** + +| Variable | Default | Description | +| ----------------------------------- | --------- | ------------------------------------------------------------------------------ | +| `MEMSTACK_CONSOLIDATION_ENABLED` | `false` | Enable background memory consolidation | +| `MEMSTACK_CONSOLIDATION_INTERVAL` | `3600` | Seconds between consolidation runs (minimum 60) | +| `MEMSTACK_CONSOLIDATION_BATCH_SIZE` | `20` | Max memories per agent per run (1–100) | +| `MEMSTACK_CONSOLIDATION_MODEL` | _(empty)_ | Ollama model for consolidation (falls back to `MEMSTACK_LLM_MODEL` when empty) | + +--- + +## How It Works + +Every write goes through a deduplication pipeline: similarity check, then LLM consultation for ambiguous cases. The pipeline decides add (new), merge (append facts), update (replace), or ignore (duplicate). Memories are stored as Markdown files with YAML frontmatter — human-readable, version-controllable. Search uses vector + keyword hybrid search via LanceDB, reranked by importance with time-based decay. Each agent gets its own namespace; shared mode copies private writes to a shared pool. + +**Write pipeline:** similarity thresholds filter obvious matches, then Ollama decides add/merge/update/ignore for ambiguous cases; falls back to "add" if LLM is unavailable. + +**Storage:** Markdown files with YAML frontmatter in a vault directory — human-readable, editable, version-controllable. + +**Search:** vector + keyword hybrid via LanceDB, importance-weighted reranking with RRF fusion and time-based decay. --- ## Integrations -**OpenClaw Bridge** — TypeScript plugin for auto-recall, auto-capture, and native memory blocking → [Setup guide](docs/connect-openclaw.md) +**OpenClaw MCP** — Native MCP connection for memory tools. See [Connecting OpenClaw to MemStack](docs/connect-openclaw.md) for the setup guide. --- @@ -209,31 +578,97 @@ The MCP server exposes 7 memory operations as tools, running as a separate proce ### `ValidationError: vault_path field required` -`MEMSTACK_VAULT_PATH` not set. Set it in `.env` or as an environment variable before starting the server. +`MEMSTACK_VAULT_PATH` is not set. Add it to your `.env` file or set it as an environment variable before starting the server. ### Port 7777 already in use -Another process is on port 7777. Use `--port 8080` or kill the process on that port. +Another process is on port 7777. Use `--port 8080` to start on a different port, or stop the existing process. + +**macOS / Linux — find and kill the process:** + +```bash +lsof -i :7777 +kill -9 +``` + +**Windows CMD:** + +```cmd +netstat -ano | findstr :7777 +taskkill /F /PID +``` + +**Windows PowerShell:** + +```powershell +Get-Process -Id (Get-NetTCPConnection -LocalPort 7777).OwningProcess | Stop-Process -Force +``` + +Or start MemStack on a different port: + +```bash +uv run memstack start --port 8080 +``` ### Search returns 503 -Embedding provider unavailable. Start Ollama (`ollama serve`, then `ollama pull nomic-embed-text`) or set `MEMSTACK_EMBEDDING_PROVIDER=fastembed`. +The embedding provider is unavailable. The default provider is fastembed (no server needed). If using Ollama, start it (`ollama serve`, then `ollama pull nomic-embed-text`) or switch back to fastembed with `MEMSTACK_EMBEDDING_PROVIDER=fastembed`. CRUD operations still work when search is disabled. ### Smart write always adds (never deduplicates) -Ollama not running, so LLM consultation falls back to "add". Start Ollama and pull `llama3`. +Ollama is not running, so LLM consultation falls back to "add". Start Ollama and pull `llama3`: + +```bash +ollama serve +ollama pull llama3 +``` + +### `MemStack already running (PID ...)` + +A previous instance is still running. Stop it with `uv run memstack stop`. If the process is gone but the state file remains, `memstack stop` will clean it up automatically. + +### `.env` file not being read + +The `.env` file must be in the directory where you run `memstack start`. Ensure `MEMSTACK_VAULT_PATH` in the `.env` file points to an absolute path. If your `.env` file was saved by a text editor that adds a UTF-8 BOM (byte order mark), MemStack will handle it correctly as of v1.4.5. + +### Daemon fails to start + +Check the log file for errors: + +**macOS / Linux:** + +```bash +cat ~/.memstack/logs/memstack.log +``` + +**Windows CMD:** + +```cmd +type %USERPROFILE%\.memstack\logs\memstack.log +``` + +**Windows PowerShell:** + +```powershell +Get-Content $env:USERPROFILE\.memstack\logs\memstack.log +``` + +Common causes: `MEMSTACK_VAULT_PATH` not set, Ollama not reachable, or the port is already in use. -> More: [docs/troubleshooting.md](docs/troubleshooting.md) +> **Known warning:** You may see `PytestUnhandledThreadExceptionWarning` in test output related to `MagicMock` and `threading.Timer`. This is a test fixture issue, not a runtime problem — the production code uses real `threading.Timer` objects, not mocks. --- ## Changelog +- **v1.5.0** — Removed OpenClaw TypeScript bridge; MCP-native connection only; pagination for list endpoint and MCP memory_list; category classification auto-tags memories and boosts same-category merge candidates +- **v1.4.6** — LLM merge produces rewritten content instead of raw append; watcher deletes stale index entries on vault edit; scan_vault cleans up orphaned index entries; synthesis prompt preserves preference specifics; LLM prompt guides categorical grouping and contradiction handling +- **v1.4.5** — Lifespan refactor; BOM-safe .env parsing; version-agnostic test assertions; removed unused obsidian_mode; embedding defaults changed to fastembed; removed autofallback; dimension validation with auto-reindex - **v1.4.4** — LLM memory synthesis for auto-capture writes; background consolidation with rewrite/enrich/merge/split - **v1.4.3** — Merge decision in smart write pipeline; auto-ignore made opt-in; lower similarity thresholds - **v1.4.2** — Caching layer; async REST/MCP handlers; deferred importance updates -→ [Full changelog](CHANGELOG.md) +See [CHANGELOG.md](CHANGELOG.md) for the full history. --- diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..3d097bd --- /dev/null +++ b/STATUS.md @@ -0,0 +1,43 @@ +# STATUS — feat/integration + +## Current Phase + +Ship-ready. PRs #7 and #8 merged, polished, verified. All 596 tests pass, lint clean. + +## Merged PRs + +| # | Branch | Status | +| --- | ------------------------- | ------------------------------------------------------ | +| 4 | feat/config-fixes | Merged (squash) | +| 3 | feat/version-assertions | Merged (squash) | +| 5 | feat/lifespan-refactor | Merged (manual — conflict resolution on CHANGELOG.md) | +| 7 | feat/embedding-config | Merged (squash) | +| 8 | feat/dimension-validation | Merged (conflict resolution on STATUS.md, progress.md) | + +## Changes from PR #7 (feat/embedding-config) + +- `src/memstack/core/config.py` — removed `embedding_autofallback: bool = True`, changed `embedding_provider` default from `"ollama"` to `"fastembed"`, changed `embedding_model` default from `"nomic-embed-text"` to `"BAAI/bge-small-en-v1.5"` +- `src/memstack/search/embeddings.py` — removed `from loguru import logger`, removed `autofallback` variable and fallback branch, updated `getattr` defaults, both provider branches now raise RuntimeError immediately when unavailable +- `.env.example` — removed `MEMSTACK_EMBEDDING_AUTOFALLBACK` block, updated provider/model defaults +- `tests/test_embeddings.py` — removed autofallback tests, updated RuntimeError match strings +- `tests/test_config.py` — removed `embedding_autofallback` assertions, updated defaults + +## Changes from PR #8 (feat/dimension-validation) + +- `src/memstack/search/index.py` — added `validate_dimension()` method: checks existing LanceDB table vector dimension against current embedding provider; on mismatch, logs warning and triggers full reindex +- `src/memstack/interfaces/rest/app.py` — call `search_index.validate_dimension()` after SearchIndex creation, before vault scan +- `src/memstack/interfaces/mcp/server.py` — call `search_index.validate_dimension()` after SearchIndex creation in mcp_lifespan +- `tests/test_search_index.py` — added `TestValidateDimension` class with 5 tests + +## Out-of-Scope Items (flagged for integration branch) + +- ~~README.md: stale embedding defaults and removed `MEMSTACK_EMBEDDING_AUTOFALLBACK` field~~ — fixed: defaults updated, AUTOFALLBACK row removed, troubleshooting updated +- ~~ARCHITECTURE.md: stale autofallback references~~ — fixed: removed autofallback from startup sequence and architectural patterns, updated embeddings.py description +- ~~CHANGELOG.md: no entry for embedding config or dimension validation changes~~ — fixed: added entries under v1.4.5 + +## Verification + +- 596 tests pass, 0 failures +- Ruff lint clean, format clean +- Zero TODOs, FIXMEs, HACKs in changed files +- Zero stale autofallback references in src/, tests/, .env.example diff --git a/docs/connect-openclaw.md b/docs/connect-openclaw.md index d2eed53..a7f637d 100644 --- a/docs/connect-openclaw.md +++ b/docs/connect-openclaw.md @@ -1,14 +1,13 @@ -# Connecting OpenClaw to MemStack +# Connecting OpenClaw to MemStack via MCP -This guide walks through making MemStack the exclusive memory engine for an OpenClaw agent. Once connected, OpenClaw's native memory is fully disabled — every memory read and write goes through MemStack. +This guide configures an OpenClaw agent to use MemStack's memory tools through a native MCP connection. The agent calls memory tools explicitly as tool calls — there are no hooks, no auto-capture, and no auto-recall. You tell the agent when to remember something. --- ## Prerequisites - MemStack is installed, `.env` is valid, `MEMSTACK_VAULT_PATH` is set -- OpenClaw is installed, Node.js 22.14+ is available -- `openclaw gateway status` returns healthy +- OpenClaw is installed and `openclaw gateway status` returns healthy --- @@ -35,22 +34,11 @@ curl http://localhost:7778/health # MCP If either fails, check `MEMSTACK_LOG_FILE` before continuing. -## Step 3 — Build and install the bridge plugin +## Step 3 — Disable native memory -From the MemStack project root: +OpenClaw's built-in memory system operates at the framework level, below the agent loop. If it is not disabled, OpenClaw will continue to use its own memory pipeline for all reads and writes — MemStack tools will never be called regardless of MCP configuration. This step is **required**, not optional. -``` -cd openclaw-bridge -npm install -npm run build -openclaw plugins install . -``` - -The last command registers the plugin with OpenClaw by path. It does not copy files — it records the path and loads from it on each gateway start. - -## Step 4 — Update `openclaw.json` - -Open the file from Step 1 and apply the complete configuration block below. This does four things: disables all four native memory pathways, claims the memory slot for the bridge plugin, configures the plugin with MemStack's endpoints, and registers MemStack as an MCP server. +Open the file from Step 1 and add these keys: ```json { @@ -67,9 +55,6 @@ Open the file from Step 1 and apply the complete configuration block below. This } }, "plugins": { - "slots": { - "memory": "memstack-bridge-v1" - }, "entries": { "active-memory": { "enabled": false @@ -80,16 +65,25 @@ Open the file from Step 1 and apply the complete configuration block below. This "enabled": false } } - }, - "memstack-bridge-v1": { - "enabled": true, - "config": { - "restEndpoint": "http://localhost:7777", - "mcpEndpoint": "http://localhost:7778" - } } } - }, + } +} +``` + +What each section does: + +- `memorySearch.enabled: false` — disables the vector search and retrieval pipeline +- `compaction.memoryFlush.enabled: false` — stops the framework writing to native Markdown during context compaction +- `plugins.entries.active-memory.enabled: false` — shuts down the proactive recall sub-agent +- `plugins.entries.memory-core.config.dreaming.enabled: false` — stops the background knowledge consolidation scheduler + +## Step 4 — Add the MCP server to `openclaw.json` + +Open the file from Step 1 and add the MCP server block: + +```json +{ "mcp": { "servers": { "memstack": { @@ -101,20 +95,20 @@ Open the file from Step 1 and apply the complete configuration block below. This } ``` -What each section does: +This registers all 7 MemStack tools in the agent's tool catalog: -- `memorySearch.enabled: false` — disables the vector search and retrieval pipeline -- `compaction.memoryFlush.enabled: false` — stops the framework writing to native Markdown during context compaction. Without this, compaction events silently bypass the bridge plugin. -- `plugins.entries.active-memory.enabled: false` — shuts down the proactive recall sub-agent that runs its own LLM turn before the main response -- `plugins.entries.memory-core.config.dreaming.enabled: false` — stops the background knowledge consolidation scheduler -- `plugins.slots.memory: "memstack-bridge-v1"` — claims the exclusive slot. The value must match the `id` in `openclaw.plugin.json` exactly. -- `plugins.entries.memstack-bridge-v1` — activates the plugin and passes endpoint config -- `mcp.servers.memstack` — registers MemStack MCP tools in the agent's tool catalog for explicit agent use +| Tool | Description | +| -------------------------- | ----------------------- | +| `memory_write` | Write a memory | +| `memory_search` | Search by meaning | +| `memory_read` | Read a single memory | +| `memory_delete` | Delete a memory | +| `memory_list` | List agent memories | +| `memory_inject` | Inject context | +| `memory_get_system_prompt` | Get system prompt block | ## Step 5 — Restart the gateway -A standard restart is sufficient in most cases: - ``` openclaw gateway restart ``` @@ -128,17 +122,7 @@ openclaw gateway install --force openclaw gateway start ``` -## Step 6 — Verify the bridge plugin is active - -``` -openclaw plugins inspect memstack-bridge-v1 --runtime --json -``` - -The output must show the plugin's registered memory capability and all three hooks (`before_prompt_build`, `agent_end`, `before_tool_call`). If the plugin is not listed, the path from Step 3 was not recorded correctly — re-run `openclaw plugins install .` from the `openclaw-bridge/` directory. - -Do not use `openclaw memory status` to verify — it runs in a subprocess that does not load the full plugin registry. - -## Step 7 — Verify MemStack MCP tools are visible +## Step 6 — Verify MCP tools are visible ``` openclaw mcp list memstack --schema @@ -158,7 +142,7 @@ memory_get_system_prompt If empty, MemStack's MCP server is not reachable. Confirm `memstack start` is running and `MEMSTACK_MCP_PORT` matches the port in `openclaw.json`. -## Step 8 — Inject the system prompt +## Step 7 — (Optional) Inject the system prompt Fetch the mandate block for your agent: @@ -168,56 +152,91 @@ curl http://localhost:7777/agents/{your_agent_id}/system-prompt Copy the `system_prompt_block` value. Prepend it to the agent's effective system prompt — before AGENTS.md, before SOUL.md, before BOOT.md. It must load first. -Note: the bridge plugin enforces memory routing at the framework level regardless of the system prompt. This step adds behavioral guidance on top — telling the model how to use MemStack's tools explicitly when it wants to. +This adds behavioral guidance telling the model how to use MemStack's tools. The agent calls memory tools explicitly as tool calls — there is no automatic capture or recall. -## Step 9 — Verify MemStack is receiving memories +## Step 8 — Remembering things + +MemStack does not automatically capture conversations. When you want the agent to remember something, tell it to call `memory_write` explicitly. + +### With synthesis enabled -Start an OpenClaw session and have a conversation. Then check the vault: +If `MEMSTACK_SYNTHESIS_ENABLED=true` in your `.env`, pass the conversation as `content` and include `tags: ["auto-capture"]`: ``` -ls {MEMSTACK_VAULT_PATH}/memstack/{your_agent_id}/ +memory_write( + content="User said they prefer dark mode in all apps. Assistant acknowledged and noted it for future reference.", + agent_id="my-agent", + tags=["auto-capture"] +) ``` -Files should appear within seconds of the session ending. The `agent_end` hook fires after every turn and POSTs the exchange to MemStack automatically. If no files appear after multiple turns, check the gateway log for bridge plugin errors: +The `"auto-capture"` tag triggers server-side synthesis: the LLM decomposes the raw exchange into discrete factual statements before each one enters the smart write pipeline independently. This produces cleaner, more searchable memories. + +### Without synthesis + +If `MEMSTACK_SYNTHESIS_ENABLED=false` (the default), the `tags` parameter is optional and content passes through the smart write pipeline as-is — still deduplicated against existing memories, but not decomposed: ``` -openclaw logs --tail 50 | grep memstack +memory_write( + content="User prefers dark mode in all apps.", + agent_id="my-agent" +) ``` ---- +### Recalling memories -## Removing the integration +The agent can search for memories at any time: ``` -openclaw plugins uninstall memstack-bridge-v1 +memory_search(query="display preferences", agent_id="my-agent") ``` -Then remove the four deactivation keys and the `plugins.slots.memory` entry from `openclaw.json`. Restart the gateway. Native memory will resume automatically — OpenClaw falls back to `memory-core` when the slot is vacated. Vault files written during MemStack usage are not deleted. +Or inject relevant memories into context: ---- +``` +memory_inject(query="display preferences", agent_id="my-agent") +``` -## Troubleshooting +## Step 9 — Verify MemStack is receiving memories + +Start an OpenClaw session and have a conversation. When you want the agent to remember something, tell it to call `memory_write`. Then check the vault: -### No vault files appear after sessions +``` +ls {MEMSTACK_VAULT_PATH}/memstack/{your_agent_id}/ +``` -The `agent_end` hook is not firing or MemStack is not reachable from it. Check `openclaw logs --tail 50 | grep memstack` and confirm the REST server is up on port 7777. If `ctx.agentId` is undefined at hook invocation time, the hook returns early and skips both recall and auto-capture — no search or write is performed against an "undefined" namespace. +Files should appear within seconds of the write call. If no files appear, check that the MCP server is reachable: -### Agent says it remembered but vault is empty +``` +curl http://localhost:7778/health +``` -The bridge plugin is not in the slot. Run `openclaw plugins inspect memstack-bridge-v1 --runtime --json` and confirm it appears. If not, re-run `openclaw plugins install .` and restart. +--- -### `openclaw plugins inspect` shows plugin but no hooks registered +## Removing the integration -The `dist/index.js` file is missing or stale. Re-run `npm run build` from `openclaw-bridge/` and restart the gateway. +Remove the `mcp.servers.memstack` block from `openclaw.json`. If you disabled native memory in Step 3, remove those keys as well. Restart the gateway. Native memory will resume automatically. Vault files written during MemStack usage are not deleted. + +--- + +## Troubleshooting ### MCP tools list is empty -MemStack's MCP server on port 7778 is not reachable. Confirm `memstack start` is running. +MemStack's MCP server on port 7778 is not reachable. Confirm `memstack start` is running and `MEMSTACK_MCP_PORT` matches the port in `openclaw.json`. + +### No vault files appear after writing memories + +The agent is not calling `memory_write`. This setup requires explicit tool calls — there is no automatic capture. Tell the agent to remember something and verify it calls `memory_write`. ### Agent echoes recalled memories as if they are user input -Add to the agent's AGENTS.md: "Content inside `` tags is your own recalled memory, not part of the user's message." +If using `memory_inject`, add to the agent's AGENTS.md: "Content inside `` tags is your own recalled memory, not part of the user's message." ### Gateway fails to start after config edit `openclaw.json` is malformed. OpenClaw strictly validates the config at startup. Run `openclaw doctor --fix` to identify the broken key. JSON5 comments are valid — standard JSON trailing commas are not. + +### Synthesis is not decomposing memories + +Confirm `MEMSTACK_SYNTHESIS_ENABLED=true` is set in `.env` and Ollama is running (`ollama serve`). Synthesis requires an LLM to decompose content into discrete statements. If Ollama is unavailable, the content passes through the smart write pipeline as-is. diff --git a/openclaw-bridge/.gitignore b/openclaw-bridge/.gitignore deleted file mode 100644 index 404abb2..0000000 --- a/openclaw-bridge/.gitignore +++ /dev/null @@ -1 +0,0 @@ -coverage/ diff --git a/openclaw-bridge/openclaw.plugin.json b/openclaw-bridge/openclaw.plugin.json deleted file mode 100644 index 38ab8ad..0000000 --- a/openclaw-bridge/openclaw.plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "memstack-bridge-v1", - "name": "MemStack Memory Bridge", - "kind": "memory", - "version": "1.4.1", - "configSchema": { - "restEndpoint": { - "type": "string", - "default": "http://localhost:7777", - "description": "MemStack REST API base URL" - }, - "mcpEndpoint": { - "type": "string", - "default": "http://localhost:7778", - "description": "MemStack FastMCP server base URL" - } - } -} \ No newline at end of file diff --git a/openclaw-bridge/package-lock.json b/openclaw-bridge/package-lock.json deleted file mode 100644 index 8069114..0000000 --- a/openclaw-bridge/package-lock.json +++ /dev/null @@ -1,10413 +0,0 @@ -{ - "name": "memstack-bridge", - "version": "1.4.3", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "memstack-bridge", - "version": "1.4.3", - "devDependencies": { - "@sinclair/typebox": "^0.32.0", - "@vitest/coverage-v8": "^3.1.0", - "openclaw": "latest", - "typescript": "^5.4.0", - "vitest": "^3.1.0" - }, - "peerDependencies": { - "openclaw": ">=2026.1.0" - } - }, - "node_modules/@agentclientprotocol/sdk": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.21.0.tgz", - "integrity": "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.93.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.93.0.tgz", - "integrity": "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@anthropic-ai/vertex-sdk": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.16.0.tgz", - "integrity": "sha512-ntxemtRkwPsjVzGQJsmBPRW38tfas6VuVlD1v6pHffDJKLPtCdaiN9KUQeraJ/F34tjxEWlsaCnl3t/orJm1Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": ">=0.50.3 <1", - "google-auth-library": "^9.4.2" - } - }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-bedrock": { - "version": "3.1042.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock/-/client-bedrock-3.1042.0.tgz", - "integrity": "sha512-oEVjGU8wgW+eTF7ApdRU4jTs/iMVl4OdfpLmiNLuB082UVxxN/fQ5GIX2Ktbyt+x0mPlI3fug36XnOyf7oCo+Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/token-providers": "3.1042.0", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1042.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1042.0.tgz", - "integrity": "sha512-uYJ/HDSQvorlgYqZSwRFGolEx5wygqyuBRfemXJ3Bla2yiRj9maSVOvWP88i/hDC2BKoH6NQw8GPB9Z4RYAnwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/eventstream-handler-node": "^3.972.14", - "@aws-sdk/middleware-eventstream": "^3.972.10", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/middleware-websocket": "^3.972.16", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/token-providers": "3.1042.0", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/eventstream-serde-browser": "^4.2.14", - "@smithy/eventstream-serde-config-resolver": "^4.3.14", - "@smithy/eventstream-serde-node": "^4.2.14", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.1043.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1043.0.tgz", - "integrity": "sha512-ALRMiZ1UO6Vs4stAv0xkuUTxGJGHbSQ/T/m62sKDWu9fyTiz4Xo5UHUyeD7l5xgfRWVCbpzSJzfoEtzlBrc6TQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.974.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", - "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.22", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.31", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.31.tgz", - "integrity": "sha512-W5JtzDp3ejzhOOknXlnt+vJsNN2GZdAcBK+hR7HQ1DCacXqS0UpmnIyihIU7CK0IB+XYWeBaN3bBv4pXavp7Vg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", - "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", - "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", - "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-login": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", - "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", - "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-ini": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", - "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", - "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/token-providers": "3.1041.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1041.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", - "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", - "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.1043.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1043.0.tgz", - "integrity": "sha512-pOM9CG72ecjZwlMnmihwDbZYjgTHz28G+W5yUTlL9a9CpR6FxIEyzejKer/69RRN5ePUAyEnsmwuxn4EdBKzDw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.1043.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-cognito-identity": "^3.972.31", - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-ini": "^3.972.38", - "@aws-sdk/credential-provider-login": "^3.972.38", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", - "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", - "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", - "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", - "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", - "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", - "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-arn-parser": "^3.972.3", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", - "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", - "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-format-url": "^3.972.10", - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/eventstream-serde-browser": "^4.2.14", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", - "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.25", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", - "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/config-resolver": "^4.4.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", - "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.37", - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1042.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1042.0.tgz", - "integrity": "sha512-rOEGTVOrceb/1CfIWK0zl1v2WS70f/i5bDirLl5xdFAbVQ5znub6Ezf2ugmJEg+rionO0IkwbKX3Dh3T/oZjbA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", - "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", - "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-endpoints": "^3.4.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", - "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", - "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", - "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", - "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/bedrock-token-generator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@aws/bedrock-token-generator/-/bedrock-token-generator-1.1.0.tgz", - "integrity": "sha512-i+DkWnfdA4j4sffy9dI4k3OGoOWqN8CTGdtO4IZ3c0kpKYFr6KyqzqLQmoRNrF3ACFcWj6u+J6cbBQ97j9wx5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-providers": "^3.525.0", - "@aws-sdk/util-format-url": ">=3.525.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/hash-node": ">=2.1.3", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": ">=3.2.1", - "@smithy/signature-v4": ">=2.1.3", - "@smithy/types": ">=2.11.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@clack/core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.0.tgz", - "integrity": "sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@clack/prompts": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.3.0.tgz", - "integrity": "sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "1.3.0", - "fast-string-width": "^3.0.2", - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@google/genai/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@google/genai/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@google/genai/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/genai/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@google/genai/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@google/genai/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@grammyjs/runner": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@grammyjs/runner/-/runner-2.0.3.tgz", - "integrity": "sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0" - }, - "engines": { - "node": ">=12.20.0 || >=14.13.1" - }, - "peerDependencies": { - "grammy": "^1.13.1" - } - }, - "node_modules/@grammyjs/transformer-throttler": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@grammyjs/transformer-throttler/-/transformer-throttler-1.2.1.tgz", - "integrity": "sha512-CpWB0F3rJdUiKsq7826QhQsxbZi4wqfz1ccKX+fr+AOC+o8K7ZvS+wqX0suSu1QCsyUq2MDpNiKhyL2ZOJUS4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bottleneck": "^2.0.0" - }, - "engines": { - "node": "^12.20.0 || >=14.13.1" - }, - "peerDependencies": { - "grammy": "^1.0.0" - } - }, - "node_modules/@grammyjs/types": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.26.0.tgz", - "integrity": "sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@homebridge/ciao": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.8.tgz", - "integrity": "sha512-lNhpCsZVbdbjz2trFjQdzQ3cUIMZQMIMksi7wd3ntTIYgdaGLqT1Ms97DfVIJYHzRuduf56ISvgU8RRLTpK/ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "fast-deep-equal": "^3.1.3", - "source-map-support": "^0.5.21", - "tslib": "^2.8.1" - }, - "bin": { - "ciao-bcs": "lib/bonjour-conformance-testing.js" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lydell/node-pty": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.2.0-beta.12.tgz", - "integrity": "sha512-qIK890UwPupoj07osVvgOIa++1mxeHbcGry4PKRHhNVNs81V2SCG34eJr46GybiOmBtc8Sj5PB1/GGM5PL549g==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", - "@lydell/node-pty-linux-x64": "1.2.0-beta.12", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", - "@lydell/node-pty-win32-x64": "1.2.0-beta.12" - } - }, - "node_modules/@lydell/node-pty-darwin-arm64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-arm64/-/node-pty-darwin-arm64-1.2.0-beta.12.tgz", - "integrity": "sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lydell/node-pty-darwin-x64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-x64/-/node-pty-darwin-x64-1.2.0-beta.12.tgz", - "integrity": "sha512-4LrS5pCJwqHKDVf1zS2gyNV0m4hKAXch+XZNhbZ6LY8uwVL8BhchzQBO40Os5anuRxRCWzHpw4Sp64Ie8q7E4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lydell/node-pty-linux-arm64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-arm64/-/node-pty-linux-arm64-1.2.0-beta.12.tgz", - "integrity": "sha512-Sx+A71x5BDGHt9ansfrtGxwq2VFVDWvJUAdlUL0Hv0qeiJUfts+hgopx+CgT4PSwahKjdEgtu0+FAfY9rICKRw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lydell/node-pty-linux-x64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-x64/-/node-pty-linux-x64-1.2.0-beta.12.tgz", - "integrity": "sha512-bJzs94njofYhGg/UDqW1nj0dtvvu+2OvxMY+RlLS1T17VgcktKoIR6PuenTwE5HJ/D6StCPADmXcT0nNsCKmIQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lydell/node-pty-win32-arm64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-arm64/-/node-pty-win32-arm64-1.2.0-beta.12.tgz", - "integrity": "sha512-p7POgjVEiFaBC3/y+AKuV1FzePCsJ6HmZDv2XK+jBZSfwP8+uBAw181ZiKYN1YuRa/XpmBGaWezcI8hZkbW++g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@lydell/node-pty-win32-x64": { - "version": "1.2.0-beta.12", - "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-x64/-/node-pty-win32-x64-1.2.0-beta.12.tgz", - "integrity": "sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@mariozechner/clipboard": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", - "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.2", - "@mariozechner/clipboard-darwin-universal": "0.3.2", - "@mariozechner/clipboard-darwin-x64": "0.3.2", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-musl": "0.3.2", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" - } - }, - "node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", - "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", - "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", - "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", - "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", - "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", - "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", - "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", - "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", - "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", - "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/jiti": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", - "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "std-env": "^3.10.0", - "yoctocolors": "^2.1.2" - }, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@mariozechner/pi-agent-core": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.73.0.tgz", - "integrity": "sha512-ugcpvq0X9fr9fTSK29/3S4+KU/eeVMrBb7ZU3HqiF3xD7I1GlgumLj4FYmDrYSEA6+rzgNWlJUKwjKh9o0Z6AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/pi-ai": "^0.73.0", - "typebox": "^1.1.24" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.73.0.tgz", - "integrity": "sha512-phKOpcde/ssz6UYszkmaGJ9LF9mgt/AP8LrtSwsfap+kMSeFfSQ2/mCSBT1mLJ2BqVuff9uXs1/+op1aQeaafQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.91.1", - "@aws-sdk/client-bedrock-runtime": "^3.1030.0", - "@google/genai": "^1.40.0", - "@mistralai/mistralai": "^2.2.0", - "chalk": "^5.6.2", - "openai": "6.26.0", - "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@mariozechner/pi-ai/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/@mariozechner/pi-coding-agent": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.73.0.tgz", - "integrity": "sha512-Fs2dRIgtjDT8X5VDGNGzxj251B0FvkRsgX03YJv1FK4wg5Maj+jkf8/5A6tbPnPcXsCgs41xxJRf3tF5vJRccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.73.0", - "@mariozechner/pi-ai": "^0.73.0", - "@mariozechner/pi-tui": "^0.73.0", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "uuid": "^14.0.0", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.5" - } - }, - "node_modules/@mariozechner/pi-coding-agent/node_modules/file-type": { - "version": "21.3.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", - "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/@mariozechner/pi-coding-agent/node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/@mariozechner/pi-tui": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.73.0.tgz", - "integrity": "sha512-St1W+tMPKHatfK+lblsKfL+SsFyFVMK2tW6xHpBfCiMuevbOCRo/CMatso7mu1642UO04ncmfCrrpUK5L9aoog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", - "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "koffi": "^2.9.0" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", - "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@mozilla/readability": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", - "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/canvas": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", - "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", - "dev": true, - "license": "MIT", - "optional": true, - "workspaces": [ - "e2e/*" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.100", - "@napi-rs/canvas-darwin-arm64": "0.1.100", - "@napi-rs/canvas-darwin-x64": "0.1.100", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", - "@napi-rs/canvas-linux-arm64-musl": "0.1.100", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", - "@napi-rs/canvas-linux-x64-gnu": "0.1.100", - "@napi-rs/canvas-linux-x64-musl": "0.1.100", - "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", - "@napi-rs/canvas-win32-x64-msvc": "0.1.100" - } - }, - "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", - "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", - "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", - "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", - "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", - "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", - "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", - "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", - "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", - "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", - "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.100", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", - "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", - "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", - "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", - "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", - "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", - "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", - "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", - "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", - "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", - "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", - "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", - "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", - "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", - "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", - "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", - "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", - "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", - "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", - "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", - "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", - "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", - "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", - "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", - "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", - "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", - "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@sinclair/typebox": { - "version": "0.32.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.35.tgz", - "integrity": "sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@slack/bolt": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.7.2.tgz", - "integrity": "sha512-ALHtaS2iaP2WAWgX08yXsoCxEDitC6AqZs26ot6smXJQzBFMM4slVP+w3blLwzUV551xZ/+9RlBmWHsZDJJ5HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/oauth": "^3.0.5", - "@slack/socket-mode": "^2.0.7", - "@slack/types": "^2.20.1", - "@slack/web-api": "^7.15.1", - "axios": "^1.12.0", - "express": "^5.0.0", - "path-to-regexp": "^8.1.0", - "raw-body": "^3", - "tsscmp": "^1.0.6" - }, - "engines": { - "node": ">=18", - "npm": ">=8.6.0" - }, - "peerDependencies": { - "@types/express": "^5.0.0" - } - }, - "node_modules/@slack/logger": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", - "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": ">=18" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/oauth": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.5.tgz", - "integrity": "sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/web-api": "^7.15.0", - "@types/jsonwebtoken": "^9", - "@types/node": ">=18", - "jsonwebtoken": "^9" - }, - "engines": { - "node": ">=18", - "npm": ">=8.6.0" - } - }, - "node_modules/@slack/socket-mode": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz", - "integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/web-api": "^7.15.0", - "@types/node": ">=18", - "@types/ws": "^8", - "eventemitter3": "^5", - "ws": "^8" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/types": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.0.tgz", - "integrity": "sha512-ZLMsKnD5KLRPmhFEoGoBQUD5Pc2bH3xFc5ygHlioEc0WmLGyZGoGCtMff4rpejrFnptrhfxcKpWxW4r3g39R0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/web-api": { - "version": "7.15.2", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.15.2.tgz", - "integrity": "sha512-/m9qVFkiq85Oa/FSQwYIRDa/AO4qNYkDh4sRBK1WqEc2+RyG7w4tbU6rBIwUOcc/TmWOIr24Nraquxg7um5mYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.1", - "@slack/types": "^2.21.0", - "@types/node": ">=18", - "@types/retry": "0.12.0", - "axios": "^1.15.0", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.4", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.17", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", - "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.23.17", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", - "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", - "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", - "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", - "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", - "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", - "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", - "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", - "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", - "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", - "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", - "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.32", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", - "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", - "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/service-error-classification": "^4.3.1", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", - "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", - "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", - "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", - "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", - "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", - "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", - "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-uri-escape": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", - "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", - "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", - "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", - "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.13", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", - "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", - "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", - "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.49", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", - "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.54", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", - "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.17", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", - "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", - "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", - "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.3.1", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.25", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", - "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@telegraf/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", - "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agent-base": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", - "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", - "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bn.js": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", - "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/croner": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/croner/-/croner-10.0.1.tgz", - "integrity": "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==", - "dev": true, - "funding": [ - { - "type": "other", - "url": "https://paypal.me/hexagonpp" - }, - { - "type": "github", - "url": "https://github.com/sponsors/hexagon" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-uri-to-buffer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz", - "integrity": "sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/degenerator": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-7.0.1.tgz", - "integrity": "sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "quickjs-wasi": "^2.2.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "dev": true, - "license": "MIT" - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.0.tgz", - "integrity": "sha512-XKhFohWaSBdVJNTi5TaHziqnPkv04I9UQV6q1Wy7Ui6GGQZVW12ojDFwqer14EvCXxjvPG0CyWXx7cAXpALB4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "10.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", - "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, - "node_modules/fast-xml-builder": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.9.tgz", - "integrity": "sha512-jcyKVSEX13iseJqg7n/KWw+xnu/7fdrZ333Fac54KjHDIELVCfDDJXYIm6DTJ0Su4gSzrhqiK0DzY/wZbF40mw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.1.3" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", - "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.5", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-type": { - "version": "22.0.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-22.0.1.tgz", - "integrity": "sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.5", - "token-types": "^6.1.2", - "uint8array-extras": "^1.5.0" - }, - "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gaxios/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.0.tgz", - "integrity": "sha512-CqtZlMKvfJeY0Zxv8wazDwXmSKmnMnsmNy8j8+wudi8EyG/pMUB1NqHc+Tv1QaNtpYsK9nOYjb7r7Ufu32RPSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.2.0", - "data-uri-to-buffer": "8.0.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/global-agent": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.3.tgz", - "integrity": "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "globalthis": "^1.0.2", - "matcher": "^4.0.0", - "semver": "^7.3.5", - "serialize-error": "^8.1.0" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/grammy": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.42.0.tgz", - "integrity": "sha512-1AdCge+AkjSdp2FwfICSFnVbl8Mq3KVHJDy+DgTI9+D6keJ0zWALPRKas5jv/8psiCzL4N2cEOcGW7O45Kn39g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@grammyjs/types": "3.26.0", - "abort-controller": "^3.0.0", - "debug": "^4.4.3", - "node-fetch": "^2.7.0" - }, - "engines": { - "node": "^12.20.0 || >=14.13.1" - } - }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http_ece": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz", - "integrity": "sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.0.0.tgz", - "integrity": "sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/https-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.0.0.tgz", - "integrity": "sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", - "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/koffi": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", - "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://liberapay.com/Koromix" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/linkedom": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.12.tgz", - "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "css-select": "^5.1.0", - "cssom": "^0.5.0", - "html-escaper": "^3.0.3", - "htmlparser2": "^10.0.0", - "uhyphen": "^0.2.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "canvas": ">= 2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/linkedom/node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/matcher": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz", - "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/netmask": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", - "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-edge-tts": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/node-edge-tts/-/node-edge-tts-1.2.10.tgz", - "integrity": "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "https-proxy-agent": "^7.0.1", - "ws": "^8.13.0", - "yargs": "^17.7.2" - }, - "bin": { - "node-edge-tts": "bin.js" - } - }, - "node_modules/node-edge-tts/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/node-edge-tts/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/node-edge-tts/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-edge-tts/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/node-edge-tts/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-edge-tts/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-edge-tts/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openai": { - "version": "6.36.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.36.0.tgz", - "integrity": "sha512-Has2YbIusMq9wQEierFsgf9c783dy1y9arX459LmphNacEkkM5yxi2RIyXP0LmkOroQyW19iTwALHL8Yf26UKA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/openclaw": { - "version": "2026.5.5", - "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.5.5.tgz", - "integrity": "sha512-735aCg6D4xRt4g0fZfSZB0ZqFfAThLbW8WFD740jhflkk7qzoNr9PDFaOQIqsDAXlOneTJtCayUlforheJKh9A==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@agentclientprotocol/sdk": "0.21.0", - "@anthropic-ai/sdk": "0.93.0", - "@anthropic-ai/vertex-sdk": "^0.16.0", - "@aws-sdk/client-bedrock": "3.1042.0", - "@aws-sdk/client-bedrock-runtime": "3.1042.0", - "@aws-sdk/credential-provider-node": "3.972.39", - "@aws/bedrock-token-generator": "^1.1.0", - "@clack/prompts": "^1.3.0", - "@google/genai": "^1.51.0", - "@grammyjs/runner": "^2.0.3", - "@grammyjs/transformer-throttler": "^1.2.1", - "@homebridge/ciao": "^1.3.8", - "@lydell/node-pty": "1.2.0-beta.12", - "@mariozechner/pi-agent-core": "0.73.0", - "@mariozechner/pi-ai": "0.73.0", - "@mariozechner/pi-coding-agent": "0.73.0", - "@mariozechner/pi-tui": "0.73.0", - "@modelcontextprotocol/sdk": "1.29.0", - "@mozilla/readability": "^0.6.0", - "@slack/bolt": "^4.7.2", - "@slack/types": "^2.21.0", - "@slack/web-api": "^7.15.2", - "ajv": "^8.20.0", - "chalk": "^5.6.2", - "chokidar": "^5.0.0", - "commander": "^14.0.3", - "croner": "^10.0.1", - "dotenv": "^17.4.2", - "express": "5.2.1", - "file-type": "22.0.1", - "global-agent": "^4.1.3", - "grammy": "^1.42.0", - "https-proxy-agent": "^9.0.0", - "ipaddr.js": "^2.4.0", - "jiti": "^2.6.1", - "json5": "^2.2.3", - "jszip": "^3.10.1", - "linkedom": "^0.18.12", - "markdown-it": "14.1.1", - "minimatch": "10.2.5", - "node-edge-tts": "^1.2.10", - "openai": "^6.36.0", - "openshell": "0.1.0", - "pdfjs-dist": "^5.7.284", - "playwright-core": "1.59.1", - "proxy-agent": "^8.0.1", - "qrcode": "1.5.4", - "tar": "7.5.13", - "tokenjuice": "0.7.0", - "tree-sitter-bash": "^0.25.1", - "tslog": "^4.10.2", - "typebox": "1.1.37", - "undici": "8.2.0", - "web-push": "^3.6.7", - "web-tree-sitter": "^0.26.8", - "ws": "^8.20.0", - "yaml": "^2.8.4", - "zod": "^4.4.3" - }, - "bin": { - "openclaw": "openclaw.mjs" - }, - "engines": { - "node": ">=22.14.0" - }, - "optionalDependencies": { - "sqlite-vec": "0.1.9" - } - }, - "node_modules/openshell": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/openshell/-/openshell-0.1.0.tgz", - "integrity": "sha512-B7jLewH+d73hraWcrSFgNOjvd+frW5JPejkTpqgj2EJBjX/Yk1Y4blgP5pDl4FwrBxfmwsTKR08Uwgrdo+xpSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "dotenv": "^16.5.0", - "telegraf": "^4.16.3" - }, - "bin": { - "openshell": "bin/openshell.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/openshell/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pac-proxy-agent": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.0.1.tgz", - "integrity": "sha512-3ZOSpLboOlpW4yp8Cuv21KlTULRqyJ5Uuad3wXpSKFrxdNgcHEyoa22GRaZ2UlgCVuR6z+5BiavtYVvbajL/Yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4", - "get-uri": "8.0.0", - "http-proxy-agent": "9.0.0", - "https-proxy-agent": "9.0.0", - "pac-resolver": "9.0.1", - "quickjs-wasi": "^2.2.0", - "socks-proxy-agent": "10.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/pac-resolver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-9.0.1.tgz", - "integrity": "sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "7.0.1", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "quickjs-wasi": "^2.2.0" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/pdfjs-dist": { - "version": "5.7.284", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.7.284.tgz", - "integrity": "sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=22.13.0 || >=24" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^0.1.100" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/protobufjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", - "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.1", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-agent": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.1.tgz", - "integrity": "sha512-kccqGBqHZXR8onQhY/ganJjoO8QIKKRiFBhPOzbTZK16attzSZ/0XSmp9H7jrRxPKHjhGyx1q32lMPrJ3uLFgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4", - "http-proxy-agent": "9.0.0", - "https-proxy-agent": "9.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "9.0.1", - "proxy-from-env": "^2.0.0", - "socks-proxy-agent": "10.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qrcode": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", - "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "dijkstrajs": "^1.0.1", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/qrcode/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/qrcode/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/qrcode/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qrcode/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qrcode/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/qrcode/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qrcode/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quickjs-wasi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz", - "integrity": "sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==", - "dev": true, - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/rollup": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", - "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.3", - "@rollup/rollup-android-arm64": "4.60.3", - "@rollup/rollup-darwin-arm64": "4.60.3", - "@rollup/rollup-darwin-x64": "4.60.3", - "@rollup/rollup-freebsd-arm64": "4.60.3", - "@rollup/rollup-freebsd-x64": "4.60.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", - "@rollup/rollup-linux-arm-musleabihf": "4.60.3", - "@rollup/rollup-linux-arm64-gnu": "4.60.3", - "@rollup/rollup-linux-arm64-musl": "4.60.3", - "@rollup/rollup-linux-loong64-gnu": "4.60.3", - "@rollup/rollup-linux-loong64-musl": "4.60.3", - "@rollup/rollup-linux-ppc64-gnu": "4.60.3", - "@rollup/rollup-linux-ppc64-musl": "4.60.3", - "@rollup/rollup-linux-riscv64-gnu": "4.60.3", - "@rollup/rollup-linux-riscv64-musl": "4.60.3", - "@rollup/rollup-linux-s390x-gnu": "4.60.3", - "@rollup/rollup-linux-x64-gnu": "4.60.3", - "@rollup/rollup-linux-x64-musl": "4.60.3", - "@rollup/rollup-openbsd-x64": "4.60.3", - "@rollup/rollup-openharmony-arm64": "4.60.3", - "@rollup/rollup-win32-arm64-msvc": "4.60.3", - "@rollup/rollup-win32-ia32-msvc": "4.60.3", - "@rollup/rollup-win32-x64-gnu": "4.60.3", - "@rollup/rollup-win32-x64-msvc": "4.60.3", - "fsevents": "~2.3.2" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-compare": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz", - "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-alloc": "^1.2.0" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sandwich-stream": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", - "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", - "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.0.0.tgz", - "integrity": "sha512-pyp2YR3mNxAMu0mGLtzs4g7O3uT4/9sQOLAKcViAkaS9fJWkud7nmaf6ZREFqQEi24IPkBcjfHjXhPTUWjo3uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/socks/node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sqlite-vec": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec/-/sqlite-vec-0.1.9.tgz", - "integrity": "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA==", - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "optionalDependencies": { - "sqlite-vec-darwin-arm64": "0.1.9", - "sqlite-vec-darwin-x64": "0.1.9", - "sqlite-vec-linux-arm64": "0.1.9", - "sqlite-vec-linux-x64": "0.1.9", - "sqlite-vec-windows-x64": "0.1.9" - } - }, - "node_modules/sqlite-vec-darwin-arm64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-arm64/-/sqlite-vec-darwin-arm64-0.1.9.tgz", - "integrity": "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/sqlite-vec-darwin-x64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-x64/-/sqlite-vec-darwin-x64-0.1.9.tgz", - "integrity": "sha512-KDlVyqQT7pnOhU1ymB9gs7dMbSoVmKHitT+k1/xkjarcX8bBqPxWrGlK/R+C5WmWkfvWwyq5FfXfiBYCBs6PlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/sqlite-vec-linux-arm64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec-linux-arm64/-/sqlite-vec-linux-arm64-0.1.9.tgz", - "integrity": "sha512-5wXVJ9c9kR4CHm/wVqXb/R+XUHTdpZ4nWbPHlS+gc9qQFVHs92Km4bPnCKX4rtcPMzvNis+SIzMJR1SCEwpuUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/sqlite-vec-linux-x64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec-linux-x64/-/sqlite-vec-linux-x64-0.1.9.tgz", - "integrity": "sha512-w3tCH8xK2finW8fQJ/m8uqKodXUZ9KAuAar2UIhz4BHILfpE0WM/MTGCRfa7RjYbrYim5Luk3guvMOGI7T7JQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/sqlite-vec-windows-x64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/sqlite-vec-windows-x64/-/sqlite-vec-windows-x64-0.1.9.tgz", - "integrity": "sha512-y3gEIyy/17bq2QFPQOWLE68TYWcRZkBQVA2XLrTPHNTOp55xJi/BBBmOm40tVMDMjtP+Elpk6UBUXdaq+46b0Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", - "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/telegraf": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz", - "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@telegraf/types": "^7.1.0", - "abort-controller": "^3.0.0", - "debug": "^4.3.4", - "mri": "^1.2.0", - "node-fetch": "^2.7.0", - "p-timeout": "^4.1.0", - "safe-compare": "^1.1.4", - "sandwich-stream": "^2.0.2" - }, - "bin": { - "telegraf": "lib/cli.mjs" - }, - "engines": { - "node": "^12.20.0 || >=14.13.1" - } - }, - "node_modules/telegraf/node_modules/p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/test-exclude": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/test-exclude/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tokenjuice": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/tokenjuice/-/tokenjuice-0.7.0.tgz", - "integrity": "sha512-RZIyFmzztf/8V4q1cUS5L+q8UISMSfsjzh4UoWVxQbE7/zX91SfNmHpNqopqyB4oc5hwH4XqC9O/yakVzJCu8g==", - "dev": true, - "license": "MIT", - "bin": { - "tokenjuice": "dist/cli/main.js" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/vincentkoc" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tree-sitter-bash": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz", - "integrity": "sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.2.1", - "node-gyp-build": "^4.8.2" - }, - "peerDependencies": { - "tree-sitter": "^0.25.0" - }, - "peerDependenciesMeta": { - "tree-sitter": { - "optional": true - } - } - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tslog": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.10.2.tgz", - "integrity": "sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/fullstack-build/tslog?sponsor=1" - } - }, - "node_modules/tsscmp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", - "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.x" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typebox": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", - "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/uhyphen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", - "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", - "dev": true, - "license": "ISC" - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.2.0.tgz", - "integrity": "sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.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 - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/web-push": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.6.7.tgz", - "integrity": "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "asn1.js": "^5.3.0", - "http_ece": "1.2.0", - "https-proxy-agent": "^7.0.0", - "jws": "^4.0.0", - "minimist": "^1.2.5" - }, - "bin": { - "web-push": "src/cli.js" - }, - "engines": { - "node": ">= 16" - } - }, - "node_modules/web-push/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/web-push/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/web-tree-sitter": { - "version": "0.26.8", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.8.tgz", - "integrity": "sha512-4sUwi7ZyOrIk5KLgYLkc2A/F0LFMQnBhfb+2Cdl7ik4ePJ6JD+fk4ofI2sA5eGawBKBaK4Vntt7Ww5KcEsay4A==", - "dev": true, - "license": "MIT" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/openclaw-bridge/package.json b/openclaw-bridge/package.json deleted file mode 100644 index 8fd5d7c..0000000 --- a/openclaw-bridge/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "memstack-bridge", - "version": "1.4.3", - "type": "module", - "main": "dist/index.js", - "openclaw": { - "extensions": [ - "dist/index.js" - ] - }, - "scripts": { - "build": "tsc", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "lint": "tsc --noEmit" - }, - "peerDependencies": { - "openclaw": ">=2026.1.0" - }, - "devDependencies": { - "openclaw": "latest", - "@sinclair/typebox": "^0.32.0", - "typescript": "^5.4.0", - "vitest": "^3.1.0", - "@vitest/coverage-v8": "^3.1.0" - } -} diff --git a/openclaw-bridge/src/index.ts b/openclaw-bridge/src/index.ts deleted file mode 100644 index 176590f..0000000 --- a/openclaw-bridge/src/index.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import type { MemoryPluginCapability } from "openclaw/plugin-sdk"; - -const NATIVE_MEMORY_TOOLS = [ - "memory_search", - "memory_get", - "memory_add", - "memory_delete", - "memory_list", - "memory_flush", -]; - -const RECALL_TAG_OPEN = ""; -const RECALL_TAG_CLOSE = ""; -const RECALL_TAG_REGEX = /[\s\S]*?<\/memstack_recalled>/g; -const MAX_RECALL_CHARS = 1500; -const CACHE_TTL_MS = 30_000; -const CACHE_KEY_LEN = 500; - -function serializeContent(content: unknown): string { - if (content == null) return ""; - if (typeof content === "string") return content; - if (Array.isArray(content)) { - return content - .filter((block): block is Record => block != null) - .map((block) => { - if (block.type === "text") return String(block.text ?? ""); - if (block.type === "tool_use") - return `[tool: ${block.name ?? "unknown"}(${JSON.stringify(block.input ?? {})})]`; - if (block.type === "tool_result") - return `[result: ${serializeContent(block.content)}]`; - return String(block.text ?? block.content ?? ""); - }) - .filter((s) => s.length > 0) - .join("\n"); - } - return String(content); -} - -function identityHeaders( - agentId: string | undefined, - sessionKey?: string, -): Record { - return { - "X-MemStack-Agent-ID": agentId ?? "", - "X-MemStack-Session-ID": sessionKey ?? "", - }; -} - -export default definePluginEntry({ - id: "memstack-bridge-v1", - name: "MemStack Memory Bridge", - description: - "Routes all memory operations through MemStack, disabling native memory", - register(api) { - const cfg = (api.pluginConfig ?? {}) as Record; - const rest = cfg.restEndpoint ?? "http://localhost:7777"; - - const recallCache = new Map(); - function evictExpired(): void { - const now = Date.now(); - for (const [k, v] of recallCache) { - if (v.expires < now) recallCache.delete(k); - } - } - - const capability: MemoryPluginCapability = {}; - api.registerMemoryCapability(capability); - - api.on( - "before_prompt_build", - async ( - event: { prompt: string; messages: unknown[] }, - ctx: { agentId?: string; sessionKey?: string }, - ) => { - if (!ctx.agentId) return; - const query = event.prompt?.slice(0, CACHE_KEY_LEN) ?? ""; - if (!query) return; - - const cacheKey = `${ctx.agentId}:${query}`; - evictExpired(); - const cached = recallCache.get(cacheKey); - if (cached) return { prependContext: cached.value }; - - try { - const res = await fetch( - `${rest}/agents/${ctx.agentId}/memories/search?q=${encodeURIComponent(query)}&limit=5`, - { headers: identityHeaders(ctx.agentId, ctx.sessionKey) }, - ); - const data = await res.json(); - if (!data.length) return; - - const items = data.map((m: { chunk: string }) => `- ${m.chunk}`); - let block = `${RECALL_TAG_OPEN}\n${items.join("\n")}\n${RECALL_TAG_CLOSE}`; - - if (block.length > MAX_RECALL_CHARS) { - const truncated = items - .join("\n") - .slice( - 0, - MAX_RECALL_CHARS - - RECALL_TAG_OPEN.length - - RECALL_TAG_CLOSE.length - - 2, - ); - block = `${RECALL_TAG_OPEN}\n${truncated}\n${RECALL_TAG_CLOSE}`; - } - - recallCache.set(cacheKey, { - value: block, - expires: Date.now() + CACHE_TTL_MS, - }); - return { prependContext: block }; - } catch { - // MemStack unreachable — continue without recall - } - }, - ); - - api.on( - "agent_end", - async ( - event: { messages: unknown[]; success: boolean }, - ctx: { agentId?: string; sessionKey?: string }, - ) => { - if (!ctx.agentId) return; - const messages = event.messages as Array>; - const content = messages - .filter( - (m) => - m.role === "user" || m.role === "assistant" || m.role === "tool", - ) - .map((m) => ({ - role: String(m.role), - text: serializeContent(m.content), - })) - .filter((m) => m.text.length > 0) - .map((m) => `${m.role}: ${m.text}`) - .join("\n") - .replace(RECALL_TAG_REGEX, "") - .trim(); - - if (!content) return; - - try { - await fetch(`${rest}/agents/${ctx.agentId}/memories`, { - method: "POST", - headers: { - "Content-Type": "application/json", - ...identityHeaders(ctx.agentId, ctx.sessionKey), - }, - body: JSON.stringify({ content, tags: ["auto-capture"] }), - }); - } catch { - // Fire-and-forget — do not block turn completion - } - }, - ); - - api.on("before_tool_call", async (event: { toolName: string }) => { - if (NATIVE_MEMORY_TOOLS.includes(event.toolName)) { - return { - block: true, - blockReason: - "Native memory is disabled. Use MemStack MCP tools: memory_search, memory_write, memory_read.", - }; - } - }); - }, -}); diff --git a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/hook-types.ts b/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/hook-types.ts deleted file mode 100644 index 90db957..0000000 --- a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/hook-types.ts +++ /dev/null @@ -1,53 +0,0 @@ -export type PluginHookBeforePromptBuildEvent = { - prompt: string; - messages: unknown[]; -}; - -export type PluginHookBeforePromptBuildResult = { - systemPrompt?: string; - prependContext?: string; - appendContext?: string; - prependSystemContext?: string; - appendSystemContext?: string; -}; - -export type PluginHookAgentEndEvent = { - runId?: string; - messages: unknown[]; - success: boolean; - error?: string; - durationMs?: number; -}; - -export type PluginHookAgentContext = { - runId?: string; - jobId?: string; - agentId?: string; - sessionKey?: string; - sessionId?: string; - workspaceDir?: string; - modelProviderId?: string; - modelId?: string; -}; - -export type PluginHookBeforeToolCallEvent = { - toolName: string; - params: Record; - runId?: string; - toolCallId?: string; -}; - -export type PluginHookToolContext = { - agentId?: string; - sessionKey?: string; - sessionId?: string; - runId?: string; - toolName: string; - toolCallId?: string; -}; - -export type PluginHookBeforeToolCallResult = { - params?: Record; - block?: boolean; - blockReason?: string; -}; \ No newline at end of file diff --git a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/memory.ts b/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/memory.ts deleted file mode 100644 index 5e2fc2d..0000000 --- a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/memory.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type MemoryPluginCapability = { - promptBuilder?: unknown; - flushPlanResolver?: unknown; - runtime?: unknown; - publicArtifacts?: unknown; -}; - -export function registerMemoryCapability( - _pluginId: string, - capability: MemoryPluginCapability, -): void { - // Mock — no-op -} \ No newline at end of file diff --git a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/plugin-entry.ts b/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/plugin-entry.ts deleted file mode 100644 index aac7567..0000000 --- a/openclaw-bridge/tests/__mocks__/openclaw/plugin-sdk/plugin-entry.ts +++ /dev/null @@ -1,47 +0,0 @@ -type DefinePluginEntryOptions = { - id: string; - name: string; - description: string; - register(api: MockPluginApi): void; -}; - -type DefinedPluginEntry = DefinePluginEntryOptions; - -interface MockPluginApi { - id: string; - name: string; - pluginConfig?: Record; - config: Record; - registerMemoryCapability(capability: Record): void; - on(event: string, handler: (...args: unknown[]) => unknown): void; -} - -const registered: DefinedPluginEntry[] = []; - -export function definePluginEntry(options: DefinePluginEntryOptions): DefinedPluginEntry { - registered.push(options); - return options; -} - -export function getLastRegisteredPlugin(): DefinedPluginEntry | undefined { - return registered[registered.length - 1]; -} - -export function createMockApi(config?: Record): MockPluginApi { - const events = new Map unknown>>(); - const capabilities: Record[] = []; - - return { - id: "test-plugin", - name: "Test Plugin", - pluginConfig: config ?? { restEndpoint: "http://localhost:7777", mcpEndpoint: "http://localhost:7778" }, - config: {}, - registerMemoryCapability(cap: Record) { - capabilities.push(cap); - }, - on(event: string, handler: (...args: unknown[]) => unknown) { - if (!events.has(event)) events.set(event, []); - events.get(event)!.push(handler); - }, - } as MockPluginApi & { _events: typeof events; _capabilities: typeof capabilities }; -} \ No newline at end of file diff --git a/openclaw-bridge/tests/index.test.ts b/openclaw-bridge/tests/index.test.ts deleted file mode 100644 index 901c9a4..0000000 --- a/openclaw-bridge/tests/index.test.ts +++ /dev/null @@ -1,701 +0,0 @@ -import { describe, it, expect, vi, beforeEach, type Mock } from "vitest"; - -vi.mock("openclaw/plugin-sdk/plugin-entry", () => ({ - definePluginEntry: (entry: { register(api: MockPluginApi): void }) => entry, -})); - -vi.mock("openclaw/plugin-sdk/memory", () => ({ - registerMemoryCapability: (cap: Record) => cap, -})); - -vi.mock("openclaw/plugin-sdk/hook-types", () => ({ - // Type-only import — runtime is irrelevant -})); - -type MockPluginApi = { - id: string; - name: string; - pluginConfig?: Record; - config: Record; - registerMemoryCapability(cap: Record): void; - on(event: string, handler: (...args: unknown[]) => unknown): void; -}; - -function createMockApi(config?: Record): MockPluginApi & { - _events: Map unknown>>; - _capabilities: Record[]; -} { - const events = new Map unknown>>(); - const capabilities: Record[] = []; - - return { - id: "test-plugin", - name: "Test Plugin", - pluginConfig: config ?? { - restEndpoint: "http://localhost:7777", - mcpEndpoint: "http://localhost:7778", - }, - config: {}, - registerMemoryCapability(cap: Record) { - capabilities.push(cap); - }, - on(event: string, handler: (...args: unknown[]) => unknown) { - if (!events.has(event)) events.set(event, []); - events.get(event)!.push(handler); - }, - _events: events, - _capabilities: capabilities, - }; -} - -async function loadPlugin() { - const mod = await import("../src/index.js"); - return mod.default; -} - -describe("MemStack Bridge Plugin", () => { - let mockFetch: Mock; - - beforeEach(() => { - mockFetch = vi.fn(); - globalThis.fetch = mockFetch; - }); - - async function registerPlugin(config?: Record) { - const plugin = await loadPlugin(); - const api = createMockApi(config); - plugin.register(api); - return { plugin, api }; - } - - describe("plugin entry", () => { - it("has correct id and name", async () => { - const plugin = await loadPlugin(); - expect(plugin.id).toBe("memstack-bridge-v1"); - expect(plugin.name).toBe("MemStack Memory Bridge"); - }); - }); - - describe("memory capability registration", () => { - it("registers a memory capability to claim the memory slot", async () => { - const { api } = await registerPlugin(); - expect(api._capabilities).toHaveLength(1); - }); - }); - - describe("before_prompt_build hook", () => { - it("queries MemStack and returns prependContext", async () => { - mockFetch.mockResolvedValue({ - json: () => - Promise.resolve([ - { chunk: "Deployed v2 to production" }, - { chunk: "Meeting notes from standup" }, - ]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result = await handlers[0]( - { prompt: "What did we deploy recently?", messages: [] }, - { agentId: "main", sessionKey: "agent:main:chan:user1" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [url] = mockFetch.mock.calls[0] as [string]; - expect(url).toContain("/agents/main/memories/search"); - expect(url).toContain("q=What%20did%20we%20deploy%20recently"); - - const typed = result as { prependContext: string }; - expect(typed.prependContext).toContain(""); - expect(typed.prependContext).toContain("Deployed v2 to production"); - expect(typed.prependContext).toContain(""); - }); - - it("handles MemStack unreachable gracefully", async () => { - mockFetch.mockRejectedValue(new Error("Connection refused")); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result = await handlers[0]( - { prompt: "What did we deploy?", messages: [] }, - { agentId: "main" }, - ); - - expect(result).toBeUndefined(); - }); - - it("returns undefined when prompt is empty", async () => { - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result = await handlers[0]( - { prompt: "", messages: [] }, - { agentId: "main" }, - ); - - expect(result).toBeUndefined(); - expect(mockFetch).not.toHaveBeenCalled(); - }); - - it("returns undefined when MemStack returns empty results", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result = await handlers[0]( - { prompt: "obscure query", messages: [] }, - { agentId: "main" }, - ); - - expect(result).toBeUndefined(); - }); - - it("truncates context when it exceeds 1500 characters", async () => { - const longChunk = "A".repeat(600); - mockFetch.mockResolvedValue({ - json: () => - Promise.resolve([ - { chunk: longChunk }, - { chunk: longChunk }, - { chunk: longChunk }, - ]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result = await handlers[0]( - { prompt: "test query", messages: [] }, - { agentId: "main" }, - ); - - const typed = result as { prependContext: string }; - expect(typed.prependContext.length).toBeLessThanOrEqual(1500); - }); - }); - - describe("agent_end hook", () => { - it("extracts exchange, strips memstack_recalled, and POSTs", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: "What did we deploy?" }, - { role: "assistant", content: "We deployed v2 to production." }, - { - role: "user", - content: - "\n- Deployed v2\n\nAny updates?", - }, - { role: "assistant", content: "No new updates since v2." }, - ], - success: true, - }, - { agentId: "main", sessionKey: "agent:main:chan:user1" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - expect(url).toBe("http://localhost:7777/agents/main/memories"); - expect(init.method).toBe("POST"); - expect(init.headers["X-MemStack-Agent-ID"]).toBe("main"); - expect(init.headers["X-MemStack-Session-ID"]).toBe("agent:main:chan:user1"); - - const body = JSON.parse(init.body as string); - expect(body.tags).toEqual(["auto-capture"]); - expect(body.content).not.toContain(""); - expect(body.content).toContain("What did we deploy?"); - expect(body.content).toContain("We deployed v2 to production."); - expect(body.content).toContain("Any updates?"); - expect(body.content).toContain("No new updates since v2."); - }); - - it("does nothing when messages are empty", async () => { - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]({ messages: [], success: true }, { agentId: "main" }); - - expect(mockFetch).not.toHaveBeenCalled(); - }); - - it("does nothing when all messages are system role", async () => { - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [{ role: "system", content: "You are helpful." }], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).not.toHaveBeenCalled(); - }); - - it("includes tool role messages in auto-capture", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: "Run the tests" }, - { - role: "assistant", - content: [ - { type: "text", text: "I'll run the tests now." }, - { - type: "tool_use", - name: "run_tests", - input: { suite: "all" }, - }, - ], - }, - { - role: "tool", - content: [ - { - type: "tool_result", - content: "All 42 tests passed", - }, - ], - }, - { role: "assistant", content: "All tests passed." }, - ], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).toContain("[tool: run_tests"); - expect(body.content).toContain("[result: All 42 tests passed]"); - expect(body.content).toContain("All 42 tests passed"); - }); - - it("captures all messages without truncation", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - const messages = Array.from({ length: 10 }, (_, i) => ({ - role: i % 2 === 0 ? "user" : "assistant", - content: `Message ${i + 1}`, - })); - - await handlers[0]({ messages, success: true }, { agentId: "main" }); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).toContain("Message 1"); - expect(body.content).toContain("Message 10"); - }); - - it("excludes system messages from auto-capture", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "system", content: "You are helpful." }, - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi there" }, - ], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).not.toContain("You are helpful"); - expect(body.content).toContain("Hello"); - expect(body.content).toContain("Hi there"); - }); - - it("serializes structured content blocks", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: "Check the server" }, - { - role: "assistant", - content: [ - { type: "text", text: "Let me check." }, - { - type: "tool_use", - name: "ssh_run", - input: { command: "uptime" }, - }, - ], - }, - { - role: "tool", - content: [{ type: "tool_result", content: "up 42 days" }], - }, - { - role: "assistant", - content: "Server has been up 42 days.", - }, - ], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).toContain("Let me check."); - expect(body.content).toContain("[tool: ssh_run("); - expect(body.content).toContain('"command":"uptime"'); - expect(body.content).toContain("[result: up 42 days]"); - expect(body.content).toContain("Server has been up 42 days."); - }); - - it("serializes nested tool_result content arrays", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { - role: "tool", - content: [ - { - type: "tool_result", - content: [ - { type: "text", text: "Line 1" }, - { type: "text", text: "Line 2" }, - ], - }, - ], - }, - ], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).toContain("Line 1"); - expect(body.content).toContain("Line 2"); - expect(body.content).not.toContain("[object Object]"); - }); - - it("handles null and undefined content gracefully", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: null }, - { role: "assistant", content: "Hello" }, - { role: "user", content: undefined }, - ], - success: true, - }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.content).toContain("assistant: Hello"); - expect(body.content).not.toContain("null"); - expect(body.content).not.toContain("undefined"); - }); - - it("handles fetch failure silently (fire-and-forget)", async () => { - mockFetch.mockRejectedValue(new Error("Connection refused")); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi there" }, - ], - success: true, - }, - { agentId: "main" }, - ); - // Should not throw - }); - }); - - describe("before_tool_call hook", () => { - it("blocks all 6 native memory tools", async () => { - const { api } = await registerPlugin(); - const handlers = api._events.get("before_tool_call")!; - - const nativeTools = [ - "memory_search", - "memory_get", - "memory_add", - "memory_delete", - "memory_list", - "memory_flush", - ]; - - for (const toolName of nativeTools) { - const result = (await handlers[0]( - { toolName, params: {} }, - { agentId: "main", toolName }, - )) as { block: boolean; blockReason: string }; - expect(result.block).toBe(true); - expect(result.blockReason).toContain("Native memory is disabled"); - } - }); - - it("passes non-memory tools through", async () => { - const { api } = await registerPlugin(); - const handlers = api._events.get("before_tool_call")!; - - const result = await handlers[0]( - { toolName: "web_search", params: {} }, - { agentId: "main", toolName: "web_search" }, - ); - - expect(result).toBeUndefined(); - }); - }); - - describe("custom config", () => { - it("uses restEndpoint from plugin config", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([{ id: "mem-1", chunk: "test" }]), - }); - - const { api } = await registerPlugin({ - restEndpoint: "http://custom:9000", - mcpEndpoint: "http://custom:9001", - }); - - const handlers = api._events.get("before_prompt_build")!; - - await handlers[0]( - { prompt: "test query", messages: [] }, - { agentId: "main" }, - ); - - const [url] = mockFetch.mock.calls[0] as [string]; - expect(url).toContain("http://custom:9000"); - }); - - it("defaults to localhost:7777 when pluginConfig is undefined", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([{ id: "mem-1", chunk: "test" }]), - }); - - const plugin = await loadPlugin(); - const api = createMockApi(); - // @ts-expect-error — testing undefined pluginConfig - api.pluginConfig = undefined; - plugin.register(api); - - const handlers = api._events.get("before_prompt_build")!; - await handlers[0]( - { prompt: "test query", messages: [] }, - { agentId: "main" }, - ); - - const [url] = mockFetch.mock.calls[0] as [string]; - expect(url).toContain("http://localhost:7777"); - }); - - it("defaults to localhost:7777 when pluginConfig has no restEndpoint", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([{ id: "mem-1", chunk: "test" }]), - }); - - const plugin = await loadPlugin(); - const api = createMockApi({} as Record); - plugin.register(api); - - const handlers = api._events.get("before_prompt_build")!; - await handlers[0]( - { prompt: "test query", messages: [] }, - { agentId: "main" }, - ); - - const [url] = mockFetch.mock.calls[0] as [string]; - expect(url).toContain("http://localhost:7777"); - }); - }); - - describe("identity headers with undefined agentId", () => { - it("does not call MemStack when agentId is undefined", async () => { - mockFetch.mockResolvedValue({ ok: true }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("agent_end")!; - - await handlers[0]( - { - messages: [ - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi" }, - ], - success: true, - }, - {}, - ); - - expect(mockFetch).not.toHaveBeenCalled(); - }); - }); - - describe("before_prompt_build cache", () => { - afterEach(() => { - vi.useRealTimers(); - }); - - it("returns cached result on second identical prompt within 30s", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([{ chunk: "Deployed v2 to production" }]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result1 = await handlers[0]( - { prompt: "What did we deploy?", messages: [] }, - { agentId: "main" }, - ); - expect(mockFetch).toHaveBeenCalledTimes(1); - - const result2 = await handlers[0]( - { prompt: "What did we deploy?", messages: [] }, - { agentId: "main" }, - ); - expect(mockFetch).toHaveBeenCalledTimes(1); - - expect((result2 as { prependContext: string }).prependContext).toBe( - (result1 as { prependContext: string }).prependContext, - ); - }); - - it("fetches fresh results for different prompts", async () => { - mockFetch - .mockResolvedValueOnce({ - json: () => Promise.resolve([{ chunk: "Alpha result" }]), - }) - .mockResolvedValueOnce({ - json: () => Promise.resolve([{ chunk: "Beta result" }]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const result1 = await handlers[0]( - { prompt: "query alpha", messages: [] }, - { agentId: "main" }, - ); - const result2 = await handlers[0]( - { prompt: "query beta", messages: [] }, - { agentId: "main" }, - ); - - expect(mockFetch).toHaveBeenCalledTimes(2); - expect((result1 as { prependContext: string }).prependContext).toContain( - "Alpha result", - ); - expect((result2 as { prependContext: string }).prependContext).toContain( - "Beta result", - ); - }); - - it("fetches fresh result after cache expiry", async () => { - vi.useFakeTimers(); - mockFetch - .mockResolvedValueOnce({ - json: () => Promise.resolve([{ chunk: "Stale data" }]), - }) - .mockResolvedValueOnce({ - json: () => Promise.resolve([{ chunk: "Fresh data" }]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - await handlers[0]( - { prompt: "cached query", messages: [] }, - { agentId: "main" }, - ); - expect(mockFetch).toHaveBeenCalledTimes(1); - - vi.advanceTimersByTime(31_000); - - const result = await handlers[0]( - { prompt: "cached query", messages: [] }, - { agentId: "main" }, - ); - expect(mockFetch).toHaveBeenCalledTimes(2); - expect((result as { prependContext: string }).prependContext).toContain( - "Fresh data", - ); - }); - - it("uses first 500 chars as cache key", async () => { - mockFetch.mockResolvedValue({ - json: () => Promise.resolve([{ chunk: "Shared prefix result" }]), - }); - - const { api } = await registerPlugin(); - const handlers = api._events.get("before_prompt_build")!; - - const shared = "a".repeat(500); - const promptA = shared + "suffix_A"; - const promptB = shared + "suffix_B"; - - await handlers[0]({ prompt: promptA, messages: [] }, { agentId: "main" }); - expect(mockFetch).toHaveBeenCalledTimes(1); - - const result = await handlers[0]( - { prompt: promptB, messages: [] }, - { agentId: "main" }, - ); - expect(mockFetch).toHaveBeenCalledTimes(1); - expect((result as { prependContext: string }).prependContext).toContain( - "Shared prefix result", - ); - }); - }); -}); diff --git a/openclaw-bridge/tsconfig.json b/openclaw-bridge/tsconfig.json deleted file mode 100644 index 1140bb6..0000000 --- a/openclaw-bridge/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "outDir": "dist", - "rootDir": "src", - "declaration": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src"], - "exclude": ["node_modules", "dist", "tests"] -} diff --git a/openclaw-bridge/vitest.config.ts b/openclaw-bridge/vitest.config.ts deleted file mode 100644 index 18d842c..0000000 --- a/openclaw-bridge/vitest.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { defineConfig } from "vitest/config"; -import path from "path"; - -export default defineConfig({ - resolve: { - alias: { - "openclaw/plugin-sdk/plugin-entry": path.resolve( - __dirname, - "tests/__mocks__/openclaw/plugin-sdk/plugin-entry.ts", - ), - "openclaw/plugin-sdk/memory": path.resolve( - __dirname, - "tests/__mocks__/openclaw/plugin-sdk/memory.ts", - ), - "openclaw/plugin-sdk/hook-types": path.resolve( - __dirname, - "tests/__mocks__/openclaw/plugin-sdk/hook-types.ts", - ), - }, - }, - test: { - globals: true, - coverage: { - provider: "v8", - reporter: ["text", "json", "html"], - include: ["src/**/*.ts"], - thresholds: { - branches: 90, - functions: 90, - lines: 90, - statements: 90, - }, - }, - }, -}); diff --git a/progress.md b/progress.md new file mode 100644 index 0000000..c5706e5 --- /dev/null +++ b/progress.md @@ -0,0 +1,90 @@ +# Progress — feat/integration v1.5.0 + +## 2026-05-17 + +### Pivot: Remove OpenClaw bridge, switch to MCP-native connection + +- Deleted `openclaw-bridge/` directory (TypeScript plugin with 3 lifecycle hooks: before_prompt_build, agent_end, before_tool_call) +- Deleted `.claude/specs/auto-capture.md` (bridge auto-capture spec) +- Deleted `vault/memstack/openclaw/` (empty vault directory) +- Rewrote `docs/connect-openclaw.md` for MCP-only connection with explicit `memory_write` + `tags: ["auto-capture"]` workflow +- Updated `README.md`: removed Node.js prereq, updated integrations section, updated synthesis description +- Updated `CONTRIBUTING.md`: removed bridge dev setup, TypeScript test section, bridge from project structure and scopes +- Updated `.gitignore`: removed `openclaw-bridge/dist/` entry +- Cleaned bridge out-of-scope lines from `.claude/specs/consolidation.md`, `thresholds.md`, `synthesis.md` +- The agent now calls memory tools explicitly via MCP — no hooks, no auto-capture, no auto-recall +- MemStack server code is unchanged + +## 2026-05-15 + +### /start-int integration polish + +- Squash-merged PR #7 (feat/embedding-config) — removed autofallback, changed defaults to fastembed/BAAI/bge-small-en-v1.5 +- Merged PR #8 (feat/dimension-validation) with conflict resolution on STATUS.md and progress.md +- Conflicts were in continuity docs only; no code conflicts +- Fixed stale references in README.md (defaults, AUTOFALLBACK row, troubleshooting), ARCHITECTURE.md (autofallback mentions), CHANGELOG.md (added embedding-config and dimension-validation entries under v1.4.5) +- Polisher: no changes needed, code already clean +- Reviewer: PASS — 0 P1 findings, 6 P2s (all deferrable pre-existing issues) +- Verifier: PASS — 596 tests, lint clean, format clean, zero TODOs, version consistent + +### Changes from PR #7 (feat/embedding-config) + +- Removed `embedding_autofallback: bool = True` from `src/memstack/core/config.py` +- Changed `embedding_provider` default from `"ollama"` to `"fastembed"`, `embedding_model` default from `"nomic-embed-text"` to `"BAAI/bge-small-en-v1.5"` +- Rewrote `create_embedding_provider()`: removed autofallback logic, both provider branches raise RuntimeError when unavailable +- Removed `MEMSTACK_EMBEDDING_AUTOFALLBACK` block from `.env.example` +- Removed 2 autofallback tests, renamed 1 test, updated RuntimeError match strings + +### Changes from PR #8 (feat/dimension-validation) + +- Added `validate_dimension()` method to SearchIndex: checks existing LanceDB table vector dimension against current embedding provider; on mismatch, logs warning and triggers full reindex +- Added startup calls in `app.py` and `server.py` +- Added 5 tests in `TestValidateDimension` class + +## 2026-05-14 + +### /start-int integration polish (v1.4.5) + +- Squash-merged PR #4 (feat/config-fixes) — BOM fix + obsidian_mode removal +- Squash-merged PR #3 (feat/version-assertions) — replaced 6 hardcoded version strings with `__version__` +- Manually merged PR #5 (feat/lifespan-refactor) — GitHub had merge conflicts on CHANGELOG.md; resolved locally +- Resolved CHANGELOG.md conflict: combined config-fixes, version-assertions, and lifespan-refactor entries into single v1.4.5 section +- Fixed 3 ruff format issues (index.py, test_async_execution.py, test_synthesis.py) +- Bumped version from 1.4.4 to 1.4.5 in `__init__.py`, `pyproject.toml`, `.env.example` +- Regenerated README.md from scratch: glossary, all-platform instructions, full API reference, e2e walkthrough, troubleshooting, known warnings + +### /finish documentation regeneration + +- Created ARCHITECTURE.md — internal architecture extracted from README +- Created CONTRIBUTING.md — dev setup, test/lint commands, project structure +- Rewrote README.md as pure end-user guide +- Added v1.4.4 entry to CHANGELOG.md covering synthesis and consolidation + +## 2026-05-12 + +### PR merges — v1.4.4 + +- PR #42 feat/synthesis — merged via squash +- PR #43 feat/consolidation — merged with conflict resolution + +### Conflict resolution notes + +- `config.py`: Combined synthesis fields and consolidation fields from both branches +- `__init__.py`: Combined docstring and exports from both branches +- `test_config.py`: Combined synthesis tests and consolidation tests from both branches +- `.env.example`: Combined synthesis and consolidation env vars from both branches + +## 2026-05-09 + +### PR merges — v1.4.3 + +- PR #38 feat/thresholds — merged via squash +- PR #39 feat/auto-capture — merged with conflict resolution +- PR #40 feat/merge-pipeline — merged with conflict resolution + +### Conflict resolution notes + +- `config.py`: Took pipeline logic from merge-pipeline with threshold defaults from thresholds +- `test_config.py`: Combined similarity_ignore_enabled assertions from both branches +- `test_pipeline.py`: Kept expanded `_make_settings` from merge-pipeline with threshold defaults +- `CHANGELOG.md`: Combined v1.4.3 entries from auto-capture and merge-pipeline diff --git a/pyproject.toml b/pyproject.toml index 99b8134..e6954ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "memstack" -version = "1.4.4" +version = "1.4.5" description = "A local-first memory server for AI agents" license = "MIT" requires-python = ">=3.11" diff --git a/reference/MemStack_Architecture.md b/reference/MemStack_Architecture.md new file mode 100644 index 0000000..a14e642 --- /dev/null +++ b/reference/MemStack_Architecture.md @@ -0,0 +1,122 @@ +# MemStack — Architecture + +## What it is +A local-first memory server for AI agents. Agents read and write memories via MCP or REST. Every memory is a markdown file in a user-owned vault. The search index is always derived from the vault and always rebuildable. + +## Hard constraints +- Local only. No cloud, no external services required. +- Vault is ground truth. The index is disposable. +- Compatible with Linux, macOS, native Windows, and WSL2. +- MCP and REST interfaces both supported. +- All components independently testable. +- MemStack always decides where memories go. Agents never choose. + +## Core stack +- **Language:** Python 3.11 minimum +- **Package manager:** uv +- **REST framework:** FastAPI +- **Search store:** LanceDB (vector + keyword + hybrid, single store) +- **Embeddings:** Ollama (default), fastembed (fallback) — single env toggle +- **File watching:** watchfiles +- **CLI:** Typer +- **Configuration:** Pydantic Settings, `MEMSTACK_` prefix +- **MCP:** FastMCP +- **Logging:** Loguru +- **Testing:** pytest + hypothesis + pytest-asyncio + pytest-cov +- **Data validation:** Pydantic v2 +- **Markdown parsing:** python-frontmatter + regex boundary detection + +## What it must do +- Accept memory writes from agents via MCP and REST +- Decide intelligently whether to add, update, or ignore each write +- Store memories as markdown files with YAML frontmatter +- Index memories for hybrid search (semantic + keyword) via LanceDB +- Rank results using RRF fused with importance score +- Watch the vault for user edits and sync the index automatically +- Inject relevant memories into agent context on demand (top-N by score) +- Support per-agent private memory and shared memory across agents +- Run setup, start, stop, status, rebuild, and prune from the CLI +- Full test coverage with eval gate from v1.1 onwards + +## Structure +``` +memstack/ + core/ — vault I/O, memory models, config + search/ — LanceDB integration, hybrid search, ranking + intelligence/ — smart writes, importance scoring, chunking + interfaces/ — FastAPI REST, FastMCP server, file watcher + cli/ — Typer CLI commands +tests/ +evals/ +setup.sh +pyproject.toml +.env.example +system_prompt_kit.md +``` + +## Memory file +Each memory is a markdown file with YAML frontmatter carrying identity, agent ownership, type, importance score, tags, and timestamps. Body is the memory content. Human-readable and human-editable. Obsidian-compatible when `MEMSTACK_OBSIDIAN_MODE=true`. + +## Filename format +``` +slug-agentid-YYYY-MM-DD.md +{slug}-{agent_id}-{YYYY-MM-DD}.md +``` + +## Vault layout +``` +vault/ + memstack/ + shared/ — all memories copied here when shared mode is on + {agent_id}/ — private per agent, always written to regardless of shared mode +``` + +## Shared memory model +- Write: always to agent's private directory first, then copy to shared if `MEMSTACK_SHARED_MODE=true` +- Read: from shared directory when shared mode is on (all agents see all memories), from private directory when off +- Turning shared mode off never deletes existing copies + +## Search and ranking +1. LanceDB hybrid search returns semantic and keyword ranked lists +2. RRF fuses the two lists into one +3. Importance score re-weights the final ranking +4. Top-N results returned to agent + +## Smart write pipeline +1. Run semantic similarity check against existing memories +2. If similarity is below add threshold → add immediately +3. If similarity is above ignore threshold → ignore immediately +4. If similarity is in ambiguous middle range → consult LLM to decide + +## Importance scoring +- Initial score set at write time +- Score increases on every retrieval hit +- Score decays on a configurable half-life +- Prune command uses score to identify low-value memories + +## Key configuration variables +``` +MEMSTACK_VAULT_PATH= # required, no default +MEMSTACK_OBSIDIAN_MODE=true +MEMSTACK_SHARED_MODE=false +MEMSTACK_EMBEDDING_PROVIDER=ollama +MEMSTACK_EMBEDDING_MODEL=nomic-embed-text +MEMSTACK_INJECTION_TOP_N=5 +MEMSTACK_INJECTION_MIN_SCORE=0.3 +MEMSTACK_SIMILARITY_ADD_THRESHOLD=0.3 +MEMSTACK_SIMILARITY_IGNORE_THRESHOLD=0.92 +MEMSTACK_IMPORTANCE_DECAY_HALFLIFE=7 +MEMSTACK_IMPORTANCE_INITIAL_SCORE=0.5 +MEMSTACK_IMPORTANCE_RERANK_WEIGHT=0.2 +MEMSTACK_RRF_K=60 +MEMSTACK_CHUNK_MAX_TOKENS=512 +MEMSTACK_CHUNK_OVERLAP_TOKENS=50 +MEMSTACK_PRUNE_MIN_IMPORTANCE=0.1 +MEMSTACK_RETRY_MAX_ATTEMPTS=3 +MEMSTACK_RETRY_BACKOFF_BASE=2 +MEMSTACK_LOG_LEVEL=INFO +MEMSTACK_LOG_FILE=~/.memstack/logs/memstack.log +MEMSTACK_LOG_ROTATION=10 MB +MEMSTACK_LOG_RETENTION=7 days +MEMSTACK_STATE_FILE=~/.memstack/state.json +``` diff --git a/reference/MemStack_Design.md b/reference/MemStack_Design.md new file mode 100644 index 0000000..ebe97c4 --- /dev/null +++ b/reference/MemStack_Design.md @@ -0,0 +1,111 @@ +# MemStack — Design Document + +--- + +## What is MemStack? + +A local-first memory server for AI agents. Agents read and write memories through MCP or REST. Every memory is a markdown file in a vault the user owns. The vault is always ground truth — the search index is derived from it and always rebuildable. + +--- + +## The Core Idea + +Most agent memory systems are black boxes. You cannot see what your agent remembers, you cannot edit it, and your data lives on someone else's server. + +MemStack is different. Every memory your agent writes is a markdown file sitting in a folder on your machine. Open it in Obsidian, read it, edit it, delete it. The agent reflects your changes automatically. Your memory is yours. + +--- + +## The Core Loop + +``` +Agent submits a memory +→ MemStack decides whether to add, update, or ignore it +→ MemStack decides where it goes (private, shared, or both) +→ Markdown file written to vault +→ Search index updated + +User edits a file in Obsidian +→ MemStack detects the change via file watcher +→ Search index updated automatically + +Agent queries memory +→ Hybrid search returns results ranked by relevance + importance +→ Results injected into agent context +``` + +--- + +## Guiding Principles + +- **Local only, always.** Privacy is the product. No cloud, no external services required. +- **Vault is ground truth.** The index is disposable and always rebuildable. +- **MemStack decides, agents submit.** Agents write content. MemStack decides whether to store it, where to put it, and how to rank it. Agents never control routing or deduplication. +- **Retrieval only.** Agents bring their own LLM. +- **Simple over clever.** Every feature must justify its complexity. +- **No regressions.** Every version from v1.1 onwards must pass the eval gate before shipping. +- **Slow progression.** Small, stable versions over large, risky jumps. + +--- + +## Why Competitors Lose to MemStack + +**mem0** — Cloud-managed, opaque, paid. You cannot see or edit what agents remember. + +**Zep** — Complex setup, cloud-first, not built for solo developers. + +**basic-memory** — Local markdown, closest to MemStack. But no smart write management, no importance scoring, no multi-agent namespacing. + +**LangMem** — Framework-specific, cloud-dependent, no human visibility. + +MemStack is the only system that is simultaneously local, intelligent, and human-readable. + +--- + +## Key Features + +**Vault as ground truth** — Delete the index and rebuild it anytime from vault files alone. Nothing is lost. + +**Obsidian-compatible** — When Obsidian mode is on, memory files use full Obsidian-flavored markdown: wiki-links, inline tags, callouts, Dataview-compatible frontmatter. Toggle off for clean standard markdown. + +**Smart writes** — Before writing, MemStack checks similarity against existing memories. Clear cases are handled by rule. Ambiguous cases consult an LLM. No blind appends, no bloat. + +**Importance scoring** — Every memory has an importance score. It starts at a sensible default, increases on every retrieval hit, and decays over a configurable half-life. Frequently accessed recent memories rank higher automatically. + +**Shared and private memory** — Each agent always writes to its own private directory. When shared mode is on, MemStack copies memories to the shared directory and all agents read from there. Turning shared mode off never deletes existing copies. + +**Hybrid search** — LanceDB handles both semantic and keyword search in a single store. Results are fused via RRF and re-ranked by importance score. + +**Auto-injection** — A single endpoint returns the top-N most relevant memories for the current agent context. Orchestrators call it before every LLM call. + +**File watching** — MemStack watches the vault for user edits via watchfiles. Index stays in sync automatically without manual intervention. + +**Eval gate** — From v1.1 onwards, every version must beat or match the previous retrieval quality baseline before shipping. Numbers are committed and versioned. + +--- + +## Versioned Roadmap + +### v1.0 — Foundation +Vault structure, markdown read/write, memory file format, basic REST API, Pydantic models, configuration, logging. A working vault with no search yet. + +### v1.1 — Search + Evals +LanceDB integration, semantic search, keyword search, hybrid RRF ranking, importance score re-ranking. Eval runner, golden set, and quality baseline established. All future versions measured against this baseline. + +### v1.2 — Intelligence +Smart write pipeline (rule-based + LLM on ambiguity), importance scoring with access-decay, semantic chunking. Eval gate must pass. + +### v1.3 — Interfaces +FastMCP server, file watcher with watchfiles, startup incremental vault scan, auto-injection endpoint. Eval gate must pass. + +### v1.4 — Auto-injection + Memory Routing +Inject endpoint, top-N retrieval, shared/private memory routing, mirror writes. Eval gate must pass. + +### v1.5 — CLI + Ops +Full Typer CLI, guided setup script with validation gate, rebuild command with diff report, prune command with dry run, health check endpoint, system_prompt_kit.md. Eval gate must pass. + +### v1.6 — Graph + Multi-vault +Wiki-link graph surfaces related memories during retrieval. Multi-vault support. Memory timeline endpoint. Eval gate must pass. + +### v1.7 — Ingestion +PDF ingestion, URL ingestion, image and diagram ingestion via vision model. All ingested content stored as markdown in the vault — visible and editable in Obsidian. Eval gate must pass. diff --git a/reference/branch-plan.md b/reference/branch-plan.md new file mode 100644 index 0000000..00eb781 --- /dev/null +++ b/reference/branch-plan.md @@ -0,0 +1,68 @@ +# Branch Plan — v1.4.5 Reactive Summit + +## Summary + +| # | Branch | Scope | Complexity | +|---|--------|-------|------------| +| 0 | feat/integration | Merge hub — review and merge all PRs, resolve conflicts, update CHANGELOG for v1.4.5 changes | — | +| 1 | feat/embedding-config | Remove autofallback + default to fastembed | Small | +| 2 | feat/dimension-validation | Dimension validation + auto-reindex at startup | Medium | + +All branches are cut from feat/integration and are fully independent. Dependencies resolve at merge time. + +--- + +## feat/integration + +- **Scope**: Merge hub — review PRs from feat/embedding-config and feat/dimension-validation, resolve conflicts, update CHANGELOG.md with v1.4.5 section documenting autofallback removal, fastembed default, dimension validation, verify all tests pass, ruff lint/format clean +- **Out of scope**: Any implementation work +- **Acceptance criteria**: Both branches merged, `python -m pytest tests/ -v` passes, `ruff check src/ tests/` clean, `ruff format --check src/ tests/` clean, `git grep autofallback -- src/ tests/ .env.example` returns zero, `git grep embedding_autofallback -- src/ tests/` returns zero + +--- + +## feat/embedding-config + +- **Scope**: + - `src/memstack/core/config.py` — remove `embedding_autofallback: bool = True` (line 37), change `embedding_provider` default from `"ollama"` to `"fastembed"` (line 35), change `embedding_model` default from `"nomic-embed-text"` to `"BAAI/bge-small-en-v1.5"` (line 36) + - `src/memstack/search/embeddings.py` — rewrite `create_embedding_provider()`: remove `autofallback` parameter, remove fallback branch (lines 134-151), update `getattr` defaults (lines 125-126) to `"fastembed"` and `"BAAI/bge-small-en-v1.5"`, update docstring. If configured provider is unavailable, raise RuntimeError immediately with clear message naming the provider and how to fix it. + - `.env.example` — remove `MEMSTACK_EMBEDDING_AUTOFALLBACK` block (lines 51-53), update provider/model defaults and comments (lines 43-49) + - `tests/test_embeddings.py` — remove `test_falls_back_to_fastembed_when_ollama_unavailable` (lines 118-127), remove `test_raises_when_both_providers_unavailable` (lines 147-156), rename `test_raises_when_ollama_unavailable_and_no_autofallback` to `test_raises_when_ollama_unavailable`, remove `embedding_autofallback` from all Settings() calls +- **Out of scope**: Dimension validation, SearchIndex changes, REST API, MCP, watcher, pipeline, vault, version bump, CHANGELOG +- **Acceptance criteria**: + - `git grep autofallback -- src/ tests/ .env.example` returns zero + - `git grep embedding_autofallback -- src/ tests/` returns zero + - Default provider is fastembed with model `BAAI/bge-small-en-v1.5` + - Ollama unavailable → RuntimeError raised, no silent fallback + - All existing tests pass with test updates + - Zero lint errors, zero format issues + +--- + +## feat/dimension-validation + +- **Scope**: + - `src/memstack/search/index.py` — add `validate_dimension()` method to SearchIndex: check if existing table's vector dimension matches current provider dimension; if mismatch, log warning and call `self.reindex()` + - `src/memstack/interfaces/rest/app.py` — call `search_index.validate_dimension()` after SearchIndex creation (after line 78) + - `src/memstack/interfaces/mcp/server.py` — call `search_index.validate_dimension()` after SearchIndex creation (after line 46) + - `tests/test_search_index.py` — add 4 tests: `test_validate_dimension_no_table`, `test_validate_dimension_matching`, `test_validate_dimension_mismatch_triggers_reindex`, `test_validate_dimension_no_provider` +- **Out of scope**: Embedding config changes, .env.example, config.py defaults, autofallback removal, version bump, CHANGELOG +- **Acceptance criteria**: + - Dimension mismatch at startup triggers auto-reindex from vault with warning log + - Matching dimensions → no reindex, no warning + - No table yet → skip validation gracefully + - No embedding provider → skip validation gracefully + - All 4 new tests pass + - All existing tests pass + - Zero lint errors, zero format issues + +--- + +## Expected Merge Conflicts + +| File | Conflicting Branches | Nature | +|------|---------------------|--------| +| `src/memstack/search/index.py` | feat/dimension-validation only | No conflict — only one branch touches it | +| `src/memstack/interfaces/rest/app.py` | feat/dimension-validation only | No conflict — only one branch touches it | +| `src/memstack/interfaces/mcp/server.py` | feat/dimension-validation only | No conflict — only one branch touches it | + +No merge conflicts expected — the two branches touch completely separate files. \ No newline at end of file diff --git a/reference/v1_0_architecture.md b/reference/v1_0_architecture.md new file mode 100644 index 0000000..43c703a --- /dev/null +++ b/reference/v1_0_architecture.md @@ -0,0 +1,132 @@ +# MemStack v1.0 — Foundation + +## Goal +A working vault. No search yet. No intelligence yet. Just a solid, well-structured base that every future version builds on top of without modification. + +--- + +## What it must deliver + +### Configuration +- Single `Settings` class using Pydantic Settings +- All variables prefixed with `MEMSTACK_` +- `MEMSTACK_VAULT_PATH` is required — MemStack refuses to start without it +- `.env.example` documents every variable with inline comments +- Clear error message on startup if any required variable is missing + +### Vault structure +- On first start, create the vault directory structure if it does not exist +- Structure: + ``` + {MEMSTACK_VAULT_PATH}/ + memstack/ + shared/ + {agent_id}/ — created on first write from that agent + ``` + +### Memory file format +Two modes controlled by `MEMSTACK_OBSIDIAN_MODE`: + +**Obsidian mode (true):** +``` +--- +id: slug-agentid-YYYY-MM-DD +agent: {agent_id} +type: memory +importance: 0.5 +tags: [tag1, tag2] +created: ISO timestamp +updated: ISO timestamp +--- + +Memory body content here. + +Related: [[other-memory-slug]] +``` + +**Standard mode (false):** +``` +--- +id: slug-agentid-YYYY-MM-DD +agent: {agent_id} +type: memory +importance: 0.5 +tags: [tag1, tag2] +created: ISO timestamp +updated: ISO timestamp +--- + +Memory body content here. +``` + +### Memory models +- A `Memory` Pydantic model covering all frontmatter fields plus body +- A `MemoryWrite` model for incoming write requests (agent submits content and optional tags) +- A `MemoryRead` model for outgoing read responses +- No raw dicts crossing between components + +### Vault I/O +- Write a memory file to the correct agent directory +- Read a memory file by ID +- Delete a memory file by ID +- List all memory files for a given agent +- Parse frontmatter and body cleanly using python-frontmatter +- Never modify the body during read/write — only MemStack-generated files are written, never partial edits + +### Slug generation +- Generated from the first meaningful line of the memory body +- Lowercased, whitespace replaced with hyphens, special characters stripped +- Format: `{slug}-{agent_id}-{YYYY-MM-DD}` +- Stored in frontmatter as `id` + +### REST API +Base URL: `http://localhost:7777` + +Endpoints: +``` +POST /agents/{agent_id}/memories — write a new memory +GET /agents/{agent_id}/memories — list all memories for agent +GET /agents/{agent_id}/memories/{id} — read a single memory +DELETE /agents/{agent_id}/memories/{id} — delete a memory +GET /health — server status +``` + +Health check response: +```json +{ + "status": "healthy", + "version": "1.0.0", + "components": { + "vault": "healthy" + } +} +``` + +### Logging +- Loguru for all logging +- Log file at `MEMSTACK_LOG_FILE` +- Automatic rotation at `MEMSTACK_LOG_ROTATION` +- Automatic retention at `MEMSTACK_LOG_RETENTION` +- Console output during development + +### Error handling +- Missing vault path on startup → fail immediately with clear message +- File write failure → fail loud, log error +- Invalid memory format on read → log warning, skip file +- No silent failures anywhere + +### Testing +- Full pytest coverage for all vault I/O operations +- Full coverage for all REST endpoints +- Full coverage for slug generation and memory model validation +- Minimum 90% coverage enforced via pytest-cov + +--- + +## Constraints +- No search in this version +- No smart writes in this version +- No file watcher in this version +- No MCP in this version +- No CLI beyond `memstack start` and `memstack stop` in this version +- Vault is the only dependency — MemStack v1.0 must work with no other services running diff --git a/reference/v1_1_architecture.md b/reference/v1_1_architecture.md new file mode 100644 index 0000000..4720f44 --- /dev/null +++ b/reference/v1_1_architecture.md @@ -0,0 +1,93 @@ +# MemStack v1.1 — Search + Evals + +## Goal +Make the vault searchable. Establish the retrieval quality baseline that every future version must beat or match before shipping. + +--- + +## What it must deliver + +### LanceDB integration +- LanceDB as the single store for both vector and keyword search +- One table: `memories` +- Each row stores: memory ID, agent ID, chunk text, chunk index, embedding vector, importance score, created timestamp, updated timestamp, tags +- Index is always rebuildable from vault files alone — it is disposable +- On startup, build the index from vault if it does not exist + +### Embedding pipeline +- Provider controlled by `MEMSTACK_EMBEDDING_PROVIDER` +- **Ollama (default):** connect to local Ollama instance, use `MEMSTACK_EMBEDDING_MODEL` +- **fastembed (fallback):** run in-process, no separate server needed +- If Ollama is selected but unreachable at startup: log a clear warning, auto-fallback to fastembed if `MEMSTACK_EMBEDDING_AUTOFALLBACK=true`, otherwise fail loud +- Embedding provider is fully swappable with no changes outside of `.env` + +### Semantic chunking +- Split memory body at paragraph and sentence boundaries +- Hard ceiling of `MEMSTACK_CHUNK_MAX_TOKENS` tokens per chunk +- Overlap of `MEMSTACK_CHUNK_OVERLAP_TOKENS` tokens between adjacent chunks +- Chunking happens in-memory only — the vault file on disk is never modified +- Each chunk is indexed as a separate row in LanceDB with its chunk index recorded + +### Hybrid search +- LanceDB built-in hybrid search combining semantic and keyword results +- RRF fusion of the two ranked lists using `MEMSTACK_RRF_K` +- Importance score re-weighting of the final fused list using `MEMSTACK_IMPORTANCE_RERANK_WEIGHT` +- Results returned as ranked list of memory IDs with scores + +### REST API additions +``` +GET /agents/{agent_id}/memories/search?q={query}&limit={n} — search agent's private memories +GET /shared/memories/search?q={query}&limit={n} — search shared memories +``` + +Search response per result: +```json +{ + "id": "memory-slug", + "score": 0.87, + "importance": 0.5, + "chunk": "relevant chunk text", + "agent": "{agent_id}", + "created": "ISO timestamp" +} +``` + +### Health check update +```json +{ + "status": "healthy", + "version": "1.1.0", + "components": { + "vault": "healthy", + "lancedb": "healthy", + "embeddings": "healthy" + } +} +``` + +### Eval system +- A golden set of query-memory pairs built from real indexed vault content +- Eval runner that measures: + - Retrieval precision at N (P@N) + - Retrieval recall at N (R@N) + - Mean reciprocal rank (MRR) + - Average query latency in milliseconds +- A chunking parameter sweep across `MEMSTACK_CHUNK_MAX_TOKENS` and `MEMSTACK_CHUNK_OVERLAP_TOKENS` values to find optimal settings +- Results committed to `evals/results/v1.1.json` and versioned +- Minimum quality bar defined — all future versions must meet or exceed it + +### Testing +- Full coverage for LanceDB integration +- Full coverage for embedding pipeline and provider switching +- Full coverage for chunking logic — hypothesis used to generate edge case inputs +- Full coverage for hybrid search and RRF ranking +- Full coverage for new search endpoints +- Minimum 90% coverage enforced + +--- + +## Constraints +- No smart writes in this version — all writes go straight to vault and index +- No file watcher in this version +- No MCP in this version +- Eval gate applies from v1.2 onwards — v1.1 establishes the baseline, it does not have to beat anything diff --git a/reference/v1_2_architecture.md b/reference/v1_2_architecture.md new file mode 100644 index 0000000..0f1ea68 --- /dev/null +++ b/reference/v1_2_architecture.md @@ -0,0 +1,83 @@ +# MemStack v1.2 — Intelligence + +## Goal +Make MemStack smart about what it stores. No blind appends. No bloat. Every memory in the vault earns its place. + +--- + +## What it must deliver + +### Smart write pipeline +Every incoming memory write passes through this pipeline before touching the vault: + +``` +1. Embed the incoming memory content +2. Run semantic similarity search against existing memories for this agent +3. Evaluate the highest similarity score: + — Below MEMSTACK_SIMILARITY_ADD_THRESHOLD → add as new memory immediately + — Above MEMSTACK_SIMILARITY_IGNORE_THRESHOLD → ignore, return "duplicate" response + — Between the two thresholds → consult LLM +4. LLM decision (ambiguous zone only): + — Add → write as new memory + — Update → overwrite the most similar existing memory + — Ignore → discard silently +5. Write to vault and update index +``` + +### LLM consultation (ambiguous zone) +- LLM provider is whatever the user has configured for embeddings (Ollama by default) +- LLM receives: the incoming memory content + the top-3 most similar existing memories +- LLM returns one of three decisions: add, update, ignore +- LLM call is logged with the decision and similarity score for debuggability +- If LLM is unreachable: fall back to rule-based add (err on the side of keeping data) + +### Importance scoring +- Every memory is assigned `MEMSTACK_IMPORTANCE_INITIAL_SCORE` at write time +- Score increases by a fixed increment on every retrieval hit +- Score decays passively on a half-life of `MEMSTACK_IMPORTANCE_DECAY_HALFLIFE` days +- Decay is computed lazily at read time — no background job needed +- Importance score is stored in both the vault file frontmatter and the LanceDB index +- When a memory is updated via smart write, its importance score is preserved from the previous version + +### Importance score update flow +``` +Memory retrieved by search +→ Compute decay since last access +→ Apply retrieval hit increment +→ Update score in LanceDB row +→ Update score in vault file frontmatter +→ Return memory to caller +``` + +### REST API additions +No new endpoints in this version. The write endpoint (`POST /agents/{agent_id}/memories`) now passes through the smart write pipeline automatically. Response includes the pipeline decision: + +```json +{ + "decision": "added", + "id": "{slug}-{agent_id}-2024-01-15", + "similarity_score": 0.21 +} +``` + +Possible decision values: `added`, `updated`, `ignored` + +### Testing +- Full coverage for smart write pipeline — all three similarity zones +- Hypothesis tests for edge cases in similarity threshold boundaries +- Full coverage for LLM consultation path and fallback behavior +- Full coverage for importance score decay calculation +- Full coverage for importance score update on retrieval +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.2 +- Retrieval quality must meet or exceed v1.1 baseline +- Results committed to `evals/results/v1.2.json` + +--- + +## Constraints +- No file watcher in this version +- No MCP in this version +- Smart write pipeline applies to REST writes only in this version — MCP writes get it in v1.3 diff --git a/reference/v1_3_architecture.md b/reference/v1_3_architecture.md new file mode 100644 index 0000000..09be53f --- /dev/null +++ b/reference/v1_3_architecture.md @@ -0,0 +1,85 @@ +# MemStack v1.3 — Interfaces + +## Goal +Add the MCP server and file watcher. Agents can now use MemStack natively via MCP. The vault stays in sync with user edits automatically. + +--- + +## What it must deliver + +### FastMCP server +- MCP server implemented using FastMCP +- Runs alongside the FastAPI REST server as a separate process +- All MCP tools pass through the same smart write pipeline as REST writes +- Agent ID passed via MCP tool arguments — no separate auth needed + +MCP tools: +``` +memory_write — submit a memory for smart write processing +memory_search — hybrid search across agent or shared memories +memory_read — read a single memory by ID +memory_delete — delete a memory by ID +memory_list — list all memories for the calling agent +``` + +Each tool has: +- A clear description agents use to know when to call it +- Typed input parameters via Pydantic +- Typed response + +### system_prompt_kit.md (first draft) +- Behavioral rules for agents: when to write, what is worth remembering, what to never write, how to write effectively, how to search effectively +- MCP tool reference: every tool, its parameters, its response format, one example call per tool +- Kept short and strict — written for agents to read, not humans +- Lives at project root, versioned alongside the code + +### File watcher +- watchfiles monitors `MEMSTACK_VAULT_PATH/memstack/` recursively +- Runs as a background task within the FastAPI process +- Events handled: + - **File created:** parse frontmatter and body, add to LanceDB index + - **File modified:** re-parse, update LanceDB row + - **File deleted:** remove from LanceDB index +- Watcher only processes `.md` files +- Watcher ignores files it cannot parse — logs a warning and moves on +- Watcher does not trigger smart write pipeline — user edits go straight to index, no deduplication applied + +### Startup vault scan +- On every startup, compare each vault file's `mtime` against the last recorded indexed timestamp in `MEMSTACK_STATE_FILE` +- Files newer than last index timestamp are reindexed +- Files unchanged are skipped +- State file updated after scan completes +- Scan runs before the file watcher starts, so no events are missed + +### Health check update +```json +{ + "status": "healthy", + "version": "1.3.0", + "components": { + "vault": "healthy", + "lancedb": "healthy", + "embeddings": "healthy", + "watcher": "healthy", + "mcp": "healthy" + } +} +``` + +### Testing +- Full coverage for all MCP tools +- Full coverage for file watcher event handling — created, modified, deleted +- Full coverage for startup vault scan and state file management +- Full coverage for watcher skipping non-markdown files and unparseable files +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.3 +- Retrieval quality must meet or exceed v1.1 baseline +- Results committed to `evals/results/v1.3.json` + +--- + +## Constraints +- No auto-injection endpoint in this version — that ships in v1.4 +- No CLI beyond start/stop in this version — full CLI ships in v1.5 diff --git a/reference/v1_4_1_architecture.md b/reference/v1_4_1_architecture.md new file mode 100644 index 0000000..c523aeb --- /dev/null +++ b/reference/v1_4_1_architecture.md @@ -0,0 +1,555 @@ +# MemStack v1.4.1 — OpenClaw Integration + Enforced Memory Routing + +## Goal +Make MemStack the actual memory engine for OpenClaw — not a suggestion, not an optional tool the agent can choose to call. Every memory read and write that OpenClaw performs goes through MemStack. Native memory is dead. This is achieved through a native OpenClaw bridge plugin that claims the exclusive memory slot and hooks into the framework's own lifecycle, combined with config keys that shut down every native memory pathway. + +--- + +## The Problem + +v1.4 registers MemStack as an MCP server and adds the inject endpoint. What it does not do is make OpenClaw actually use MemStack. OpenClaw has five internal memory components that operate at the framework level, below the agent loop: the memory search pipeline, the compaction flush, the active-memory sub-agent, the dreaming scheduler, and the memory slot itself. An external MCP server has no visibility into any of these. The agent sees MemStack tools in its catalog but the framework is still writing memories to its own Markdown files, running its own embeddings, and injecting its own recalled context into every prompt — all without asking the agent. + +Config keys alone are also not enough. Disabling the four native components stops the framework from using its own memory, but it does not tell the framework to use MemStack instead. The internal recall signals and compaction events just fail silently. The agent has no memory at all. + +The correct fix is a native OpenClaw plugin that claims `plugins.slots.memory` — the exclusive slot the framework uses to route all memory operations. Once a plugin owns that slot, the framework calls it directly for search, write, recall injection, and compaction handling. The agent no longer decides anything about memory. The framework enforces it. + +--- + +## What it must deliver + +### New configuration variable + +``` +MEMSTACK_MCP_PORT=7778 — port the FastMCP server listens on (default: 7778, REST stays on 7777) +``` + +Implied by v1.3 but never formally defined. Required here because the bridge plugin config and connection guide reference it explicitly. + +--- + +### The MemStack Bridge Plugin + +This is the core deliverable of v1.4.1. It is a native TypeScript OpenClaw plugin that ships as part of the MemStack project under `openclaw-bridge/` at the project root, alongside `memstack/`, `tests/`, and `evals/`. Users install it once into their OpenClaw extensions directory. It is not published to npm — it is a local plugin installed by path. + +#### Location in the MemStack project + +``` +memstack/ — Python server (existing) +tests/ — Python tests (existing) +evals/ — Eval runner (existing) +openclaw-bridge/ — NEW: TypeScript OpenClaw plugin + package.json + openclaw.plugin.json + src/ + index.ts + dist/ — compiled output, gitignored +``` + +#### `openclaw.plugin.json` + +The manifest that OpenClaw reads during the discovery phase. The `id` must exactly match the value placed in `plugins.slots.memory` in `openclaw.json`. The `kind: "memory"` field makes the plugin eligible for the exclusive memory slot. + +```json +{ + "id": "memstack-bridge-v1", + "name": "MemStack Memory Bridge", + "kind": "memory", + "version": "1.4.1", + "configSchema": { + "restEndpoint": { + "type": "string", + "default": "http://localhost:7777", + "description": "MemStack REST API base URL" + }, + "mcpEndpoint": { + "type": "string", + "default": "http://localhost:7778", + "description": "MemStack FastMCP server base URL" + } + } +} +``` + +#### `package.json` + +```json +{ + "name": "memstack-bridge", + "version": "1.4.1", + "type": "module", + "main": "dist/index.js", + "openclaw": { + "extensions": ["dist/index.js"] + }, + "scripts": { + "build": "tsc" + }, + "peerDependencies": { + "openclaw": ">=2026.1.0" + }, + "devDependencies": { + "openclaw": "latest", + "@sinclair/typebox": "^0.32.0", + "typescript": "^5.4.0" + } +} +``` + +#### `src/index.ts` — what the plugin implements + +The plugin exports a `definePluginEntry` wrapper with a `register(api)` function. Inside `register`, it does five things: + +**1. Register the memory capability** + +Claims the memory slot. The `runtime` object implements `MemorySearchManager` — the interface OpenClaw calls for all internal search and write operations. Every call forwards to MemStack's REST API on port 7777. Agent ID and session key from the hook context are passed as headers on every request to partition memories correctly. + +```typescript +import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; +import { registerMemoryCapability } from "openclaw/plugin-sdk/memory"; + +export default definePluginEntry({ + register(api) { + const cfg = api.pluginConfig; + const rest = cfg.restEndpoint ?? "http://localhost:7777"; + + api.registerMemoryCapability({ + runtime: { + search: async (query, opts) => { + const res = await fetch(`${rest}/agents/${opts.agentId}/memories/search?q=${encodeURIComponent(query)}&limit=${opts.limit ?? 5}`, { + headers: { + "X-MemStack-Agent-ID": opts.agentId, + "X-MemStack-Session-ID": opts.sessionKey ?? "" + } + }); + return res.json(); + }, + write: async (content, opts) => { + const res = await fetch(`${rest}/agents/${opts.agentId}/memories`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-MemStack-Agent-ID": opts.agentId, + "X-MemStack-Session-ID": opts.sessionKey ?? "" + }, + body: JSON.stringify({ content, tags: opts.tags ?? [] }) + }); + return res.json(); + } + }, + + // Routes compaction flush to MemStack instead of native Markdown files. + // Without this, context compaction silently writes to native storage + // even when memorySearch is disabled. + flushPlanResolver: async (params) => { + await fetch(`${rest}/agents/${params.agentId}/memories`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-MemStack-Agent-ID": params.agentId + }, + body: JSON.stringify({ content: params.flushContent, tags: ["compaction-flush"] }) + }); + return { handled: true }; + } + }); +``` + +**2. `before_prompt_build` hook — Auto-Recall** + +Fires before every LLM call. Queries MemStack for memories relevant to the current user message and injects them into the prompt via `prependContext`. The agent receives recalled memories as part of every prompt automatically — it does not choose to search. + +```typescript + api.on("before_prompt_build", async (event, ctx) => { + const query = event.prompt?.slice(0, 500) ?? ""; + if (!query) return; + + try { + const res = await fetch( + `${rest}/agents/${ctx.agentId}/memories/search?q=${encodeURIComponent(query)}&limit=5`, + { + headers: { + "X-MemStack-Agent-ID": ctx.agentId, + "X-MemStack-Session-ID": ctx.sessionKey ?? "" + } + } + ); + const data = await res.json(); + if (!data.results?.length) return; + + const block = data.results + .map((m: { chunk: string }) => `- ${m.chunk}`) + .join("\\n"); + + return { + prependContext: `\\n${block}\\n` + }; + } catch { + // MemStack unreachable — continue without recall rather than blocking the turn + } + }); +``` + +Cap is ~1500 characters of injected context to preserve the session token budget. The `` wrapper prevents small models from treating recalled memories as user instructions. + +**3. `agent_end` hook — Auto-Capture** + +Fires at the end of every agent turn. Extracts the most recent user/assistant exchange from `event.messagesSnapshot`, strips any `` blocks to prevent recursive pollution, and POSTs the turn to MemStack for storage and smart write processing. + +```typescript + api.on("agent_end", async (event, ctx) => { + const messages = event.messagesSnapshot ?? []; + const recent = messages.slice(-4); // last user + assistant exchange + const content = recent + .filter((m: { role: string }) => m.role === "user" || m.role === "assistant") + .map((m: { role: string; content: string }) => `${m.role}: ${m.content}`) + .join("\\n") + .replace(/[\\s\\S]*?<\\/memstack_recalled>/g, "") + .trim(); + + if (!content) return; + + try { + await fetch(`${rest}/agents/${ctx.agentId}/memories`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-MemStack-Agent-ID": ctx.agentId, + "X-MemStack-Session-ID": ctx.sessionKey ?? "" + }, + body: JSON.stringify({ content, tags: ["auto-capture"] }) + }); + } catch { + // Fire-and-forget — do not block turn completion + } + }); +``` + +**4. `before_tool_call` hook — Native memory blocking** + +Intercepts any attempt by the agent to call OpenClaw's native memory tools. Returns `{ block: true }` to prevent execution at the framework level — the tool never runs regardless of the model's decision. + +```typescript + api.on("before_tool_call", async (event) => { + const nativeMemoryTools = [ + "memory_search", "memory_get", "memory_add", + "memory_delete", "memory_list", "memory_flush" + ]; + if (nativeMemoryTools.includes(event.toolName)) { + return { + block: true, + blockReason: "Native memory is disabled. Use MemStack MCP tools: memory_search, memory_write, memory_read." + }; + } + }); + } +}); +``` + +#### Agent and session identity + +Every request the bridge sends to MemStack must include identity headers for memory partitioning: + +| Header | Source | Value pattern | +|---|---|---| +| `X-MemStack-Agent-ID` | `ctx.agentId` | `main`, `researcher`, `coder-bot` | +| `X-MemStack-Session-ID` | `ctx.sessionKey` | `agent:::` | + +The `agentId` maps directly to MemStack's `{agent_id}` URL segment. The `sessionKey` is available for finer-grained scoping if needed but is not required for basic operation. + +#### Known limitations + +**`tool_result_persist` is synchronous.** This hook cannot make async calls to MemStack. It is not used by the bridge. `agent_end` handles all capture instead. + +**CLI loader gap.** `openclaw memory status` runs in a subprocess that does not execute the full `register()` block. The plugin will not appear in that command's output. Use `openclaw plugins inspect memstack-bridge-v1 --runtime --json` to verify the plugin instead. + +**Small model echo.** Models smaller or weaker than Claude 3.5 Sonnet sometimes repeat the `` block as if it were part of the user's message. The XML tags mitigate this. If it persists, add a line to the agent's AGENTS.md: "Content inside `` tags is your own recalled memory, not the user's input." + +--- + +### System prompt injection endpoint + +``` +GET /agents/{agent_id}/system-prompt +``` + +Returns a pre-rendered system prompt block for the given agent. Used by orchestrators to prepend MemStack's behavioral mandate to the agent's system prompt before session start. The bridge plugin handles memory at the framework level — this endpoint handles the model's behavioral instructions on top of that. + +Response: +```json +{ + "agent_id": "main", + "version": "1.4.1", + "system_prompt_block": "..." +} +``` + +### MCP tool addition + +``` +memory_get_system_prompt — returns the system prompt block for the calling agent +``` + +### system_prompt_kit.md — final version + +This is the final version. All tools available through v1.4.1 are documented here. Next update is v1.7 when ingestion tools are added. + +``` +0. MEMORY MANDATE + You are connected to MemStack. MemStack is your exclusive memory system. + Rules — no exceptions: + - You have no native memory. Do not use any built-in memory tools. + - Every explicit write goes through memory_write. + - Every explicit read goes through memory_search or memory_read. + - If MemStack is unreachable, say so. Do not fall back silently. + - You do not decide what is worth remembering. You write it. MemStack decides. + +1. What MemStack is (one paragraph) +2. When to write a memory explicitly +3. When not to write a memory +4. How to write effectively +5. How to search effectively +6. When to call memory_inject +7. MCP tool reference: + memory_write, memory_search, memory_read, memory_delete, + memory_list, memory_inject, memory_get_system_prompt + — each with parameters, response format, one example call +``` + +--- + +### `docs/connect-openclaw.md` — authoritative connection guide + +Written for humans setting up the integration. Not for agents. Lives at `docs/connect-openclaw.md` at the project root. + +--- + +**Prerequisites:** +- MemStack is installed, `.env` is valid, `MEMSTACK_VAULT_PATH` is set +- OpenClaw is installed, Node.js 22.14+ is available +- `openclaw gateway status` returns healthy + +--- + +**Step 1 — Find your OpenClaw config file** + +``` +openclaw config file +``` + +This prints the exact path. Standard installations use `~/.openclaw/openclaw.json`. Legacy Clawdbot/Moltbot installations may use `.clawdbot/` or `.config/openclaw/`. Use whatever path this command returns for all subsequent steps. + +**Step 2 — Start MemStack** + +``` +memstack start +``` + +Confirm both servers are up: +``` +curl http://localhost:7777/health # REST +curl http://localhost:7778/health # MCP +``` + +If either fails, check `MEMSTACK_LOG_FILE` before continuing. + +**Step 3 — Build and install the bridge plugin** + +From the MemStack project root: +``` +cd openclaw-bridge +npm install +npm run build +openclaw plugins install . +``` + +The last command registers the plugin with OpenClaw by path. It does not copy files — it records the path and loads from it on each gateway start. + +**Step 4 — Update `openclaw.json`** + +Open the file from Step 1 and apply the complete configuration block below. This does four things: disables all four native memory pathways, claims the memory slot for the bridge plugin, configures the plugin with MemStack's endpoints, and registers MemStack as an MCP server. + +```json +{ + "agents": { + "defaults": { + "memorySearch": { + "enabled": false + }, + "compaction": { + "memoryFlush": { + "enabled": false + } + } + } + }, + "plugins": { + "slots": { + "memory": "memstack-bridge-v1" + }, + "entries": { + "active-memory": { + "enabled": false + }, + "memory-core": { + "config": { + "dreaming": { + "enabled": false + } + } + }, + "memstack-bridge-v1": { + "enabled": true, + "config": { + "restEndpoint": "http://localhost:7777", + "mcpEndpoint": "http://localhost:7778" + } + } + } + }, + "mcp": { + "servers": { + "memstack": { + "url": "http://localhost:7778", + "transport": "streamable-http" + } + } + } +} +``` + +What each section does: +- `memorySearch.enabled: false` — disables the vector search and retrieval pipeline +- `compaction.memoryFlush.enabled: false` — stops the framework writing to native Markdown during context compaction. Without this, compaction events silently bypass the bridge plugin. +- `plugins.entries.active-memory.enabled: false` — shuts down the proactive recall sub-agent that runs its own LLM turn before the main response +- `plugins.entries.memory-core.config.dreaming.enabled: false` — stops the background knowledge consolidation scheduler +- `plugins.slots.memory: "memstack-bridge-v1"` — claims the exclusive slot. The value must match the `id` in `openclaw.plugin.json` exactly. +- `plugins.entries.memstack-bridge-v1` — activates the plugin and passes endpoint config +- `mcp.servers.memstack` — registers MemStack MCP tools in the agent's tool catalog for explicit agent use + +**Step 5 — Restart the gateway** + +A standard restart is sufficient in most cases: +``` +openclaw gateway restart +``` + +If you are on macOS with launchd or Linux with systemd and config changes do not appear to apply, use the full reset sequence: +``` +openclaw gateway stop +openclaw doctor --fix +openclaw gateway install --force +openclaw gateway start +``` + +**Step 6 — Verify the bridge plugin is active** + +``` +openclaw plugins inspect memstack-bridge-v1 --runtime --json +``` + +The output must show the plugin's registered memory capability and all three hooks (`before_prompt_build`, `agent_end`, `before_tool_call`). If the plugin is not listed, the path from Step 3 was not recorded correctly — re-run `openclaw plugins install .` from the `openclaw-bridge/` directory. + +Do not use `openclaw memory status` to verify — it runs in a subprocess that does not load the full plugin registry. + +**Step 7 — Verify MemStack MCP tools are visible** + +``` +openclaw mcp list memstack --schema +``` + +Expected: +``` +memory_write +memory_search +memory_read +memory_delete +memory_list +memory_inject +memory_get_system_prompt +``` + +If empty, MemStack's MCP server is not reachable. Confirm `memstack start` is running and `MEMSTACK_MCP_PORT` matches the port in `openclaw.json`. + +**Step 8 — Inject the system prompt** + +Fetch the mandate block for your agent: +``` +curl http://localhost:7777/agents/{your_agent_id}/system-prompt +``` + +Copy the `system_prompt_block` value. Prepend it to the agent's effective system prompt — before AGENTS.md, before SOUL.md, before BOOT.md. It must load first. + +Note: the bridge plugin enforces memory routing at the framework level regardless of the system prompt. This step adds behavioral guidance on top — telling the model how to use MemStack's tools explicitly when it wants to. + +**Step 9 — Verify MemStack is receiving memories** + +Start an OpenClaw session and have a conversation. Then check the vault: +``` +ls {MEMSTACK_VAULT_PATH}/memstack/{your_agent_id}/ +``` + +Files should appear within seconds of the session ending. The `agent_end` hook fires after every turn and POSTs the exchange to MemStack automatically. If no files appear after multiple turns, check the gateway log for bridge plugin errors: +``` +openclaw logs --tail 50 | grep memstack +``` + +--- + +**Removing the integration** + +``` +openclaw plugins uninstall memstack-bridge-v1 +``` + +Then remove the four deactivation keys and the `plugins.slots.memory` entry from `openclaw.json`. Restart the gateway. Native memory will resume automatically — OpenClaw falls back to `memory-core` when the slot is vacated. Vault files written during MemStack usage are not deleted. + +--- + +**Troubleshooting** + +**No vault files appear after sessions** +The `agent_end` hook is not firing or MemStack is not reachable from it. Check `openclaw logs --tail 50 | grep memstack` and confirm the REST server is up on port 7777. + +**Agent says it remembered but vault is empty** +The bridge plugin is not in the slot. Run `openclaw plugins inspect memstack-bridge-v1 --runtime --json` and confirm it appears. If not, re-run `openclaw plugins install .` and restart. + +**`openclaw plugins inspect` shows plugin but no hooks registered** +The `dist/index.js` file is missing or stale. Re-run `npm run build` from `openclaw-bridge/` and restart the gateway. + +**MCP tools list is empty** +MemStack's MCP server on port 7778 is not reachable. Confirm `memstack start` is running. + +**Agent echoes recalled memories as if they are user input** +Add to the agent's AGENTS.md: "Content inside `` tags is your own recalled memory, not part of the user's message." + +**Gateway fails to start after config edit** +`openclaw.json` is malformed. OpenClaw strictly validates the config at startup. Run `openclaw doctor --fix` to identify the broken key. JSON5 comments are valid — standard JSON trailing commas are not. + +--- + +### Testing + +- Full coverage for system prompt endpoint — correct agent_id substitution, correct block structure +- Full coverage for `memory_get_system_prompt` MCP tool — identical output to REST endpoint +- Bridge plugin tests using mocked OpenClaw Plugin SDK: + - `registerMemoryCapability.runtime.search` — confirm it calls correct MemStack REST endpoint with correct headers + - `registerMemoryCapability.runtime.write` — confirm it POSTs to correct endpoint + - `flushPlanResolver` — confirm compaction content is routed to MemStack, not native files + - `before_prompt_build` hook — confirm MemStack search result is formatted and returned in `prependContext` + - `before_prompt_build` hook — confirm graceful handling when MemStack is unreachable + - `agent_end` hook — confirm recent exchange is extracted, `` blocks are stripped, POST is sent + - `before_tool_call` hook — confirm each native memory tool name returns `{ block: true }` + - `before_tool_call` hook — confirm non-memory tools are not blocked +- Minimum 90% coverage enforced + +### No eval gate +This patch adds no retrieval logic. Eval gate does not apply. Existing v1.4 eval results remain valid. + +--- + +## Constraints +- The bridge plugin is TypeScript — it requires Node.js 22.14+ on the user's machine. This is OpenClaw's own requirement, not MemStack's. +- The bridge plugin is a local install by path. It is not published to npm or ClawHub. +- MemStack cannot verify from its side whether the bridge plugin is installed or active. Verification is done via `openclaw plugins inspect`. +- `MEMSTACK_MCP_PORT` default is 7778. If changed in `.env`, the same port must be used in `openclaw.json` under `mcp.servers.memstack.url`. +- No changes to vault, index, search logic, or existing v1.4 endpoints. \ No newline at end of file diff --git a/reference/v1_4_2_architecture.md b/reference/v1_4_2_architecture.md new file mode 100644 index 0000000..0a41b22 --- /dev/null +++ b/reference/v1_4_2_architecture.md @@ -0,0 +1,115 @@ +# MemStack v1.4.2 — Speed Optimization & System Prompt Fix + +## Goal +Fix the performance and behavioral regressions that make MemStack unusable at speed with OpenClaw. Search latency averages 19 seconds. Every search triggers 10–30 synchronous file writes for importance updates. Embeddings are regenerated per query with zero caching. The FTS index rebuilds after every single write. And the system prompt tells the model to call `memory_inject` before every response — causing OpenClaw agents to narrate memory operations in every message instead of responding naturally. This version fixes all of these without adding new features. + +--- + +## What it must deliver + +### Async execution for all sync handlers + +All FastAPI async handlers call synchronous code directly — search index operations, vault I/O, embedding generation, LLM consultation, LanceDB writes. This blocks uvicorn's event loop for the duration of each operation, causing 19-second search latency under any concurrency. + +Every sync call must be wrapped in `asyncio.get_event_loop().run_in_executor(None, ...)` so blocking operations run in the default thread pool instead of on the event loop. This applies to all REST endpoint handlers and all MCP tool functions. MCP tools are currently sync `def` functions — each must become `async def` that delegates sync work to the executor. + +No behavioral change. All existing tests pass transparently because TestClient drives the event loop synchronously. + +### Ollama client singleton + +`consult_llm()` creates a new `ollama.Client()` instance on every call. This adds connection overhead to every ambiguous write decision. The client is stateless beyond the host URL and safe to reuse. + +Replace with a module-level singleton matching the pattern already used in `OllamaProvider._get_client()`. One client instance, reused across all calls. + +### Embedding cache on provider instances + +Every `search()` and `find_similar()` call regenerates the query embedding from scratch — network call for Ollama, CPU computation for fastembed. Same query text always produces the same vector. + +Add an in-memory LRU cache on each embedding provider instance, keyed by input text. Deterministic embeddings guarantee cache correctness. Cache is process-local and lost on restart, which is acceptable. + +Configuration: +``` +MEMSTACK_EMBEDDING_CACHE_SIZE=1024 — max cached embeddings (default 1024, ~1.5MB) +``` + +### Defer importance updates to background tasks + +Every search result triggers synchronous `vault.update_importance()` + `search_index.update_importance()`. With the default `limit=10`, a single search causes 20–30 blocking file I/O operations plus 10 LanceDB writes. This transforms a read operation into a write-heavy one. + +Return search results immediately with current importance values. Fire importance updates as `asyncio.create_task()` background tasks. Batch all updates for a single search into one task. The response contains slightly stale importance (milliseconds behind), which is acceptable because importance is advisory, not user-facing. Background task errors are logged but do not fail the search response. + +### FTS rebuild interval + +`SearchIndex.add()` sets `_fts_indexed = False` on every call. The first search after any write rebuilds the full FTS index. Under the bridge's auto-recall pattern (search before every prompt), every auto-capture write triggers a full FTS rebuild before the next search. + +Only invalidate FTS after every N adds. Between rebuilds, vector search still finds new content — the RRF reranker weights the vector component appropriately. Add an add counter. When it reaches the threshold, reset `_fts_indexed = False` and reset the counter. After a full rebuild, also reset the counter to zero. + +Configuration: +``` +MEMSTACK_FTS_REBUILD_INTERVAL=50 — number of adds between full FTS rebuilds (default 50) +``` + +### TTL result cache for search and inject + +Identical queries hit the full pipeline every time with zero caching. The bridge's `before_prompt_build` hook sends a search request on every prompt — back-to-back prompts with similar prefixes are redundant. + +Add a TTL cache on `SearchIndex`, keyed by `(query_text, agent_id, limit)`. Invalidate the entire cache on `add()`, `delete()`, and `update_importance()`. Expired entries trigger a fresh search. + +Configuration: +``` +MEMSTACK_SEARCH_CACHE_TTL=30 — seconds to cache search results (default 30) +``` + +### Auto-recall cache in OpenClaw bridge + +The bridge plugin fires `before_prompt_build` on every prompt, hitting MemStack's REST search endpoint each time with no caching. Back-to-back prompts with similar prefixes are redundant HTTP calls. + +Add an in-memory cache in the bridge plugin. Key: first 500 chars of the prompt. Value: the `prependContext` string. TTL: 30 seconds. On cache hit, return the cached result without calling MemStack. On cache miss, call MemStack and store the result. Evict expired entries on each call. + +### System prompt — stop telling models to call memory_inject every response + +Section 6 currently says "Before every LLM response, call memory_inject." This causes OpenClaw agents to narrate memory operations in every message. The bridge plugin already handles auto-recall via `before_prompt_build` — the model should not repeat what the bridge does automatically, and should not mention the memory system unprompted. + +Rewrite Section 6 to state that memories are injected automatically and the model should NOT call `memory_inject` or mention the memory system unless the user explicitly asks about it. Adjust Section 0 and Section 1 to use factual statements instead of imperative "you must" language. Add explicit instruction: "Do not mention the memory system, memory tools, or memory operations in your responses unless the user asks about memory." + +Do NOT change Sections 2–5 (write/search guidance) or Section 7 (tool reference). Those are reference documentation the model needs when it does choose to use tools. + +### Vault read/list memory cache + +`vault.read()` and `vault.list_memories()` hit disk and parse YAML frontmatter on every call. In the search/inject pipeline, these are called repeatedly — `list_memories()` globs all `.md` files and parses each one every time. + +Add an LRU cache on `VaultStore`, keyed by `(agent_id, memory_id)` for `read()` and `agent_id` for `list_memories()`. Invalidate on `write()`, `update()`, `delete()`, and `update_importance()`. + +Configuration: +``` +MEMSTACK_VAULT_CACHE_SIZE=512 — max cached entries (default 512) +``` + +### Testing + +- Full coverage for embedding cache: hits return same vector, misses call provider, cache respects max size +- Full coverage for result cache: second identical query returns cached result, cache invalidated after `add()` and `delete()`, TTL expiry triggers fresh search +- Full coverage for deferred importance: response returns current importance values, background task updates vault and index, background task error handling +- Full coverage for FTS rebuild interval: search works after adds without immediate rebuild, rebuild triggers after N adds +- Full coverage for vault cache: read returns cached data, invalidation on all mutation methods +- Full coverage for bridge auto-recall cache: second identical prompt returns cached result, expired entries trigger fresh fetch, different prompts produce different results +- Full coverage for system prompt changes: Section 6 no longer contains "Before every LLM response, call memory_inject", sections 0 and 1 use factual tone, Sections 2–5 and 7 are unchanged +- Coverage for async execution: existing tests pass transparently (TestClient drives the event loop synchronously) +- Coverage for Ollama client singleton: `consult_llm` reuses the same client instance across calls +- Minimum 90% coverage enforced + +### No eval gate +This patch changes performance characteristics, not retrieval logic. Existing v1.4 eval results remain valid. A latency comparison should be run to confirm the speed improvement, but eval gate does not apply. + +--- + +## Constraints +- No new features — all changes fix broken behavior (unacceptable latency, write-amplification on reads, incorrect model behavior from the system prompt) +- No new external dependencies — `cachetools` is already a transitive dependency via FastMCP +- No changes to vault format, search algorithm, or smart write pipeline logic +- No changes to OpenClaw bridge hook structure or lifecycle — only the auto-recall hook gets a local cache +- System prompt changes are limited to sections 0, 1, and 6 — sections 2–5 and 7 are untouched +- Bridge plugin `MemoryPluginCapability` remains empty — extending it requires SDK changes outside this project +- v1.5 rebuild must reset the FTS add counter alongside `_fts_indexed` so the rebuild interval starts fresh +- Result cache key `(query_text, agent_id, limit)` is a tuple that v1.6 will extend to `(query_text, agent_id, limit, graph_enabled)` when graph-aware retrieval is added +- Vault cache key `(agent_id, memory_id)` is a tuple that v1.6 will extend to `(vault_path, agent_id, memory_id)` when multi-vault is added \ No newline at end of file diff --git a/reference/v1_4_3_architecture.md b/reference/v1_4_3_architecture.md new file mode 100644 index 0000000..56e634c --- /dev/null +++ b/reference/v1_4_3_architecture.md @@ -0,0 +1,229 @@ +# MemStack v1.4.3 — Smart Write Merge & Auto-Capture Fix + +## Goal +Fix three bugs that cause MemStack to lose information it should retain. Bug 1 is the most severe: additive memories on the same topic are auto-discarded because the pipeline treats high embedding similarity as "duplicate" rather than "same topic." A memory like "favourite bike is Honda CBR1000RR" and another like "other favourite bike is KTM Super Duke" score 0.959 similarity — the second one gets silently dropped. Bug 2 causes the OpenClaw bridge auto-capture to mangle structured content into `[object Object]` and discard tool-role messages entirely. Bug 3 has aggressive default similarity thresholds that make the auto-ignore zone too wide. This version fixes all three without adding new features. + +--- + +## What it must deliver + +### Bug 1: Additive memories discarded by auto-ignore + +When a memory scores above `MEMSTACK_SIMILARITY_IGNORE_THRESHOLD` against an existing memory, it is auto-discarded. This is wrong. High similarity means same topic, not same information. The LLM should decide between add, merge, update, and ignore — the pipeline must never skip LLM consultation for high-similarity matches. + +**Fix: make auto-ignore an opt-in toggle, off by default.** + +Add `MEMSTACK_SIMILARITY_IGNORE_ENABLED` (default: `false`). When `false`, scores at or above the add threshold always go to the LLM. When `true`, the old behavior is restored: scores >= ignore threshold are auto-ignored. This preserves backward compatibility for users who want aggressive deduplication while fixing the default behavior. + +**Fix: add merge as a fourth LLM decision.** + +Current LLM decisions: add, update, ignore. The merge decision appends incoming content to the existing memory's body with a `"\n\n"` separator, preserving the same memory ID, importance, importance_updated, and created timestamp. This is distinct from update, which replaces content entirely and generates a new slug. + +The merge operation: + +1. Read the existing memory from the vault +2. Append incoming content: `old_memory.body + "\n\n" + incoming_content` +3. Preserve: `id`, `agent`, `type`, `tags`, `created`, `importance`, `importance_updated` +4. Update: `updated` = now, `body` = merged body +5. Write to the same file path (same slug, atomic overwrite via `_atomic_write`) +6. Invalidate caches +7. In shared mode: call `_update_shared_copy(old_memory, merged_memory)` + +Search index update after merge: + +1. Delete old chunks from the index: `search_index.delete(old_memory_id)` +2. Add new chunks for merged content: `search_index.add(merged_memory, chunks)` +3. In shared mode: `_reindex_shared_copy(old_memory_id, merged_memory)` + +The REST endpoint returns status 200 with `decision: "merged"` — it falls into the same branch as "updated" and "ignored". The MCP tool returns `decision: "merged"` in its response dict. + +Configuration: +``` +MEMSTACK_SIMILARITY_IGNORE_ENABLED=false — enable auto-ignore for scores >= ignore threshold (default: false, auto-ignore is off) +``` + +### Bug 2: Auto-capture not recording everything + +The `agent_end` hook in the OpenClaw bridge (`openclaw-bridge/src/index.ts`) has three bugs: + +1. `messages.slice(-4)` — only the last 4 messages are captured. Conversations longer than 2 turns lose earlier context. +2. `m.role === "user" || m.role === "assistant"` — tool role messages are silently discarded. Tool calls, tool results, and sub-agent dispatches are lost entirely. +3. `{ role: string; content: string }` — assistant messages can have structured content (arrays of `{type: "text"}`, `{type: "tool_use"}`, `{type: "tool_result"}` blocks). Coercing these to string produces `[object Object]`. + +Fix the content extraction: + +1. Remove `slice(-4)` — capture all messages. +2. Widen the role filter to include `"tool"`: `m.role === "user" || m.role === "assistant" || m.role === "tool"`. +3. Add a `serializeContent()` function that handles both string content and structured content blocks: + +```typescript +function serializeContent(content: unknown): string { + if (typeof content === "string") return content; + if (Array.isArray(content)) { + return content + .map((block: Record) => { + if (block.type === "text") return String(block.text ?? ""); + if (block.type === "tool_use") + return `[tool: ${block.name ?? "unknown"}(${JSON.stringify(block.input ?? {})})]`; + if (block.type === "tool_result") + return `[result: ${String(block.content ?? "")}]`; + return String(block.text ?? block.content ?? ""); + }) + .filter((s) => s.length > 0) + .join("\n"); + } + return String(content); +} +``` + +The `agent_end` handler type annotation widens from `Array<{ role: string; content: string }>` to `Array>` and uses `serializeContent(m.content)` instead of `m.content`. + +No new hook registrations. This is a bug fix — the data is already in `event.messages`, the hook just isn't extracting it correctly. + +### Bug 3: Similarity thresholds too aggressive + +Lower both defaults: + +| Setting | Old | New | +| --- | --- | --- | +| `MEMSTACK_SIMILARITY_ADD_THRESHOLD` | 0.3 | 0.25 | +| `MEMSTACK_SIMILARITY_IGNORE_THRESHOLD` | 0.92 | 0.85 | + +Lowering the add threshold means slightly more things auto-add without LLM consultation (scores 0.25–0.3 were previously in the LLM zone). Lowering the ignore threshold narrows the auto-ignore zone (when enabled) from 0.92–1.0 to 0.85–1.0. + +Files to update: +- `src/memstack/core/config.py` — default values +- `.env.example` — documented defaults and version header +- `README.md` — config reference table, pipeline description, troubleshooting section + +--- + +## Type changes + +`src/memstack/intelligence/llm.py`: +- `LLMDecision`: `Literal["add", "update", "ignore"]` → `Literal["add", "merge", "update", "ignore"]` +- Prompt: add "merge" explanation — "the incoming memory adds new facts to an existing memory on the same topic" +- Valid decision check: add "merge" + +`src/memstack/intelligence/pipeline.py`: +- `SmartWriteResult.decision`: `Literal["added", "updated", "ignored"]` → `Literal["added", "merged", "updated", "ignored"]` + +`src/memstack/core/models.py`: +- `MemoryWriteResponse.decision`: `Literal["added", "updated", "ignored"]` → `Literal["added", "merged", "updated", "ignored"]` + +--- + +## Pipeline logic + +`SmartWritePipeline.process()` — the auto-ignore block becomes conditional: + +```python +def process(self, write: MemoryWrite) -> SmartWriteResult: + similar = self._find_similar_for_write(write) + if not similar: + return self._add_memory(write) + + best_score = similar[0]["score"] + if best_score < self._settings.similarity_add_threshold: + return self._add_memory(write) + + # Auto-ignore is opt-in (default: off) + if self._settings.similarity_ignore_enabled and best_score >= self._settings.similarity_ignore_threshold: + return SmartWriteResult( + decision="ignored", + memory_id=similar[0]["memory_id"], + similarity_score=best_score, + ) + + # Consult LLM for all scores at or above the add threshold + llm_result = consult_llm(...) + if llm_result.decision == "add": + return self._add_memory(write) + elif llm_result.decision == "merge": + return self._merge_memory(write, similar[0], best_score) + elif llm_result.decision == "update": + return self._update_memory(write, similar[0], best_score) + else: # ignore + return SmartWriteResult(decision="ignored", ...) +``` + +`SmartWritePipeline._merge_memory()` — new method: + +```python +def _merge_memory(self, write, matched, similarity_score) -> SmartWriteResult: + old_memory_id = matched["memory_id"] + old_importance = matched.get("importance", self._settings.importance_initial_score) + + merged_memory = self._vault.merge( + agent_id=write.agent_id, + memory_id=old_memory_id, + incoming_content=write.content, + preserved_importance=old_importance, + ) + + # Delete old chunks, add new chunks for merged content + try: + self._search_index.delete(old_memory_id) + except Exception as e: + logger.warning("Failed to delete old memory from index {}: {}", old_memory_id, e) + + try: + chunks = chunk_text(merged_memory.body, ...) + if not self._search_index.add(merged_memory, chunks): + logger.warning("Search indexing skipped for merged memory {}", merged_memory.id) + except Exception as e: + logger.warning("Failed to index merged memory {}: {}", merged_memory.id, e) + + if self._settings.shared_mode: + self._reindex_shared_copy(old_memory_id, merged_memory) + + return SmartWriteResult(decision="merged", memory_id=merged_memory.id, similarity_score=similarity_score) +``` + +--- + +## VaultStore.merge() + +New method on `VaultStore`: + +```python +def merge(self, agent_id, memory_id, incoming_content, preserved_importance, + preserved_importance_updated=None) -> Memory: +``` + +- Read existing memory via `self.read()` +- Merge body: `old_memory.body + "\n\n" + incoming_content` +- Preserve: `id`, `agent`, `type`, `tags`, `created`, `importance`, `importance_updated` +- Update: `updated` = now, `body` = merged body +- Write to the same file path (same slug, overwriting in-place via `_atomic_write`) +- Invalidate caches +- In shared mode: `self._update_shared_copy(old_memory, merged_memory)` + +--- + +## Testing + +- Full coverage for the merge pipeline path: LLM returns "merge", vault.merge() is called, search index is updated, shared copy is reindexed +- Full coverage for VaultStore.merge(): append content, preserve ID/importance/created, update timestamp, shared mode +- Coverage for auto-ignore toggle: default `similarity_ignore_enabled=False` sends high-similarity scores to LLM; `True` restores auto-ignore +- Coverage for LLM "merge" decision: `consult_llm` returns "merge", pipeline calls `_merge_memory` +- Coverage for auto-capture fix: all messages captured (no slice), tool role included, structured content serialized (no `[object Object]`), system messages excluded +- Coverage for threshold defaults: `similarity_add_threshold == 0.25`, `similarity_ignore_threshold == 0.85`, `similarity_ignore_enabled == False` +- All existing tests pass with updated mocks for the conditional auto-ignore path +- Minimum 90% coverage enforced + +### No eval gate +This patch changes the merge/deduplication decision logic and threshold defaults. The v1.4 eval results tested retrieval, not smart write. The change does not affect search ranking or inject scoring. A merge behavior comparison should be run to confirm that additive memories are no longer discarded, but eval gate does not apply. + +--- + +## Constraints +- No new features beyond the bug fixes described above +- Auto-ignore toggle (`similarity_ignore_enabled`) is an opt-in backward-compatibility option, off by default — it restores the old behavior for users who want it, it does not add new behavior +- No changes to search ranking, inject scoring, or retrieval logic +- No changes to vault file format or frontmatter schema +- No changes to OpenClaw bridge hook structure or lifecycle — only the content extraction logic in `agent_end` +- No new hook registrations (subagent_spawning, subagent_ended, after_tool_call) — these are features, not bugs +- No changes to system prompt content +- Merge operation preserves the same memory ID (slug) — no new files created for merges +- Shared mode merge follows the same pattern as update: private write first, then shared copy update \ No newline at end of file diff --git a/reference/v1_4_4_architecture.md b/reference/v1_4_4_architecture.md new file mode 100644 index 0000000..dd2ec43 --- /dev/null +++ b/reference/v1_4_4_architecture.md @@ -0,0 +1,317 @@ +# MemStack v1.4.4 — LLM Synthesis & Background Consolidation + +## Goal +Raw auto-capture exchanges arrive as unstructured conversation dumps — flat one-liners that enter the smart write pipeline directly. This produces vague or noisy memories that add little value. This version adds two toggleable server-side features that use LLMs to improve memory quality: synthesis extracts discrete, durable facts from raw exchanges before they enter the pipeline, and consolidation periodically rewrites, merges, and splits existing memories to keep the vault coherent over time. Neither feature changes the vault format, search algorithm, or smart write pipeline logic. + +--- + +## What it must deliver + +### LLM Memory Synthesis on auto-capture + +The OpenClaw bridge `agent_end` hook POSTs raw conversation exchanges to MemStack tagged `auto-capture`. These exchanges are undifferentiated text — user questions, assistant answers, tool calls, and tool results all mashed together. The smart write pipeline processes them as-is, producing memories that are often too vague to be useful. + +When synthesis is enabled, MemStack intercepts auto-capture writes before they reach the pipeline. The raw content is sent to an LLM that extracts discrete, durable facts, preferences, decisions, and strong opinions. A single exchange can produce multiple memory statements. Each statement then enters the smart write pipeline independently — with its own similarity check, LLM consultation, and deduplication. + +Synthesis is entirely server-side. The bridge plugin is not modified. The trigger is the presence of `"auto-capture"` in the write's `tags` list combined with `synthesis_enabled=True` in settings. + +#### Implementation + +New module: `src/memstack/intelligence/synthesis.py` + +A `synthesize()` function takes the raw exchange content and returns a list of discrete memory statements. It uses the same `ollama.Client` singleton pattern as `llm.py` — one client instance, reused across calls, configured from settings. + +```python +def synthesize(content: str, model: str, host: str) -> list[str]: + """Extract discrete memory statements from a raw exchange via LLM. + + Returns a list of one or more statements. Falls back to [content] + (single-item list containing the original text) on any failure. + """ +``` + +The synthesis prompt instructs the LLM to: + +1. Read the raw exchange +2. Extract discrete facts, preferences, decisions, and strong opinions +3. Return a JSON array of strings, each string being a self-contained memory statement +4. Discard small talk, greetings, and procedural filler +5. Preserve specificity — "prefers dark mode" is better than "has a preference about themes" + +Prompt template: + +``` +You are a memory extraction assistant. Read the following conversation exchange and extract discrete, durable facts, preferences, decisions, and strong opinions. Each statement must be self-contained and specific. Discard greetings, small talk, and procedural filler. Return a JSON array of strings. + +Exchange: +{content} + +Respond with JSON: {"statements": ["statement 1", "statement 2", ...]} +``` + +On LLM failure, unparseable output, or empty statements list, `synthesize()` returns `[content]` — the original exchange stored as-is. This means synthesis failure degrades gracefully to the current behavior, never losing data. + +#### REST endpoint change + +In `create_memory()` (`src/memstack/interfaces/rest/memories.py`), after constructing the `MemoryWrite` and before calling `pipeline.process()`: + +```python +if settings.synthesis_enabled and "auto-capture" in write.tags: + statements = synthesize( + content=write.content, + model=settings.synthesis_model, + host=settings.llm_host, + ) + # Process each statement independently + results = [] + for statement in statements: + stmt_write = write.model_copy(update={"content": statement}) + result = pipeline.process(stmt_write) + results.append(result) + # Return the first result for the HTTP response; log others + if results: + return results[0] + # Fallback: process the original write if no statements were produced +``` + +The endpoint still returns a single `MemoryWriteResponse`. If synthesis produces multiple statements, the HTTP response contains the decision for the first statement. Subsequent statements are processed and their results are logged at INFO level. + +If `synthesis_enabled=False` or `"auto-capture"` is not in `write.tags`, the write goes through the pipeline unchanged — current behavior. + +#### Configuration + +``` +MEMSTACK_SYNTHESIS_ENABLED=false — enable synthesis for auto-capture writes (default: false) +MEMSTACK_SYNTHESIS_MODEL=llama3 — Ollama model for synthesis (defaults to MEMSTACK_LLM_MODEL if not set) +``` + +`MEMSTACK_SYNTHESIS_MODEL` is optional. If not explicitly set, it falls back to `Settings.llm_model`. This means users who already have `MEMSTACK_LLM_MODEL` configured do not need to set a separate model for synthesis. + +--- + +### Background Memory Consolidation + +Over time, a vault accumulates vague fragments, near-duplicates, multi-topic memories, and statements that would be clearer with implicit context added. The consolidator is a scheduled background process that periodically reviews an agent's existing memories and rewrites them for clarity and coherence. + +The consolidator runs as an `asyncio.create_task` in the app lifespan — the same pattern as the file watcher. It sleeps for a configurable interval (default: 3600 seconds), then selects a batch of the stalest memories for each agent and sends them to an LLM for review. + +The LLM returns a list of operations: rewrite (replace the body of an existing memory), merge (combine multiple memories into one), split (break one memory into several), or enrich (add implicit context to clarify a statement). The consolidator applies each operation by writing, creating, or deleting vault files. All file changes go through `_atomic_write` — the existing file watcher detects the changes and re-indexes automatically. + +#### Consolidation algorithm + +1. **Select agents**: List all agent directories in `/memstack/` (excluding `shared/`) +2. **Select memories**: For each agent, pick up to `batch_size` memories sorted by `updated` timestamp ascending (oldest first — most stale) +3. **Send to LLM**: Build a prompt containing the batch and ask the LLM to identify operations +4. **Apply operations**: Each operation is applied sequentially. Before applying any operation, every memory ID referenced by that operation is read from the vault. If any ID cannot be found — the memory was deleted between batch selection and operation application — the entire operation is skipped and a warning is logged. No partial application: if a merge references three IDs and one is missing, the whole merge is skipped. The consolidator uses `VaultStore` methods for all file operations — `merge()`, `update()`, `write()`, `delete()` — so caching, shared mode, and watcher registration all work correctly +5. **File watcher re-indexes**: After each vault file change, the `VaultWatcher` detects the modification and updates the search index + +#### Operation types + +The LLM returns a JSON object with an `operations` array. Each operation has: + +**Rewrite**: Replace the body of an existing memory with a clearer version. Preserves `id`, `agent`, `type`, `tags`, `created`, `importance`, `importance_updated`. Updates `updated`. + +```json +{"action": "rewrite", "id": "prefers-dark-mode-main-2025-05-10", "body": "Strong preference for dark mode in all IDEs and terminal emulators"} +``` + +**Merge**: Combine multiple memories about the same topic into one. Creates a new memory with merged body. Deletes the originals. The new memory gets a new slug, current timestamps, and the average importance of the originals. + +```json +{"action": "merge", "ids": ["likes-python-main-2025-05-01", "python-fan-main-2025-05-03"], "body": "Strong preference for Python — uses it as primary language for data work and scripting"} +``` + +**Split**: Break one memory covering multiple unrelated topics into separate memories. Creates new memories. Deletes the original. Each new memory gets a new slug, current timestamps, and the original's importance. + +```json +{"action": "split", "id": "random-facts-main-2025-05-04", "bodies": ["Prefers dark mode in all editors", "Uses VS Code as primary IDE"]} +``` + +**Enrich**: Add implicit context to clarify a vague statement. Preserves `id`, `agent`, `type`, `tags`, `created`, `importance`, `importance_updated`. Updates `updated`. + +```json +{"action": "enrich", "id": "likes-python-main-2025-05-01", "body": "Likes Python — preferred language for data analysis and automation scripting, avoids it for systems programming"} +``` + +#### Prompt template + +``` +You are a memory consolidation assistant. Review the following memories and identify operations that improve clarity and reduce redundancy. You may: + +- rewrite: replace a vague memory with a clearer version (preserves the memory ID) +- merge: combine memories about the same topic into one (deletes originals, creates new) +- split: separate a memory covering unrelated topics into individual memories (deletes original, creates new) +- enrich: add implicit context to clarify a vague statement (preserves the memory ID) + +Rules: +- Do not invent information. Only add context that is clearly implied by the existing text. +- Do not delete memories without rewriting or merging them. +- Preserve all specific details from the original. +- Each resulting statement must be self-contained and specific. + +Memories: +{memories} + +Respond with JSON: {"operations": [{"action": "rewrite|merge|split|enrich", ...}]} +``` + +#### Implementation + +New module: `src/memstack/intelligence/consolidation.py` + +```python +class Consolidator: + """Periodically consolidates stale memories using LLM review.""" + + def __init__(self, vault: VaultStore, settings: Settings) -> None: + self._vault = vault + self._settings = settings + self._stop_event = asyncio.Event() + + async def run(self) -> None: + """Main loop: sleep for interval, then consolidate one batch per agent.""" + while not self._stop_event.is_set(): + try: + await asyncio.wait_for( + self._stop_event.wait(), + timeout=self._settings.consolidation_interval, + ) + except asyncio.TimeoutError: + pass # interval elapsed, proceed to consolidate + if self._stop_event.is_set(): + break + await self._consolidate_all_agents() + + async def _consolidate_all_agents(self) -> None: + """Iterate over all agent directories and consolidate each.""" + ... + + async def _consolidate_agent(self, agent_id: str) -> None: + """Select stalest batch, send to LLM, apply operations.""" + ... + + def stop(self) -> None: + """Signal the consolidator to stop.""" + self._stop_event.set() +``` + +The consolidator is started in `create_app()` after the watcher, and stopped on shutdown alongside it: + +```python +# In create_app(), after watcher startup +app.state.consolidator = None +if settings.consolidation_enabled: + from memstack.intelligence.consolidation import Consolidator + consolidator = Consolidator(vault=app.state.vault, settings=settings) + app.state.consolidator = consolidator + +# In startup event +if app.state.consolidator is not None: + asyncio.create_task(app.state.consolidator.run()) + +# In shutdown event +if app.state.consolidator is not None: + app.state.consolidator.stop() +``` + +#### Merge operation detail + +When the LLM returns a merge operation, the consolidator: + +1. Reads all memories listed in `ids` from the vault +2. Creates a new `MemoryWrite` with the merged `body` from the LLM, the `agent_id` from the originals, and `importance` set to the average of the originals' importance values +3. Calls `vault.write()` to create the new merged memory +4. Calls `vault.delete()` for each original memory +5. The file watcher detects each deletion and creation, updating the search index + +The new memory gets a new slug (generated from the merged body), current timestamps, and the averaged importance. + +#### Split operation detail + +When the LLM returns a split operation, the consolidator: + +1. Reads the original memory from the vault +2. Creates a new `MemoryWrite` for each body in `bodies`, with the same `agent_id` and `tags` as the original, and `importance` set to the original's importance +3. Calls `vault.write()` for each new memory +4. Calls `vault.delete()` for the original memory +5. The file watcher detects each creation and deletion, updating the search index + +#### Rewrite and enrich operation detail + +Both rewrite and enrich preserve the existing memory ID. They read the original, update the body (and `updated` timestamp), and overwrite the file: + +1. Read the original memory +2. Construct an updated version with the new body and current `updated` timestamp +3. Write via `_atomic_write` to the same file path (same slug) +4. Invalidate caches +5. The file watcher detects the modification and re-indexes + +These operations must NOT change the memory ID (slug) — only the body and `updated` timestamp change. + +#### Configuration + +``` +MEMSTACK_CONSOLIDATION_ENABLED=false — enable background consolidation (default: false) +MEMSTACK_CONSOLIDATION_INTERVAL=3600 — seconds between consolidation runs (default: 3600) +MEMSTACK_CONSOLIDATION_BATCH_SIZE=20 — max memories per agent per run (default: 20) +MEMSTACK_CONSOLIDATION_MODEL=llama3 — Ollama model for consolidation (defaults to MEMSTACK_LLM_MODEL if not set) +``` + +`MEMSTACK_CONSOLIDATION_MODEL` is optional. If not explicitly set, it falls back to `Settings.llm_model`. + +--- + +### Configuration summary + +New environment variables for v1.4.4: + +| Variable | Default | Purpose | +|---|---|---| +| `MEMSTACK_SYNTHESIS_ENABLED` | `false` | Enable synthesis for auto-capture writes | +| `MEMSTACK_SYNTHESIS_MODEL` | *(falls back to `MEMSTACK_LLM_MODEL`)* | Ollama model for synthesis | +| `MEMSTACK_CONSOLIDATION_ENABLED` | `false` | Enable background consolidation | +| `MEMSTACK_CONSOLIDATION_INTERVAL` | `3600` | Seconds between consolidation runs | +| `MEMSTACK_CONSOLIDATION_BATCH_SIZE` | `20` | Max memories per agent per consolidation run | +| `MEMSTACK_CONSOLIDATION_MODEL` | *(falls back to `MEMSTACK_LLM_MODEL`)* | Ollama model for consolidation | + +All five variables follow the existing `MEMSTACK_` prefix convention and are added to `Settings` in `src/memstack/core/config.py`. + +--- + +### Testing + +**Synthesis:** +- Unit tests for `synthesize()`: returns list of statements on valid LLM output, falls back to `[content]` on LLM failure, falls back on unparseable JSON, falls back on empty statements list, preserves original content in fallback +- Integration test for auto-capture flow: POST with `tags: ["auto-capture"]` and `synthesis_enabled=True` produces multiple memories, each processed through the pipeline independently +- Integration test for non-auto-capture writes: POST without `"auto-capture"` tag bypasses synthesis even when enabled +- Integration test for synthesis disabled: POST with `tags: ["auto-capture"]` and `synthesis_enabled=False` processes the raw content through the pipeline as-is +- HTTP response test: first statement's decision is returned in the response body +- Minimum 90% coverage enforced + +**Consolidation:** +- Unit tests for `Consolidator`: batch selection picks oldest `batch_size` memories, handles agents with fewer memories than `batch_size`, skips agents with no memories +- Unit tests for each operation type: rewrite preserves ID and updates body, merge creates new memory and deletes originals, split creates new memories and deletes original, enrich preserves ID and adds context +- Integration test for consolidation run: consolidator processes agents sequentially, applies operations, file watcher re-indexes +- Error handling test: LLM failure during consolidation logs warning and continues, invalid operation type is skipped, missing memory ID is skipped +- Stop test: `consolidator.stop()` cancels the next iteration +- Shared mode test: merge and split operations create/delete shared copies correctly +- Minimum 90% coverage enforced + +### No eval gate +This version adds two background LLM features that improve memory quality. Neither changes the search ranking algorithm, inject scoring, or retrieval logic. Existing v1.4 eval results remain valid. A quality comparison on synthesized vs. raw auto-capture memories should be run to confirm improvement, but eval gate does not apply. + +--- + +## Constraints +- No changes to vault format, search algorithm, or smart write pipeline logic — synthesis happens before the pipeline, consolidation happens outside it +- No changes to the OpenClaw bridge plugin — synthesis is server-side, triggered by the `auto-capture` tag +- Both features are off by default — `MEMSTACK_SYNTHESIS_ENABLED=false` and `MEMSTACK_CONSOLIDATION_ENABLED=false` +- Synthesis failure degrades to current behavior (raw exchange stored as-is) — never loses data +- Consolidation failure logs and continues — never loses data +- Consolidation uses the file watcher for re-indexing — no direct search index manipulation +- No new external dependencies — uses existing `ollama` client and `asyncio` +- Rewrite and enrich operations must NOT change memory IDs — only body and `updated` timestamp change +- Merge and split operations create new memories with new IDs and delete originals — this is expected behavior +- The consolidator does not process the `shared/` agent directory — shared memories are managed through private copies +- Consolidation interval must be at least 60 seconds to prevent excessive LLM calls — validated in settings +- Batch size must be at least 1 and at most 100 — validated in settings \ No newline at end of file diff --git a/reference/v1_4_5_architecture.md b/reference/v1_4_5_architecture.md new file mode 100644 index 0000000..15568a9 --- /dev/null +++ b/reference/v1_4_5_architecture.md @@ -0,0 +1,154 @@ +# MemStack v1.4.5 — Bug Fixes & Dead Code Removal + +## Goal +Fix four bugs that affect correctness or maintainability. BUG-1 silently breaks configuration when .env files have a UTF-8 BOM. ISSUE-2 causes test failures on every version bump. ISSUE-3 uses deprecated FastAPI lifecycle hooks. ISSUE-1 removes an unused config field that was shelved but never implemented. No new features, no behavioral changes. + +--- + +## What it must deliver + +### BUG-1: Strip UTF-8 BOM from .env before parsing + +When a `.env` file is saved with a UTF-8 Byte Order Mark (`\xef\xbb\xbf`), the BOM is prepended to the first key. Pydantic-settings reads `MEMSTACK_VAULT_PATH` as `MEMSTACK_VAULT_PATH`, which fails to match the `vault_path` field. MemStack refuses to start with a cryptic validation error. + +**Root cause**: `Settings` uses `env_file_encoding="utf-8"` in `SettingsConfigDict`. Python's `utf-8` codec does not strip the BOM. Python's `utf-8-sig` codec strips it automatically on read. + +**Fix**: Change `env_file_encoding` from `"utf-8"` to `"utf-8-sig"` in `Settings.model_config`. + +**File**: `src/memstack/core/config.py`, line 22 + +```python +# Before +env_file_encoding="utf-8", +# After +env_file_encoding="utf-8-sig", +``` + +This is a one-line change. The `utf-8-sig` codec is identical to `utf-8` for files without a BOM, and automatically strips the BOM for files that have one. No behavioral change for BOM-free .env files. + +**Test**: Create a `.env` file with a UTF-8 BOM prefix and verify that `Settings` parses `MEMSTACK_VAULT_PATH` correctly (without the BOM prefix). Clean up the `.env` file after the test. + +--- + +### ISSUE-1: Remove unused obsidian_mode from Settings and .env.example + +The `obsidian_mode` config field is declared in Settings but zero code reads it. No conditional logic anywhere references it. It is dead config. The `.env.example` entry explicitly labels it "Reserved: accepted but unused in current version." + +Removing dead config is allowed under feature freeze. The field can be re-added in v1.7 (Ingestion) when Obsidian formatting is actually implemented. + +**Files**: + +1. `src/memstack/core/config.py`, line 28 — remove `obsidian_mode: bool = True` +2. `.env.example`, lines 15-18 — remove the `MEMSTACK_OBSIDIAN_MODE` block (comment, "Reserved" note, default line, blank line) +3. `CONTEXT.md`, line 38 — remove the `MEMSTACK_OBSIDIAN_MODE` row from the environment variable table + +**Verification**: `git grep obsidian_mode -- src/ tests/` returns zero results. + +--- + +### ISSUE-2: Replace hardcoded version strings in tests + +Six test assertions hardcode `"1.4.3"` instead of using `memstack.__version__`. Two test method names reference stale version `"1.4_2"`. These break on every version bump — the tests are currently asserting version 1.4.3 while the package is at 1.4.4. + +Some test files already import and use `__version__` correctly. This fix brings the remaining files in line with that pattern. + +**Files**: + +1. `tests/test_cli.py` — add `from memstack import __version__`, change line 480 from `assert data["version"] == "1.4.3"` to `assert data["version"] == __version__` + +2. `tests/test_routes_health.py` — add `from memstack import __version__`, change lines 16, 33, 142 from `"1.4.3"` to `__version__`, rename `test_health_version_is_1_4_2` to `test_health_version_matches_package` + +3. `tests/test_mcp_server.py` — already has `from memstack import __version__`, change lines 633 and 650 from `"1.4.3"` to `__version__`, rename `test_mcp_server_version_is_1_4_2` to `test_mcp_server_version_matches_package` + +**Verification**: `git grep '"1\.4\.[0-3]" -- tests/` returns zero results. All version assertions use `__version__`. + +--- + +### ISSUE-3: Move watcher/consolidator startup from @app.on_event to _app_lifespan + +FastAPI's `@app.on_event("startup"/"shutdown")` are deprecated. The app already has `_app_lifespan`, which is the modern replacement, but the watcher and consolidator start/stop logic is stuck in the old event handlers. + +**Current structure** (`src/memstack/interfaces/rest/app.py`): + +- Lines 22-30: `_app_lifespan` yields a dict for MCP tool access +- Lines 108-120: Two `@app.on_event("startup")` handlers start the watcher and consolidator +- Lines 122-130: Two `@app.on_event("shutdown")` handlers stop them + +**Fix**: Move all startup/shutdown logic into `_app_lifespan`. Add `import asyncio` to file imports. + +**After**: + +```python +@asynccontextmanager +async def _app_lifespan(app: FastAPI) -> AsyncIterator[dict]: + """Yield shared objects for MCP tool access via lifespan context.""" + if app.state.watcher is not None: + asyncio.create_task(app.state.watcher.run()) + if app.state.consolidator is not None: + asyncio.create_task(app.state.consolidator.run()) + try: + yield { + "vault": app.state.vault, + "search_index": app.state.search_index, + "smart_write_pipeline": app.state.smart_write_pipeline, + "settings": app.state.settings, + } + finally: + if app.state.watcher is not None: + app.state.watcher.stop() + if app.state.consolidator is not None: + app.state.consolidator.stop() +``` + +Remove all four `@app.on_event` decorated functions (lines 108-130). + +**Verification**: `git grep on_event -- src/` returns zero results. Watcher and consolidator start and stop correctly via lifespan. + +--- + +## Version bump: 1.4.4 → 1.4.5 + +**Files**: + +1. `src/memstack/__init__.py` — `__version__ = "1.4.5"` +2. `pyproject.toml`, line 7 — `version = "1.4.5"` +3. `.env.example`, line 1 — `# MemStack v1.4.5 Configuration` +4. `README.md`, line 35 — `"version": "1.4.5"` in health example +5. `CHANGELOG.md` — add v1.4.5 section at top + +--- + +## Commit order + +Each issue gets its own commit, then a final version-bump commit: + +1. `fix(config): strip UTF-8 BOM from .env files by using utf-8-sig codec` +2. `chore(config): remove unused obsidian_mode field and .env.example entry` +3. `fix(tests): replace hardcoded version strings with __version__ import` +4. `refactor(app): move watcher/consolidator startup from on_event to lifespan` +5. `chore(release): bump version to 1.4.5 and update changelog` + +--- + +## Verification + +After all commits: + +1. `python -m pytest tests/ -v` — all tests pass +2. `ruff check src/ tests/` — zero lint errors +3. `ruff format --check src/ tests/` — zero formatting differences +4. `git grep '"1\.4\.[0-3]" -- tests/` — zero hardcoded version strings in tests +5. `git grep obsidian_mode -- src/ tests/` — zero references +6. `git grep on_event -- src/` — zero deprecated event handler usage +7. Version consistency: `__version__`, `pyproject.toml` version, `.env.example` header, and README example all show `1.4.5` + +--- + +## Constraints +- No new features — all changes are bug fixes, dead code removal, or deprecation cleanup +- No changes to vault format, search algorithm, smart write pipeline, or REST API behavior +- No changes to OpenClaw bridge +- No new dependencies +- Removing `obsidian_mode` is dead code removal, not feature work — it was never read by any code path +- ISSUE-4 (implementing obsidian_mode path behavior) is explicitly out of scope — it is a feature, not a bug fix +- The BOM fix must not change behavior for BOM-free .env files — `utf-8-sig` is transparent for files without a BOM \ No newline at end of file diff --git a/reference/v1_4_architecture.md b/reference/v1_4_architecture.md new file mode 100644 index 0000000..fc233bb --- /dev/null +++ b/reference/v1_4_architecture.md @@ -0,0 +1,97 @@ +# MemStack v1.4 — Auto-injection + Memory Routing + +## Goal +Complete the memory routing model and add auto-injection. Orchestrators can now call a single endpoint before every LLM call and receive relevant memories automatically. + +--- + +## What it must deliver + +### Shared memory routing +Full implementation of the shared/private memory model: + +``` +MEMSTACK_SHARED_MODE=true +→ Write to: /vault/memstack/{agent_id}/{filename}.md (always) +→ Copy to: /vault/memstack/shared/{filename}.md (when shared mode is on) +→ Read from: /vault/memstack/shared/ (all agents read from here) + +MEMSTACK_SHARED_MODE=false +→ Write to: /vault/memstack/{agent_id}/{filename}.md (only) +→ Read from: /vault/memstack/{agent_id}/ (agent reads only its own) +``` + +Rules: +- Private write always happens first — if it fails, shared copy does not proceed +- Shared copy failure is logged but does not fail the write operation +- Turning shared mode off at any time never deletes existing copies in either directory +- Agents always have a complete private copy of everything they wrote regardless of shared mode state + +### Concurrency +- Asyncio + file-level locks on vault write operations +- Concurrent writes to different memory files proceed freely +- Concurrent writes to the same memory file queue safely +- No global write lock — unnecessary bottleneck + +### Auto-injection endpoint +``` +GET /agents/{agent_id}/inject?q={context_string} +``` + +Behavior: +- Runs hybrid search using the provided context string as the query +- Respects shared/private mode for determining which memories to search +- Filters out results below `MEMSTACK_INJECTION_MIN_SCORE` +- Returns top `MEMSTACK_INJECTION_TOP_N` results ranked by RRF + importance score +- Updates importance score for every returned memory (retrieval hit) + +Response: +```json +{ + "memories": [ + { + "id": "{slug}-{agent_id}-2024-01-15", + "body": "full memory body text", + "importance": 0.74, + "score": 0.91, + "agent": "{agent_id}", + "tags": ["project", "architecture"], + "created": "ISO timestamp" + } + ], + "count": 3, + "query": "original context string" +} +``` + +### MCP tool addition +``` +memory_inject — auto-injection via MCP, same behavior as REST inject endpoint +``` + +Added to system_prompt_kit.md with behavioral guidance on when orchestrators should call it. + +### REST API additions +``` +GET /agents/{agent_id}/inject?q={query} — auto-injection endpoint +GET /shared/inject?q={query} — inject from shared memories directly +``` + +### Testing +- Full coverage for shared mode write routing +- Full coverage for private-first write guarantee +- Full coverage for concurrent write handling +- Full coverage for auto-injection endpoint — result count, score filtering, importance update on hit +- Full coverage for shared mode toggle behavior — turning on and off +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.4 +- Retrieval quality must meet or exceed v1.1 baseline +- Injection endpoint latency measured and recorded +- Results committed to `evals/results/v1.4.json` + +--- + +## Constraints +- No CLI beyond start/stop in this version — full CLI ships in v1.5 diff --git a/reference/v1_5_architecture.md b/reference/v1_5_architecture.md new file mode 100644 index 0000000..f03bab5 --- /dev/null +++ b/reference/v1_5_architecture.md @@ -0,0 +1,111 @@ +# MemStack v1.5 — CLI + Ops + +## Goal +Make MemStack fully operable from the command line. A new user runs setup.sh and ends with a working, validated system. An existing user has full control over the vault and index from the terminal. + +--- + +## What it must deliver + +### Full Typer CLI +All commands under the `memstack` entrypoint: + +``` +memstack start — start the MemStack server in the foreground +memstack stop — graceful shutdown +memstack status — print whether server is running and component health +memstack setup — guided interactive setup (see below) +memstack rebuild — wipe index and rebuild from vault +memstack prune — clean up the vault (see below) +``` + +### memstack status +Calls the `/health` endpoint and prints a human-readable breakdown: +``` +MemStack v1.5.0 +Server: running +Vault: healthy (/home/atharva/vault/memstack) +LanceDB: healthy +Embeddings: healthy (ollama / nomic-embed-text) +Watcher: healthy +MCP: healthy +``` + +### memstack rebuild +Wipes the LanceDB index entirely and rebuilds it from vault files: +``` +1. Confirm with user before proceeding (unless --yes flag is passed) +2. Wipe LanceDB index +3. Scan vault — count all .md files +4. Index each file with progress bar showing files processed and time elapsed +5. Print diff report on completion: + + memstack rebuild complete — 4.2s + + Added: 12 + Updated: 3 + Removed: 1 + Unchanged: 126 +``` + +### memstack prune +Dry run by default — never deletes anything without `--confirm`: + +``` +memstack prune --duplicates — find near-duplicate memories, show what would be removed +memstack prune --low-importance — find memories below MEMSTACK_PRUNE_MIN_IMPORTANCE, show what would be removed +memstack prune --all — both of the above together +memstack prune --all --confirm — actually delete +``` + +Dry run output example: +``` +Dry run — nothing will be deleted. Pass --confirm to execute. + +Duplicates found: 4 + clawd-meeting-notes-clawd-2024-01-10.md → duplicate of meeting-notes-clawd-2024-01-08.md + +Low importance found: 7 + random-thought-clawd-2024-01-02.md (importance: 0.04) + ... + +Total: 11 memories would be removed. +Run with --confirm to delete. +``` + +### setup.sh — guided interactive setup +Steps in order: +``` +1. Check Python 3.11+ is installed +2. Check uv is installed — if not, print install instructions and exit +3. Install all dependencies via uv +4. Prompt: vault path (no default — required) +5. Prompt: embedding provider (ollama / fastembed) +6. If ollama selected: verify Ollama is reachable — warn if not +7. Prompt: shared mode on or off +8. Prompt: Obsidian mode on or off +9. Write a valid .env file from answers +10. Create vault directory structure +11. Run validation gate: + — Start MemStack server + — Write a test memory via REST + — Search for the test memory + — Confirm it appears in results + — Delete the test memory + — Stop MemStack server + — Print: "Setup complete. Run: memstack start" +``` + +If validation gate fails at any step, print exactly which step failed and what to check. Never leave the user guessing. + +### Testing +- Full coverage for all CLI commands +- Full coverage for setup validation gate logic +- Full coverage for prune dry run and confirm behavior +- Full coverage for rebuild diff report generation +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.5 +- Retrieval quality must meet or exceed v1.1 baseline +- Results committed to `evals/results/v1.5.json` diff --git a/reference/v1_6_architecture.md b/reference/v1_6_architecture.md new file mode 100644 index 0000000..1adb51f --- /dev/null +++ b/reference/v1_6_architecture.md @@ -0,0 +1,76 @@ +# MemStack v1.6 — Graph + Multi-vault + +## Goal +Make retrieval relationship-aware. Memories that link to each other surface related results during search. Multiple vaults can be monitored and searched simultaneously. + +--- + +## What it must deliver + +### Wiki-link graph +- When a memory is indexed, extract all `[[wiki-link]]` references from its body +- Build a directed graph of memory relationships: memory A links to memory B +- Graph stored in a lightweight SQLite database alongside the vault state file +- Graph is always rebuildable from vault files — it is disposable like the LanceDB index + +### Graph-aware retrieval +- After hybrid search returns ranked results, expand the result set by one hop through the wiki-link graph +- For each top result, fetch its directly linked memories and include them in the response with a `linked` flag +- Linked memories are ranked below direct results but above the score cutoff +- Graph expansion is optional — controlled per-request via query parameter `?graph=true` + +Search response updated: +```json +{ + "id": "{slug}-{agent_id}-2024-01-15", + "score": 0.91, + "importance": 0.74, + "linked": false +}, +{ + "id": "{slug}-{agent_id}-2024-01-10", + "score": 0.61, + "importance": 0.55, + "linked": true +} +``` + +### Multi-vault support +- `MEMSTACK_VAULT_PATHS` accepts a comma-separated list of vault paths +- `MEMSTACK_VAULT_PATH` remains valid for single-vault setups — both are supported +- Each vault is watched independently by the file watcher +- Each vault has its own LanceDB table in the same LanceDB store +- Search can target a specific vault or all vaults simultaneously via query parameter `?vault=all` +- Rebuild and prune commands accept a `--vault` flag to target a specific vault + +### Memory timeline endpoint +``` +GET /agents/{agent_id}/memories/timeline?from={ISO date}&to={ISO date}&limit={n} +GET /shared/memories/timeline?from={ISO date}&to={ISO date}&limit={n} +``` + +Returns memories sorted by creation timestamp, oldest to newest, within the given date range. No search scoring — pure chronological ordering. + +### Rebuild update +- Rebuild now also rebuilds the wiki-link graph +- Diff report includes graph changes: + ``` + Links added: 34 + Links removed: 2 + ``` + +### Testing +- Full coverage for wiki-link extraction +- Full coverage for graph building and graph storage +- Full coverage for graph-aware retrieval expansion +- Full coverage for multi-vault configuration parsing +- Full coverage for multi-vault search targeting +- Full coverage for timeline endpoint +- Hypothesis tests for edge cases in wiki-link parsing (malformed links, self-links, circular links) +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.6 +- Retrieval quality must meet or exceed v1.1 baseline +- Graph-aware retrieval measured separately — precision and recall with `?graph=true` vs without +- Results committed to `evals/results/v1.6.json` diff --git a/reference/v1_7_architecture.md b/reference/v1_7_architecture.md new file mode 100644 index 0000000..9829b14 --- /dev/null +++ b/reference/v1_7_architecture.md @@ -0,0 +1,97 @@ +# MemStack v1.7 — Ingestion + +## Goal +Ingest non-text content into the vault. PDFs, URLs, and images all land as readable, searchable markdown files. Everything stays in Obsidian. + +--- + +## What it must deliver + +### Core ingestion principle +Every ingested source — regardless of type — produces exactly one markdown file in the vault. The file is human-readable in Obsidian, indexed by MemStack, and treated identically to any hand-written memory. Ingestion is a one-way process: source → markdown → vault. The original source file is never modified. + +### Ingested file format +Same as any memory file, with additional frontmatter fields: +``` +--- +id: slug-agentid-YYYY-MM-DD +agent: {agent_id} +type: ingested +source_type: pdf | url | image +source_ref: original URL or filename +importance: 0.5 +tags: [] +created: ISO timestamp +updated: ISO timestamp +--- + +Extracted content here. +``` + +### PDF ingestion +- Extract all text from PDF pages +- Preserve document structure where possible — headings, paragraphs, lists +- Strip headers, footers, and page numbers +- If PDF is scanned (no extractable text): log a warning and skip — OCR is out of scope for this version +- One markdown file per PDF, stored in the calling agent's vault directory +- Source filename recorded in frontmatter `source_ref` + +### URL ingestion +- Fetch the page content at the given URL +- Extract main body text — strip navigation, ads, footers, and boilerplate +- Preserve headings and paragraph structure +- One markdown file per URL +- Source URL recorded in frontmatter `source_ref` +- Respects `MEMSTACK_OBSIDIAN_MODE` — Obsidian formatting applied if enabled + +### Image ingestion +- Send image to a vision-capable model via Ollama +- Model describes the image content in natural language +- Description stored as the memory body +- If no vision model is available: log a clear error, skip the image +- Vision model configurable via `MEMSTACK_VISION_MODEL` +- Source filename recorded in frontmatter `source_ref` + +### REST API additions +``` +POST /agents/{agent_id}/ingest/pdf — body: multipart file upload +POST /agents/{agent_id}/ingest/url — body: { "url": "https://..." } +POST /agents/{agent_id}/ingest/image — body: multipart file upload +``` + +All ingestion endpoints: +- Pass the resulting markdown through the smart write pipeline before writing to vault +- Return the same decision response as memory writes: `added`, `updated`, or `ignored` +- Are subject to shared memory routing if `MEMSTACK_SHARED_MODE=true` + +### MCP tool additions +``` +ingest_pdf — ingest a PDF by file path accessible to the server +ingest_url — ingest a URL +ingest_image — ingest an image by file path accessible to the server +``` + +Added to system_prompt_kit.md with guidance on when ingestion is appropriate. + +### Testing +- Full coverage for PDF text extraction and markdown conversion +- Full coverage for URL fetching and content extraction +- Full coverage for image ingestion and vision model integration +- Full coverage for ingestion smart write pipeline integration +- Full coverage for ingestion shared mode routing +- Full coverage for error handling — scanned PDF, unreachable URL, missing vision model +- Hypothesis tests for malformed inputs across all three ingestion types +- Minimum 90% coverage enforced + +### Eval gate +- Run eval runner from v1.1 against v1.7 +- Existing text retrieval quality must meet or exceed v1.1 baseline +- Ingested content retrieval measured separately — precision and recall for PDF, URL, and image ingested memories +- Results committed to `evals/results/v1.7.json` + +--- + +## Constraints +- Scanned PDF OCR is out of scope for this version +- No remote access in this version — ingestion endpoints are local only +- Vision model must be available via Ollama — no external API calls diff --git a/src/memstack/__init__.py b/src/memstack/__init__.py index c0f285b..56dadec 100644 --- a/src/memstack/__init__.py +++ b/src/memstack/__init__.py @@ -1 +1 @@ -__version__ = "1.4.4" +__version__ = "1.4.5" diff --git a/src/memstack/core/config.py b/src/memstack/core/config.py index 79a4aa0..4c04021 100644 --- a/src/memstack/core/config.py +++ b/src/memstack/core/config.py @@ -19,13 +19,12 @@ class Settings(BaseSettings): model_config = SettingsConfigDict( env_prefix="MEMSTACK_", env_file=".env", - env_file_encoding="utf-8", + env_file_encoding="utf-8-sig", ) vault_path: Path host: str = "127.0.0.1" port: int = 7777 - obsidian_mode: bool = True shared_mode: bool = False importance_initial_score: float = 0.5 log_level: str = "INFO" @@ -33,9 +32,8 @@ class Settings(BaseSettings): log_rotation: str = "10 MB" log_retention: str = "7 days" state_file: Path = Path("~/.memstack/state.json") - embedding_provider: str = "ollama" - embedding_model: str = "nomic-embed-text" - embedding_autofallback: bool = True + embedding_provider: str = "fastembed" + embedding_model: str = "BAAI/bge-small-en-v1.5" chunk_max_tokens: int = 512 chunk_overlap_tokens: int = 50 rrf_k: int = 10 @@ -43,7 +41,6 @@ class Settings(BaseSettings): index_path: Path = Path("~/.memstack/index") similarity_add_threshold: float = 0.25 similarity_ignore_threshold: float = 0.85 - similarity_ignore_enabled: bool = False importance_decay_halflife: float = 7.0 importance_hit_increment: float = 0.05 llm_model: str = "llama3" diff --git a/src/memstack/core/models.py b/src/memstack/core/models.py index 6ac0db0..95b9a1f 100644 --- a/src/memstack/core/models.py +++ b/src/memstack/core/models.py @@ -131,6 +131,15 @@ class MemoryWriteResponse(BaseModel): similarity_score: float | None = None +class MemoryListResponse(BaseModel): + """Paginated response for the list memories endpoint.""" + + memories: list[MemoryRead] + total: int + limit: int + offset: int + + class InjectionMemory(BaseModel): """A single memory returned by the inject endpoint.""" diff --git a/src/memstack/core/vault.py b/src/memstack/core/vault.py index d16ee50..d4e5405 100644 --- a/src/memstack/core/vault.py +++ b/src/memstack/core/vault.py @@ -13,7 +13,13 @@ from loguru import logger from memstack.core.config import Settings, get_settings -from memstack.core.models import Memory, MemoryRead, MemoryWrite, build_memory +from memstack.core.models import ( + Memory, + MemoryListResponse, + MemoryRead, + MemoryWrite, + build_memory, +) if TYPE_CHECKING: from memstack.interfaces.watcher import VaultWatcher @@ -39,7 +45,9 @@ def __init__(self, settings: Settings | None = None) -> None: self._memstack_dir = self._vault_path / "memstack" self._cache_size = self._settings.vault_cache_size self._read_cache: OrderedDict[tuple[str, str], Memory] = OrderedDict() - self._list_cache: OrderedDict[str, list[MemoryRead]] = OrderedDict() + self._list_cache: OrderedDict[tuple[str, int, int], MemoryListResponse] = ( + OrderedDict() + ) self._cache_lock = threading.Lock() @property @@ -64,7 +72,9 @@ def _invalidate_caches(self, agent_id: str | None = None) -> None: keys_to_remove = [k for k in self._read_cache if k[0] == agent_id] for k in keys_to_remove: del self._read_cache[k] - self._list_cache.pop(agent_id, None) + list_keys = [k for k in self._list_cache if k[0] == agent_id] + for k in list_keys: + del self._list_cache[k] else: self._read_cache.clear() self._list_cache.clear() @@ -156,10 +166,12 @@ def merge( incoming_content: str, preserved_importance: float, preserved_importance_updated: str | None = None, + merged_body: str | None = None, ) -> Memory: """Merge incoming content into an existing memory. - Appends incoming_content to the existing body with a double-newline separator. + If merged_body is provided, uses it as the new body (LLM-rewritten content). + Otherwise, appends incoming_content to the existing body with a double-newline separator. Preserves the same ID, agent, type, tags, created, importance, and importance_updated. Updates the `updated` timestamp. """ @@ -168,10 +180,13 @@ def merge( preserved_importance_updated = old_memory.importance_updated now = datetime.now(timezone.utc).isoformat() - merged_body = old_memory.body + "\n\n" + incoming_content + if merged_body: + final_body = merged_body + else: + final_body = old_memory.body + "\n\n" + incoming_content merged_memory = old_memory.model_copy( update={ - "body": merged_body, + "body": final_body, "updated": now, "importance": preserved_importance, "importance_updated": preserved_importance_updated, @@ -254,25 +269,43 @@ def update_importance( return updated - def list_memories(self, agent_id: str) -> list[MemoryRead]: - """List all memories for an agent.""" + def list_memories( + self, agent_id: str, limit: int = 50, offset: int = 0 + ) -> MemoryListResponse: + """List memories for an agent with pagination. + + limit=0 returns all memories (no slicing). + """ _validate_agent_id(agent_id) + cache_key = (agent_id, limit, offset) + with self._cache_lock: - if agent_id in self._list_cache: - self._list_cache.move_to_end(agent_id) - return self._list_cache[agent_id] + if cache_key in self._list_cache: + self._list_cache.move_to_end(cache_key) + return self._list_cache[cache_key] + agent_dir = self._memstack_dir / agent_id if not agent_dir.exists(): - result: list[MemoryRead] = [] + result = MemoryListResponse( + memories=[], total=0, limit=limit, offset=offset + ) with self._cache_lock: - self._list_cache[agent_id] = result - self._list_cache.move_to_end(agent_id) + self._list_cache[cache_key] = result + self._list_cache.move_to_end(cache_key) while len(self._list_cache) > self._cache_size: self._list_cache.popitem(last=False) return result + all_paths = sorted(agent_dir.glob("*.md")) + total = len(all_paths) + + if limit == 0: + sliced = all_paths + else: + sliced = all_paths[offset : offset + limit] + memories: list[MemoryRead] = [] - for path in sorted(agent_dir.glob("*.md")): + for path in sliced: try: mem = Memory.from_frontmatter(path.read_text(encoding="utf-8")) memories.append( @@ -290,12 +323,16 @@ def list_memories(self, agent_id: str) -> list[MemoryRead]: ) except Exception: logger.warning("Skipping invalid memory file: {}", path.name) + + result = MemoryListResponse( + memories=memories, total=total, limit=limit, offset=offset + ) with self._cache_lock: - self._list_cache[agent_id] = memories - self._list_cache.move_to_end(agent_id) + self._list_cache[cache_key] = result + self._list_cache.move_to_end(cache_key) while len(self._list_cache) > self._cache_size: self._list_cache.popitem(last=False) - return memories + return result def list_shared_memories(self) -> list[MemoryRead]: """List all memories in the shared directory.""" diff --git a/src/memstack/intelligence/classify.py b/src/memstack/intelligence/classify.py new file mode 100644 index 0000000..870082d --- /dev/null +++ b/src/memstack/intelligence/classify.py @@ -0,0 +1,68 @@ +"""Content classification for memory categorisation.""" + +from __future__ import annotations + +import json + +from loguru import logger + +from memstack.intelligence.llm import _get_ollama_client, strip_code_fences + +CATEGORIES = [ + "food", + "fitness", + "work", + "entertainment", + "tools", + "relationships", + "health", + "travel", + "education", + "other", +] + +_CATEGORY_SET = set(CATEGORIES) + + +def classify_content(content: str, model: str, host: str) -> str: + """Classify content into a category using LLM. + + Returns one of the CATEGORIES strings. Falls back to "other" on failure. + """ + prompt = ( + "Classify the following memory content into exactly one of these categories: " + f"{', '.join(CATEGORIES)}.\n\n" + 'Respond as JSON with a "category" key, e.g. {"category": "food"}.\n\n' + f"Content: {content}" + ) + + try: + client = _get_ollama_client(host) + response = client.chat( + model=model, + messages=[{"role": "user", "content": prompt}], + format="json", + ) + raw = response["message"]["content"] + parsed = json.loads(strip_code_fences(raw)) + + # The LLM might return {"category": "food"} or just the category string + if isinstance(parsed, dict): + category = parsed.get("category", "") + elif isinstance(parsed, str): + category = parsed + else: + category = "" + + category = str(category).strip().lower() + if category in _CATEGORY_SET: + return category + + logger.warning( + "LLM returned unknown category: {}, defaulting to other", category + ) + return "other" + + except Exception as e: + logger.warning("Classification failed, defaulting to other: {}", e) + return "other" diff --git a/src/memstack/intelligence/consolidation.py b/src/memstack/intelligence/consolidation.py index 4f5ac8d..4f05fd8 100644 --- a/src/memstack/intelligence/consolidation.py +++ b/src/memstack/intelligence/consolidation.py @@ -12,7 +12,7 @@ from memstack.core.config import Settings from memstack.core.models import MemoryWrite from memstack.core.vault import _atomic_write -from memstack.intelligence.llm import _get_ollama_client +from memstack.intelligence.llm import _get_ollama_client, strip_code_fences if TYPE_CHECKING: from memstack.core.vault import VaultStore @@ -58,7 +58,7 @@ async def _consolidate_all_agents(self) -> None: async def _consolidate_agent(self, agent_id: str) -> None: """Select stalest batch, send to LLM, apply operations.""" - memories = self._vault.list_memories(agent_id) + memories = self._vault.list_memories(agent_id, limit=0).memories if not memories: return @@ -114,7 +114,7 @@ def _consult_llm(self, batch: list, model: str, host: str) -> list[dict]: format="json", ) content = response["message"]["content"] - parsed = json.loads(content) + parsed = json.loads(strip_code_fences(content)) operations = parsed.get("operations", []) if not isinstance(operations, list): logger.warning( diff --git a/src/memstack/intelligence/llm.py b/src/memstack/intelligence/llm.py index e097706..e917c96 100644 --- a/src/memstack/intelligence/llm.py +++ b/src/memstack/intelligence/llm.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re from dataclasses import dataclass from typing import Literal @@ -14,6 +15,14 @@ _ollama_client: ollama.Client | None = None +def strip_code_fences(text: str) -> str: + """Strip markdown code fences from LLM response text.""" + match = re.match(r"^```(?:json)?\s*\n(.*?)\n```\s*$", text.strip(), re.DOTALL) + if match: + return match.group(1) + return text + + def _get_ollama_client(host: str = "http://localhost:11434") -> ollama.Client: """Return a module-level singleton Ollama client.""" global _ollama_client @@ -32,6 +41,7 @@ def _reset_ollama_client() -> None: class LLMConsultResult: decision: LLMDecision reasoning: str + merged_content: str | None = None def consult_llm( @@ -39,6 +49,7 @@ def consult_llm( similar_memories: list[dict], model: str = "llama3", host: str = "http://localhost:11434", + incoming_category: str | None = None, ) -> LLMConsultResult: """Consult an LLM to decide whether to add, merge, update, or ignore a memory. @@ -46,19 +57,31 @@ def consult_llm( """ memory_descriptions = [] for m in similar_memories[:3]: + score_str = f"Similarity: {m['score']:.3f}" if "score" in m else "" + tags_str = f"Tags: {m['tags']}" if m.get("tags") else "" memory_descriptions.append( - f"Memory ID: {m['memory_id']}\nContent: {m['content']}\nSimilarity: {m['score']:.3f}" + f"Memory ID: {m['memory_id']}\nContent: {m['content']}\n{score_str}{tags_str}".strip() ) + category_line = "" + if incoming_category: + category_line = f"\nIncoming memory category: {incoming_category}\n" + prompt = ( "You are a memory management assistant. Decide whether to add, merge, update, or ignore " "an incoming memory based on its similarity to existing memories.\n\n" - f"Incoming memory:\n{incoming_content}\n\n" + f"Incoming memory:\n{incoming_content}\n" + f"{category_line}\n" f"Existing similar memories:\n{'---'.join(memory_descriptions)}\n\n" - 'Respond with JSON: {"decision": "add"|"merge"|"update"|"ignore", "reasoning": "brief explanation"}\n\n' - '- "add": the incoming memory contains genuinely new information\n' - '- "merge": the incoming memory adds new facts to an existing memory on the same topic\n' - '- "update": the incoming memory is a better version of an existing one\n' + 'Respond with JSON: {"decision": "add"|"merge"|"update"|"ignore", ' + '"reasoning": "brief explanation", "merged_content": "rewritten paragraph if merge, otherwise empty string"}\n\n' + '- "add": the incoming memory contains genuinely new information unrelated to existing memories\n' + '- "merge": the incoming memory belongs to the same topic or category as an existing memory, ' + "even if the specific facts differ (e.g., both are food preferences, both are about fitness, " + "both are about a toolchain). When merging, produce merged_content: a single coherent paragraph " + "that combines both memories, preserving all facts. Do not simply concatenate.\n" + '- "update": the incoming memory contradicts, replaces, or supersedes an existing one ' + "(e.g., a preference changed, a correction, an updated version of a fact)\n" '- "ignore": the existing memories already cover this information adequately' ) @@ -70,9 +93,10 @@ def consult_llm( format="json", ) content = response["message"]["content"] - parsed = json.loads(content) + parsed = json.loads(strip_code_fences(content)) decision_str = parsed.get("decision", "add").lower() reasoning = parsed.get("reasoning", "") + merged_content = parsed.get("merged_content", "") or None if decision_str not in ("add", "merge", "update", "ignore"): logger.warning("Invalid LLM decision: {}, defaulting to add", decision_str) @@ -88,7 +112,9 @@ def consult_llm( max(m["score"] for m in similar_memories) if similar_memories else 0.0, reasoning, ) - return LLMConsultResult(decision=decision, reasoning=reasoning) + return LLMConsultResult( + decision=decision, reasoning=reasoning, merged_content=merged_content + ) except Exception as e: logger.warning("LLM consultation failed, defaulting to add: {}", e) diff --git a/src/memstack/intelligence/pipeline.py b/src/memstack/intelligence/pipeline.py index 574dd99..d044840 100644 --- a/src/memstack/intelligence/pipeline.py +++ b/src/memstack/intelligence/pipeline.py @@ -9,6 +9,7 @@ from memstack.core.models import Memory, MemoryWrite from memstack.core.slug import generate_slug from memstack.core.vault import VaultStore +from memstack.intelligence.classify import classify_content from memstack.intelligence.llm import consult_llm from memstack.search.chunker import chunk_text from memstack.search.index import SearchIndex @@ -39,9 +40,39 @@ def process(self, write: MemoryWrite) -> SmartWriteResult: Returns a SmartWriteResult indicating what happened. """ + # Step 0: Classify content and tag with category + category = classify_content( + write.content, + model=self._settings.llm_model, + host=self._settings.llm_host, + ) + category_tag = f"cat:{category}" + if category_tag not in write.tags: + write.tags.append(category_tag) + # Step 1: Try to find similar memories similar = self._find_similar_for_write(write) + # Step 1b: Find same-category memories to boost merge candidates + category_matches = [] + if write.agent_id and self._search_index.is_available(): + try: + category_matches = self._search_index.find_by_tag( + tag=category_tag, + agent_id=write.agent_id, # type: ignore[arg-type] + limit=3, + ) + except Exception as e: + logger.debug("Category tag search failed: {}", e) + + # Merge category matches into similar results (avoid duplicates) + if category_matches: + seen_ids = {m["memory_id"] for m in similar} + for cm in category_matches: + if cm["memory_id"] not in seen_ids: + similar.append(cm) + seen_ids.add(cm["memory_id"]) + if not similar: # No similar memories found — add as new return self._add_memory(write) @@ -53,29 +84,21 @@ def process(self, write: MemoryWrite) -> SmartWriteResult: # Low similarity — genuinely new information return self._add_memory(write) - # Auto-ignore is opt-in (default: off) - if ( - self._settings.similarity_ignore_enabled - and best_score >= self._settings.similarity_ignore_threshold - ): - return SmartWriteResult( - decision="ignored", - memory_id=similar[0]["memory_id"], - similarity_score=best_score, - ) - - # Step 3: Consult LLM for all scores at or above the add threshold + # Consult LLM for all scores at or above the add threshold llm_result = consult_llm( incoming_content=write.content, similar_memories=similar, model=self._settings.llm_model, host=self._settings.llm_host, + incoming_category=category, ) if llm_result.decision == "add": return self._add_memory(write) elif llm_result.decision == "merge": - return self._merge_memory(write, similar[0], best_score) + return self._merge_memory( + write, similar[0], best_score, llm_result.merged_content + ) elif llm_result.decision == "update": return self._update_memory(write, similar[0], best_score) else: # ignore @@ -129,7 +152,11 @@ def _add_memory(self, write: MemoryWrite) -> SmartWriteResult: ) def _merge_memory( - self, write: MemoryWrite, matched: dict, similarity_score: float + self, + write: MemoryWrite, + matched: dict, + similarity_score: float, + merged_content: str | None = None, ) -> SmartWriteResult: """Merge incoming content into an existing memory, preserving ID and importance.""" old_memory_id = matched["memory_id"] @@ -143,6 +170,7 @@ def _merge_memory( memory_id=old_memory_id, incoming_content=write.content, preserved_importance=old_importance, + merged_body=merged_content, ) # Delete old chunks, add new chunks for merged content diff --git a/src/memstack/intelligence/synthesis.py b/src/memstack/intelligence/synthesis.py index eb0f6ff..d36b963 100644 --- a/src/memstack/intelligence/synthesis.py +++ b/src/memstack/intelligence/synthesis.py @@ -6,7 +6,7 @@ from loguru import logger -from memstack.intelligence.llm import _get_ollama_client +from memstack.intelligence.llm import _get_ollama_client, strip_code_fences def synthesize(content: str, model: str, host: str) -> list[str]: @@ -18,8 +18,10 @@ def synthesize(content: str, model: str, host: str) -> list[str]: prompt = ( "You are a memory extraction assistant. Read the following conversation exchange " "and extract discrete, durable facts, preferences, decisions, and strong opinions. " - "Each statement must be self-contained and specific. Discard greetings, small talk, " - "and procedural filler. Return a JSON array of strings.\n\n" + "Each statement must be self-contained and include specifics that pin down the " + "preference or fact (exact values, names, identifiers, attributes). Discard greetings, " + "small talk, and general knowledge not tied to the user's preference or fact. " + "Return a JSON array of strings.\n\n" f"Exchange:\n{content}\n\n" 'Respond with JSON: {"statements": ["statement 1", "statement 2", ...]}' ) @@ -32,7 +34,7 @@ def synthesize(content: str, model: str, host: str) -> list[str]: format="json", ) raw = response["message"]["content"] - parsed = json.loads(raw) + parsed = json.loads(strip_code_fences(raw)) statements = parsed.get("statements", []) if not isinstance(statements, list): diff --git a/src/memstack/interfaces/mcp/server.py b/src/memstack/interfaces/mcp/server.py index 69ce973..89a8e87 100644 --- a/src/memstack/interfaces/mcp/server.py +++ b/src/memstack/interfaces/mcp/server.py @@ -44,6 +44,7 @@ async def mcp_lifespan(server: FastMCP) -> AsyncIterator[dict[str, Any]]: embedding_provider = None search_index = SearchIndex(settings=settings, embedding_provider=embedding_provider) + search_index.validate_dimension() smart_write_pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings ) @@ -277,35 +278,46 @@ async def memory_delete( @mcp.tool async def memory_list( agent_id: str, + limit: int = 50, + offset: int = 0, ctx: Context | None = None, - ) -> list[dict[str, Any]]: - """List all memories for an agent. + ) -> dict[str, Any]: + """List memories for an agent with pagination. Args: agent_id: The agent ID to list memories for. + limit: Maximum number of memories to return (default 50, use 0 for all). + offset: Number of memories to skip (default 0). """ if ctx is None: - return [{"error": "No context available"}] + return {"error": "No context available"} lc = ctx.lifespan_context vault: VaultStore = lc["vault"] loop = asyncio.get_event_loop() - memories = await loop.run_in_executor(None, vault.list_memories, agent_id) - return [ - { - "id": m.id, - "agent": m.agent, - "type": m.type, - "importance": m.importance, - "tags": m.tags, - "created": m.created, - "updated": m.updated, - "importance_updated": m.importance_updated, - "body": m.body, - } - for m in memories - ] + result = await loop.run_in_executor( + None, vault.list_memories, agent_id, limit, offset + ) + return { + "memories": [ + { + "id": m.id, + "agent": m.agent, + "type": m.type, + "importance": m.importance, + "tags": m.tags, + "created": m.created, + "updated": m.updated, + "importance_updated": m.importance_updated, + "body": m.body, + } + for m in result.memories + ], + "total": result.total, + "limit": result.limit, + "offset": result.offset, + } @mcp.tool async def memory_inject( diff --git a/src/memstack/interfaces/rest/app.py b/src/memstack/interfaces/rest/app.py index df9a57f..8cf472d 100644 --- a/src/memstack/interfaces/rest/app.py +++ b/src/memstack/interfaces/rest/app.py @@ -1,3 +1,4 @@ +import asyncio import sys from collections.abc import AsyncIterator from contextlib import asynccontextmanager @@ -22,12 +23,22 @@ @asynccontextmanager async def _app_lifespan(app: FastAPI) -> AsyncIterator[dict]: """Yield shared objects for MCP tool access via lifespan context.""" - yield { - "vault": app.state.vault, - "search_index": app.state.search_index, - "smart_write_pipeline": app.state.smart_write_pipeline, - "settings": app.state.settings, - } + if app.state.watcher is not None: + asyncio.create_task(app.state.watcher.run()) + if app.state.consolidator is not None: + asyncio.create_task(app.state.consolidator.run()) + try: + yield { + "vault": app.state.vault, + "search_index": app.state.search_index, + "smart_write_pipeline": app.state.smart_write_pipeline, + "settings": app.state.settings, + } + finally: + if app.state.watcher is not None: + app.state.watcher.stop() + if app.state.consolidator is not None: + app.state.consolidator.stop() def create_app(settings: Settings | None = None) -> FastAPI: @@ -65,6 +76,7 @@ def create_app(settings: Settings | None = None) -> FastAPI: app.state.embedding_provider = None search_index = SearchIndex(settings=settings, embedding_provider=None) app.state.search_index = search_index + search_index.validate_dimension() app.state.smart_write_pipeline = SmartWritePipeline( vault=app.state.vault, search_index=search_index, @@ -105,28 +117,4 @@ def create_app(settings: Settings | None = None) -> FastAPI: app.include_router(memories_router, prefix="/agents/{agent_id}") app.include_router(system_prompt_router, prefix="/agents/{agent_id}") - @app.on_event("startup") - async def start_watcher(): - if app.state.watcher is not None: - import asyncio - - asyncio.create_task(app.state.watcher.run()) - - @app.on_event("startup") - async def start_consolidator(): - if app.state.consolidator is not None: - import asyncio - - asyncio.create_task(app.state.consolidator.run()) - - @app.on_event("shutdown") - async def stop_watcher(): - if app.state.watcher is not None: - app.state.watcher.stop() - - @app.on_event("shutdown") - async def stop_consolidator(): - if app.state.consolidator is not None: - app.state.consolidator.stop() - return app diff --git a/src/memstack/interfaces/rest/memories.py b/src/memstack/interfaces/rest/memories.py index 19672ec..d812602 100644 --- a/src/memstack/interfaces/rest/memories.py +++ b/src/memstack/interfaces/rest/memories.py @@ -85,14 +85,18 @@ async def create_memory(agent_id: str, data: CreateMemoryRequest, request: Reque @router.get("/memories") -async def list_memories(agent_id: str, request: Request): +async def list_memories( + agent_id: str, request: Request, limit: int = 50, offset: int = 0 +): vault = _get_vault(request) loop = asyncio.get_event_loop() try: - memories = await loop.run_in_executor(None, vault.list_memories, agent_id) + result = await loop.run_in_executor( + None, vault.list_memories, agent_id, limit, offset + ) except (PathTraversalError, InvalidIdentifierError) as e: raise HTTPException(status_code=400, detail=str(e)) from None - return memories + return result @router.get("/memories/{memory_id}") diff --git a/src/memstack/interfaces/watcher.py b/src/memstack/interfaces/watcher.py index 6f4cc11..9e37f8f 100644 --- a/src/memstack/interfaces/watcher.py +++ b/src/memstack/interfaces/watcher.py @@ -118,11 +118,19 @@ def _handle_add_or_update(self, path: Path) -> None: with self._lock: self._path_to_memory_id[str(path)] = memory_id + # Always delete existing index entries before re-indexing to avoid + # stale chunks coexisting with updated chunks (e.g. after an Obsidian edit). + try: + self._search_index.delete(memory_id) + except Exception as e: + logger.debug("Watcher: no prior index for {}: {}", memory_id, e) + + # If the filename changed, also clean up entries under the old stem. if path.stem != memory_id: try: self._search_index.delete(path.stem) except Exception as e: - logger.warning( + logger.debug( "Watcher: failed to delete old index entry {}: {}", path.stem, e ) @@ -218,6 +226,30 @@ def scan_vault( else: _update_last_indexed(state_file, state) + # Clean up orphaned index entries (vault files deleted while server was offline). + orphans_removed = 0 + for agent_dir in memstack_dir.iterdir(): + if not agent_dir.is_dir(): + continue + agent_id = agent_dir.name + try: + indexed_ids = search_index.list_memory_ids(agent_id) + except Exception: + continue + existing_ids = {f.stem for f in agent_dir.glob("*.md")} + for memory_id in indexed_ids: + if memory_id not in existing_ids: + try: + search_index.delete(memory_id) + orphans_removed += 1 + except Exception as e: + logger.warning( + "scan_vault: failed to delete orphan {}: {}", memory_id, e + ) + + if orphans_removed: + logger.info("Vault scan: removed {} orphaned index entries", orphans_removed) + logger.info( "Vault scan: reindexed {} files newer than {}", indexed_count, last_indexed ) diff --git a/src/memstack/search/embeddings.py b/src/memstack/search/embeddings.py index d7ad0a5..b0da9c6 100644 --- a/src/memstack/search/embeddings.py +++ b/src/memstack/search/embeddings.py @@ -5,8 +5,6 @@ from collections import OrderedDict from typing import Protocol, runtime_checkable -from loguru import logger - @runtime_checkable class EmbeddingProvider(Protocol): @@ -119,43 +117,27 @@ def check_available(self) -> bool: def create_embedding_provider(settings: object) -> EmbeddingProvider: """Create an embedding provider based on settings. - If ollama is selected but unavailable and autofallback is enabled, - falls back to fastembed with a warning. + If the configured provider is unavailable, raises RuntimeError immediately. """ - provider_name = getattr(settings, "embedding_provider", "ollama") - model = getattr(settings, "embedding_model", "nomic-embed-text") - autofallback = getattr(settings, "embedding_autofallback", True) + provider_name = getattr(settings, "embedding_provider", "fastembed") + model = getattr(settings, "embedding_model", "BAAI/bge-small-en-v1.5") cache_size = getattr(settings, "embedding_cache_size", 1024) if provider_name == "ollama": provider = OllamaProvider(model=model, cache_size=cache_size) if provider.check_available(): return provider - if autofallback: - logger.warning( - "Ollama unavailable, falling back to fastembed. " - "Set MEMSTACK_EMBEDDING_AUTOFALLBACK=false to disable." - ) - fallback = FastembedProvider( - model=FASTEMBED_DEFAULT_MODEL, cache_size=cache_size - ) - if fallback.check_available(): - return fallback - raise RuntimeError( - "Both Ollama and fastembed embedding providers are unavailable. " - "Ensure Ollama is running or fastembed dependencies are installed." - ) raise RuntimeError( - "Ollama is unavailable and autofallback is disabled. " + f"Ollama embedding provider is unavailable (model={model}). " "Start Ollama or set MEMSTACK_EMBEDDING_PROVIDER=fastembed." ) if provider_name == "fastembed": - provider = FastembedProvider(cache_size=cache_size) + provider = FastembedProvider(model=model, cache_size=cache_size) if provider.check_available(): return provider raise RuntimeError( - "Fastembed embedding provider is unavailable. " + f"Fastembed embedding provider is unavailable (model={model}). " "Ensure fastembed dependencies are installed." ) diff --git a/src/memstack/search/index.py b/src/memstack/search/index.py index 52d1569..44acd93 100644 --- a/src/memstack/search/index.py +++ b/src/memstack/search/index.py @@ -260,6 +260,61 @@ def find_similar( ) return sorted_results[:limit] + def list_memory_ids(self, agent_id: str) -> list[str]: + """Return all distinct memory_id values in the index for an agent. + + Used for orphan cleanup: find index entries whose vault files no longer exist. + """ + safe_agent = _sanitize_for_filter(agent_id) + try: + rows = ( + self.table.search() + .where(f'agent_id = "{safe_agent}"') + .select(["memory_id"]) + .to_list() + ) + return list({r["memory_id"] for r in rows}) + except Exception: + return [] + + def find_by_tag( + self, tag: str, agent_id: str, limit: int = 3 + ) -> list[dict[str, Any]]: + """Find memories with a specific tag, scoped to agent_id. + + Tags are stored as comma-joined strings, so LIKE match is used. + Returns deduplicated results by memory_id. + """ + safe_agent = _sanitize_for_filter(agent_id) + safe_tag = _sanitize_for_filter(tag) + try: + rows = ( + self.table.search() + .where(f'agent_id = "{safe_agent}" AND tags LIKE "%{safe_tag}%"') + .select(["memory_id", "chunk_text", "importance", "tags"]) + .limit(limit * 3) + .to_list() + ) + seen: set[str] = set() + results = [] + for row in rows: + mid = row["memory_id"] + if mid not in seen: + seen.add(mid) + results.append( + { + "memory_id": mid, + "content": row.get("chunk_text", ""), + "importance": row.get("importance", 0.5), + "tags": row.get("tags", ""), + } + ) + if len(results) >= limit: + break + return results + except Exception: + return [] + def reindex(self) -> None: """Rebuild the entire index from vault contents.""" try: @@ -276,7 +331,9 @@ def reindex(self) -> None: success_count = 0 fail_count = 0 - memstack_dir = Path(self._settings.vault_path).expanduser().resolve() / "memstack" + memstack_dir = ( + Path(self._settings.vault_path).expanduser().resolve() / "memstack" + ) if not memstack_dir.exists(): return @@ -309,6 +366,44 @@ def reindex(self) -> None: logger.info("Reindexed {} memories ({} failed)", success_count, fail_count) + def validate_dimension(self) -> None: + """Check if the existing table's vector dimension matches the current provider. + + If dimensions mismatch, log a warning and trigger a full reindex from vault. + Skip gracefully if no table exists yet or no embedding provider is configured. + """ + if self._embedding_provider is None: + return + + try: + table = self._db.open_table("memories") + except (ValueError, OSError): + return + + try: + schema = table.schema + vector_field = schema.field("vector") + table_dim = vector_field.type.list_size + except Exception: + return + + try: + test_vector = self._embedding_provider.embed(["__validate__"])[0] + except Exception: + logger.warning("Embedding provider failed during dimension validation") + return + provider_dim = len(test_vector) + + if provider_dim != table_dim: + logger.warning( + "Vector dimension mismatch: table has {}d vectors, " + "current provider produces {}d vectors. " + "Triggering reindex from vault.", + table_dim, + provider_dim, + ) + self.reindex() + def is_available(self) -> bool: """Check if the search index is accessible.""" try: diff --git a/tests/test_app.py b/tests/test_app.py index 2bdf092..c7139c5 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -41,6 +41,8 @@ def test_app_lifespan_yields_shared_objects(self): app.state.search_index = MagicMock() app.state.smart_write_pipeline = MagicMock() app.state.settings = MagicMock() + app.state.watcher = None + app.state.consolidator = None async def _run(): async with _app_lifespan(app) as ctx: diff --git a/tests/test_async_execution.py b/tests/test_async_execution.py index a21b5e3..96a52d1 100644 --- a/tests/test_async_execution.py +++ b/tests/test_async_execution.py @@ -8,7 +8,12 @@ from memstack.interfaces.rest import health, inject, memories, search, system_prompt MCP_SERVER_SOURCE = ( - Path(__file__).parent.parent / "src" / "memstack" / "interfaces" / "mcp" / "server.py" + Path(__file__).parent.parent + / "src" + / "memstack" + / "interfaces" + / "mcp" + / "server.py" ).read_text() MCP_TOOL_NAMES = [ diff --git a/tests/test_classify.py b/tests/test_classify.py new file mode 100644 index 0000000..e94079a --- /dev/null +++ b/tests/test_classify.py @@ -0,0 +1,58 @@ +"""Tests for content classification.""" + +from unittest.mock import MagicMock, patch + +from memstack.intelligence.classify import CATEGORIES, classify_content + + +class TestClassifyContent: + def test_returns_valid_category(self): + with patch("memstack.intelligence.classify._get_ollama_client") as mock_client: + mock_response = MagicMock() + mock_response.__getitem__ = MagicMock( + side_effect=lambda k: {"message": {"content": '{"category": "food"}'}}[ + k + ] + ) + mock_client.return_value.chat.return_value = mock_response + # Need to mock the nested access pattern + mock_client.return_value.chat.return_value = { + "message": {"content": '{"category": "food"}'} + } + result = classify_content( + "I love biryani", "llama3", "http://localhost:11434" + ) + assert result == "food" + + def test_falls_back_to_other_on_failure(self): + with patch("memstack.intelligence.classify._get_ollama_client") as mock_client: + mock_client.return_value.chat.side_effect = RuntimeError("LLM unavailable") + result = classify_content( + "Some content", "llama3", "http://localhost:11434" + ) + assert result == "other" + + def test_falls_back_to_other_on_unknown_category(self): + with patch("memstack.intelligence.classify._get_ollama_client") as mock_client: + mock_client.return_value.chat.return_value = { + "message": {"content": '{"category": "astronomy"}'} + } + result = classify_content( + "Some content", "llama3", "http://localhost:11434" + ) + assert result == "other" + + def test_handles_plain_string_response(self): + with patch("memstack.intelligence.classify._get_ollama_client") as mock_client: + mock_client.return_value.chat.return_value = { + "message": {"content": '"fitness"'} + } + result = classify_content( + "I run every day", "llama3", "http://localhost:11434" + ) + assert result == "fitness" + + def test_all_categories_are_valid(self): + assert len(CATEGORIES) == 10 + assert "food" in CATEGORIES + assert "other" in CATEGORIES diff --git a/tests/test_cli.py b/tests/test_cli.py index 8cd5e4d..deef920 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,6 +7,7 @@ from typer.testing import CliRunner +from memstack import __version__ from memstack.cli.cli import ( app, _read_state, @@ -477,7 +478,7 @@ async def _test(): assert response.status_code == 200 data = response.json() assert data["status"] == "healthy" - assert data["version"] == "1.4.3" + assert data["version"] == __version__ asyncio.run(_test()) diff --git a/tests/test_config.py b/tests/test_config.py index 2482161..ce067a2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -24,7 +24,6 @@ def test_vault_path_from_env(self, tmp_path: Path) -> None: vault.mkdir() with pytest.MonkeyPatch.context() as mp: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) - mp.delenv("MEMSTACK_OBSIDIAN_MODE", raising=False) mp.delenv("MEMSTACK_SHARED_MODE", raising=False) s = Settings() assert s.vault_path == vault @@ -35,11 +34,9 @@ def test_defaults(self, tmp_path: Path) -> None: vault.mkdir() with pytest.MonkeyPatch.context() as mp: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) - mp.delenv("MEMSTACK_OBSIDIAN_MODE", raising=False) mp.delenv("MEMSTACK_SHARED_MODE", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_INITIAL_SCORE", raising=False) s = Settings() - assert s.obsidian_mode is True assert s.shared_mode is False assert s.importance_initial_score == 0.5 @@ -49,11 +46,9 @@ def test_env_overrides(self, tmp_path: Path) -> None: vault.mkdir() with pytest.MonkeyPatch.context() as mp: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) - mp.setenv("MEMSTACK_OBSIDIAN_MODE", "false") mp.setenv("MEMSTACK_SHARED_MODE", "true") mp.setenv("MEMSTACK_IMPORTANCE_INITIAL_SCORE", "0.8") s = Settings() - assert s.obsidian_mode is False assert s.shared_mode is True assert s.importance_initial_score == 0.8 @@ -89,15 +84,13 @@ def test_search_defaults(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.delenv("MEMSTACK_EMBEDDING_PROVIDER", raising=False) mp.delenv("MEMSTACK_EMBEDDING_MODEL", raising=False) - mp.delenv("MEMSTACK_EMBEDDING_AUTOFALLBACK", raising=False) mp.delenv("MEMSTACK_CHUNK_MAX_TOKENS", raising=False) mp.delenv("MEMSTACK_CHUNK_OVERLAP_TOKENS", raising=False) mp.delenv("MEMSTACK_RRF_K", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_RERANK_WEIGHT", raising=False) s = Settings() - assert s.embedding_provider == "ollama" - assert s.embedding_model == "nomic-embed-text" - assert s.embedding_autofallback is True + assert s.embedding_provider == "fastembed" + assert s.embedding_model == "BAAI/bge-small-en-v1.5" assert s.chunk_max_tokens == 512 assert s.chunk_overlap_tokens == 50 assert s.rrf_k == 10 @@ -112,7 +105,6 @@ def test_smart_write_defaults(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.delenv("MEMSTACK_SIMILARITY_ADD_THRESHOLD", raising=False) mp.delenv("MEMSTACK_SIMILARITY_IGNORE_THRESHOLD", raising=False) - mp.delenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_DECAY_HALFLIFE", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_HIT_INCREMENT", raising=False) mp.delenv("MEMSTACK_LLM_MODEL", raising=False) @@ -120,7 +112,6 @@ def test_smart_write_defaults(self, tmp_path: Path) -> None: s = Settings() assert s.similarity_add_threshold == 0.25 assert s.similarity_ignore_threshold == 0.85 - assert s.similarity_ignore_enabled is False assert s.importance_decay_halflife == 7.0 assert s.importance_hit_increment == 0.05 assert s.llm_model == "llama3" @@ -134,7 +125,6 @@ def test_smart_write_env_overrides(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.setenv("MEMSTACK_SIMILARITY_ADD_THRESHOLD", "0.4") mp.setenv("MEMSTACK_SIMILARITY_IGNORE_THRESHOLD", "0.95") - mp.setenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", "true") mp.setenv("MEMSTACK_IMPORTANCE_DECAY_HALFLIFE", "14.0") mp.setenv("MEMSTACK_IMPORTANCE_HIT_INCREMENT", "0.1") mp.setenv("MEMSTACK_LLM_MODEL", "mistral") @@ -142,7 +132,6 @@ def test_smart_write_env_overrides(self, tmp_path: Path) -> None: s = Settings() assert s.similarity_add_threshold == 0.4 assert s.similarity_ignore_threshold == 0.95 - assert s.similarity_ignore_enabled is True assert s.importance_decay_halflife == 14.0 assert s.importance_hit_increment == 0.1 assert s.llm_model == "mistral" @@ -156,7 +145,6 @@ def test_search_env_overrides(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.setenv("MEMSTACK_EMBEDDING_PROVIDER", "fastembed") mp.setenv("MEMSTACK_EMBEDDING_MODEL", "BAAI/bge-small-en-v1.5") - mp.setenv("MEMSTACK_EMBEDDING_AUTOFALLBACK", "false") mp.setenv("MEMSTACK_CHUNK_MAX_TOKENS", "256") mp.setenv("MEMSTACK_CHUNK_OVERLAP_TOKENS", "25") mp.setenv("MEMSTACK_RRF_K", "30") @@ -164,7 +152,6 @@ def test_search_env_overrides(self, tmp_path: Path) -> None: s = Settings() assert s.embedding_provider == "fastembed" assert s.embedding_model == "BAAI/bge-small-en-v1.5" - assert s.embedding_autofallback is False assert s.chunk_max_tokens == 256 assert s.chunk_overlap_tokens == 25 assert s.rrf_k == 30 @@ -178,7 +165,6 @@ def test_intelligence_defaults(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.delenv("MEMSTACK_SIMILARITY_ADD_THRESHOLD", raising=False) mp.delenv("MEMSTACK_SIMILARITY_IGNORE_THRESHOLD", raising=False) - mp.delenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_DECAY_HALFLIFE", raising=False) mp.delenv("MEMSTACK_IMPORTANCE_HIT_INCREMENT", raising=False) mp.delenv("MEMSTACK_LLM_MODEL", raising=False) @@ -186,7 +172,6 @@ def test_intelligence_defaults(self, tmp_path: Path) -> None: s = Settings() assert s.similarity_add_threshold == 0.25 assert s.similarity_ignore_threshold == 0.85 - assert s.similarity_ignore_enabled is False assert s.importance_decay_halflife == 7.0 assert s.importance_hit_increment == 0.05 assert s.llm_model == "llama3" @@ -202,7 +187,6 @@ def test_intelligence_env_overrides(self, tmp_path: Path) -> None: mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) mp.setenv("MEMSTACK_SIMILARITY_ADD_THRESHOLD", "0.4") mp.setenv("MEMSTACK_SIMILARITY_IGNORE_THRESHOLD", "0.95") - mp.setenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", "true") mp.setenv("MEMSTACK_IMPORTANCE_DECAY_HALFLIFE", "14.0") mp.setenv("MEMSTACK_IMPORTANCE_HIT_INCREMENT", "0.1") mp.setenv("MEMSTACK_LLM_MODEL", "mistral") @@ -210,32 +194,11 @@ def test_intelligence_env_overrides(self, tmp_path: Path) -> None: s = Settings() assert s.similarity_add_threshold == 0.4 assert s.similarity_ignore_threshold == 0.95 - assert s.similarity_ignore_enabled is True assert s.importance_decay_halflife == 14.0 assert s.importance_hit_increment == 0.1 assert s.llm_model == "mistral" assert s.llm_host == "http://ollama:11434" - def test_similarity_ignore_enabled_defaults(self, tmp_path: Path) -> None: - """similarity_ignore_enabled defaults to False.""" - vault = tmp_path / "vault" - vault.mkdir() - with pytest.MonkeyPatch.context() as mp: - mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) - mp.delenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", raising=False) - s = Settings() - assert s.similarity_ignore_enabled is False - - def test_similarity_ignore_enabled_override(self, tmp_path: Path) -> None: - """similarity_ignore_enabled can be set via env var.""" - vault = tmp_path / "vault" - vault.mkdir() - with pytest.MonkeyPatch.context() as mp: - mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) - mp.setenv("MEMSTACK_SIMILARITY_IGNORE_ENABLED", "true") - s = Settings() - assert s.similarity_ignore_enabled is True - def test_get_settings_singleton(self, tmp_path: Path) -> None: """get_settings() returns the same instance on repeated calls.""" get_settings.cache_clear() @@ -385,3 +348,23 @@ def test_vault_cache_size_default(self, tmp_path: Path) -> None: mp.delenv("MEMSTACK_VAULT_CACHE_SIZE", raising=False) s = Settings() assert s.vault_cache_size == 512 + + def test_bom_env_file_stripped(self, tmp_path: Path) -> None: + """Settings with utf-8-sig strips BOM from .env files.""" + vault = tmp_path / "vault" + vault.mkdir() + env_file = tmp_path / ".env" + env_file.write_bytes( + b"\xef\xbb\xbfMEMSTACK_VAULT_PATH=" + str(vault).encode() + b"\n" + ) + s = Settings(_env_file=env_file) + assert s.vault_path == vault + + def test_bom_free_env_file_works(self, tmp_path: Path) -> None: + """Settings with utf-8-sig still works with BOM-free .env files.""" + vault = tmp_path / "vault" + vault.mkdir() + env_file = tmp_path / ".env" + env_file.write_text(f"MEMSTACK_VAULT_PATH={vault}\n", encoding="utf-8") + s = Settings(_env_file=env_file) + assert s.vault_path == vault diff --git a/tests/test_consolidation.py b/tests/test_consolidation.py new file mode 100644 index 0000000..9c869d9 --- /dev/null +++ b/tests/test_consolidation.py @@ -0,0 +1,832 @@ +"""Tests for background memory consolidation.""" + +from __future__ import annotations + +import asyncio +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from memstack.core.config import Settings +from memstack.core.models import Memory, MemoryListResponse, MemoryRead +from memstack.intelligence.consolidation import Consolidator + + +def _make_settings(tmp_path: Path, **overrides) -> Settings: + """Create Settings with a vault path and optional overrides.""" + vault = tmp_path / "vault" + vault.mkdir() + defaults = { + "consolidation_enabled": True, + "consolidation_interval": 3600, + "consolidation_batch_size": 20, + "consolidation_model": "", + "llm_model": "llama3", + "llm_host": "http://localhost:11434", + } + defaults.update(overrides) + with pytest.MonkeyPatch.context() as mp: + mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) + for key, val in defaults.items(): + mp.setenv(f"MEMSTACK_{key.upper()}", str(val)) + return Settings() + + +def _make_memory( + agent_id: str = "test-agent", + memory_id: str = "test-mem", + body: str = "test body", + importance: float = 0.5, + tags: list[str] | None = None, + updated: str = "2025-01-01T00:00:00+00:00", +) -> Memory: + """Create a Memory object for testing.""" + return Memory( + id=memory_id, + agent=agent_id, + type="memory", + importance=importance, + tags=tags or [], + created="2025-01-01T00:00:00+00:00", + updated=updated, + importance_updated="2025-01-01T00:00:00+00:00", + body=body, + ) + + +class TestConsolidatorBatchSelection: + """Batch selection picks oldest batch_size memories.""" + + def test_batch_selects_oldest_memories(self, tmp_path: Path) -> None: + """Consolidator selects the stalest (oldest updated) memories.""" + settings = _make_settings(tmp_path, consolidation_batch_size=2) + vault = MagicMock() + memories = [ + _make_memory(memory_id="new", updated="2025-05-01T00:00:00+00:00"), + _make_memory(memory_id="mid", updated="2025-03-01T00:00:00+00:00"), + _make_memory(memory_id="old", updated="2025-01-01T00:00:00+00:00"), + ] + vault.list_memories.return_value = MemoryListResponse( + memories=[ + MemoryRead( + id=m.id, + agent=m.agent, + type=m.type, + importance=m.importance, + tags=m.tags, + created=m.created, + updated=m.updated, + importance_updated=m.importance_updated, + body=m.body, + ) + for m in memories + ], + total=3, + limit=0, + offset=0, + ) + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consult_llm = MagicMock(return_value=[]) + + asyncio.run(consolidator._consolidate_agent("test-agent")) + + assert consolidator._consult_llm.call_count == 1 + batch = consolidator._consult_llm.call_args[0][0] + assert len(batch) == 2 + batch_ids = [m.id for m in batch] + assert "old" in batch_ids + assert "mid" in batch_ids + + def test_fewer_memories_than_batch_size(self, tmp_path: Path) -> None: + """If agent has fewer memories than batch_size, all are processed.""" + settings = _make_settings(tmp_path, consolidation_batch_size=20) + vault = MagicMock() + memories = [ + MemoryRead( + id="only-one", + agent="test-agent", + type="memory", + importance=0.5, + tags=[], + created="2025-01-01T00:00:00+00:00", + updated="2025-01-01T00:00:00+00:00", + importance_updated="2025-01-01T00:00:00+00:00", + body="just one memory", + ) + ] + vault.list_memories.return_value = MemoryListResponse( + memories=memories, total=1, limit=0, offset=0 + ) + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consult_llm = MagicMock(return_value=[]) + + asyncio.run(consolidator._consolidate_agent("test-agent")) + + assert consolidator._consult_llm.call_count == 1 + batch = consolidator._consult_llm.call_args[0][0] + assert len(batch) == 1 + + def test_skip_empty_agents(self, tmp_path: Path) -> None: + """Agents with no memories are skipped entirely.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + vault.list_memories.return_value = MemoryListResponse( + memories=[], total=0, limit=0, offset=0 + ) + consolidator = Consolidator(vault=vault, settings=settings) + + asyncio.run(consolidator._consolidate_agent("empty-agent")) + + vault.list_memories.assert_called_once_with("empty-agent", limit=0) + + def test_skip_shared_directory(self, tmp_path: Path) -> None: + """The shared/ directory is never processed.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + memstack_dir = tmp_path / "vault" / "memstack" + shared_dir = memstack_dir / "shared" + shared_dir.mkdir(parents=True) + vault.memstack_dir = memstack_dir + + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consolidate_agent = AsyncMock() + + asyncio.run(consolidator._consolidate_all_agents()) + + consolidator._consolidate_agent.assert_not_called() + + def test_stop_event_breaks_operations_loop(self, tmp_path: Path) -> None: + """Stop event set during operations loop breaks early.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + memories = [ + MemoryRead( + id="m1", + agent="test-agent", + type="memory", + importance=0.5, + tags=[], + created="2025-01-01T00:00:00+00:00", + updated="2025-01-01T00:00:00+00:00", + importance_updated="2025-01-01T00:00:00+00:00", + body="body1", + ) + ] + vault.list_memories.return_value = MemoryListResponse( + memories=memories, total=1, limit=0, offset=0 + ) + consolidator = Consolidator(vault=vault, settings=settings) + operations = [{"action": "unknown1"}, {"action": "unknown2"}] + consolidator._consult_llm = MagicMock(return_value=operations) + + apply_count = 0 + + def apply_side_effect(agent_id, op): + nonlocal apply_count + apply_count += 1 + consolidator.stop() + + consolidator._apply_operation = MagicMock(side_effect=apply_side_effect) + + asyncio.run(consolidator._consolidate_agent("test-agent")) + + consolidator._apply_operation.assert_called_once() + + +class TestConsolidatorOperations: + """Consolidation operations: rewrite, enrich, merge, split.""" + + def test_rewrite_preserves_id_updates_body(self, tmp_path: Path) -> None: + """Rewrite: preserves ID, updates body and updated timestamp.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="test-mem", body="old body") + vault.read.return_value = original + vault._memory_path.return_value = tmp_path / "test-mem.md" + + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "rewrite", "id": "test-mem", "body": "new body"} + + with patch("memstack.core.vault._atomic_write"): + consolidator._apply_operation("test-agent", op) + + vault.read.assert_called_once_with("test-agent", "test-mem") + vault._invalidate_caches.assert_called_once_with("test-agent") + + def test_enrich_preserves_id_adds_context(self, tmp_path: Path) -> None: + """Enrich: preserves ID, adds context to body, updates timestamp.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="test-mem", body="likes python") + vault.read.return_value = original + vault._memory_path.return_value = tmp_path / "test-mem.md" + + consolidator = Consolidator(vault=vault, settings=settings) + op = { + "action": "enrich", + "id": "test-mem", + "body": "likes python — preferred for data analysis and automation", + } + + with patch("memstack.core.vault._atomic_write"): + consolidator._apply_operation("test-agent", op) + + vault.read.assert_called_once_with("test-agent", "test-mem") + vault._invalidate_caches.assert_called_once_with("test-agent") + + def test_merge_creates_new_deletes_originals(self, tmp_path: Path) -> None: + """Merge: creates new memory with averaged importance, deletes originals.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + mem1 = _make_memory(memory_id="mem-1", importance=0.6, body="likes python") + mem2 = _make_memory(memory_id="mem-2", importance=0.4, body="python fan") + vault.read.side_effect = [mem1, mem2] + + new_memory = _make_memory( + memory_id="merged-new", importance=0.5, body="merged body" + ) + vault.write.return_value = new_memory + + consolidator = Consolidator(vault=vault, settings=settings) + op = { + "action": "merge", + "ids": ["mem-1", "mem-2"], + "body": "Strong preference for Python", + } + consolidator._apply_operation("test-agent", op) + + assert vault.read.call_count == 2 + vault.write.assert_called_once() + write_call = vault.write.call_args[0][0] + assert write_call.content == "Strong preference for Python" + assert write_call.importance == 0.5 + assert vault.delete.call_count == 2 + + def test_split_creates_new_deletes_original(self, tmp_path: Path) -> None: + """Split: creates new memories with original importance, deletes original.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory( + memory_id="multi-topic", + importance=0.7, + tags=["preference"], + body="likes dark mode. uses VS Code", + ) + vault.read.return_value = original + + consolidator = Consolidator(vault=vault, settings=settings) + op = { + "action": "split", + "id": "multi-topic", + "bodies": [ + "Prefers dark mode in all editors", + "Uses VS Code as primary IDE", + ], + } + consolidator._apply_operation("test-agent", op) + + assert vault.write.call_count == 2 + for call in vault.write.call_args_list: + write = call[0][0] + assert write.importance == 0.7 + assert write.tags == ["preference"] + vault.delete.assert_called_once_with("test-agent", "multi-topic") + + def test_skip_operation_on_missing_id(self, tmp_path: Path) -> None: + """Missing memory ID causes entire operation to be skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + vault.read.side_effect = FileNotFoundError("not found") + + consolidator = Consolidator(vault=vault, settings=settings) + + op = {"action": "rewrite", "id": "missing-id", "body": "new body"} + consolidator._apply_operation("test-agent", op) + + vault.write.assert_not_called() + vault.delete.assert_not_called() + + op = {"action": "merge", "ids": ["missing-1", "missing-2"], "body": "merged"} + vault.read.side_effect = FileNotFoundError("not found") + consolidator._apply_operation("test-agent", op) + vault.write.assert_not_called() + + def test_llm_failure_continues(self, tmp_path: Path) -> None: + """LLM failure returns empty operations list and consolidation continues.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + + with patch( + "memstack.intelligence.consolidation._get_ollama_client", + side_effect=Exception("LLM unavailable"), + ): + operations = consolidator._consult_llm( + [], "llama3", "http://localhost:11434" + ) + + assert operations == [] + + def test_stop_cancels_iteration(self, tmp_path: Path) -> None: + """stop() sets the stop event and cancels next iteration.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + + assert not consolidator._stop_event.is_set() + consolidator.stop() + assert consolidator._stop_event.is_set() + + def test_unknown_action_skipped(self, tmp_path: Path) -> None: + """Unknown consolidation action is skipped with a warning.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "unknown_action"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + vault.write.assert_not_called() + vault.delete.assert_not_called() + + def test_rewrite_missing_id(self, tmp_path: Path) -> None: + """Rewrite with missing id is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "rewrite", "body": "new body"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_rewrite_missing_body(self, tmp_path: Path) -> None: + """Rewrite with missing body is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "rewrite", "id": "test-mem"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_enrich_missing_id(self, tmp_path: Path) -> None: + """Enrich with missing id is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "enrich", "body": "enriched body"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_enrich_missing_body(self, tmp_path: Path) -> None: + """Enrich with missing body is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "enrich", "id": "test-mem"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_enrich_memory_not_found(self, tmp_path: Path) -> None: + """Enrich skips when the memory is not found.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + vault.read.side_effect = FileNotFoundError + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "enrich", "id": "missing-mem", "body": "enriched body"} + + consolidator._apply_operation("test-agent", op) + + vault.write.assert_not_called() + + def test_merge_missing_ids(self, tmp_path: Path) -> None: + """Merge with missing ids is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "merge", "body": "merged body"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_merge_missing_body(self, tmp_path: Path) -> None: + """Merge with missing body is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "merge", "ids": ["m1", "m2"]} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_merge_delete_file_not_found(self, tmp_path: Path) -> None: + """Merge continues when delete raises FileNotFoundError.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + mem1 = _make_memory(memory_id="mem-1", importance=0.5) + vault.read.return_value = mem1 + new_memory = _make_memory(memory_id="merged-new") + vault.write.return_value = new_memory + vault.delete.side_effect = FileNotFoundError + + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "merge", "ids": ["mem-1"], "body": "merged"} + + consolidator._apply_operation("test-agent", op) + + vault.write.assert_called_once() + vault.delete.assert_called_once_with("test-agent", "mem-1") + + def test_split_missing_id(self, tmp_path: Path) -> None: + """Split with missing id is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "bodies": ["part1"]} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_split_missing_bodies(self, tmp_path: Path) -> None: + """Split with missing bodies is skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "multi-topic"} + + consolidator._apply_operation("test-agent", op) + + vault.read.assert_not_called() + + def test_split_memory_not_found(self, tmp_path: Path) -> None: + """Split skips when the memory is not found.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + vault.read.side_effect = FileNotFoundError + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "missing-mem", "bodies": ["part1"]} + + consolidator._apply_operation("test-agent", op) + + vault.write.assert_not_called() + + def test_split_skips_empty_body(self, tmp_path: Path) -> None: + """Split skips empty strings in bodies list.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="multi-topic", importance=0.7, tags=["tag1"]) + vault.read.return_value = original + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "multi-topic", "bodies": ["", " ", "valid"]} + + consolidator._apply_operation("test-agent", op) + + assert vault.write.call_count == 1 + vault.delete.assert_called_once_with("test-agent", "multi-topic") + + def test_split_all_empty_bodies_keeps_original(self, tmp_path: Path) -> None: + """Split with all-empty bodies does not delete the original memory.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="multi-topic", importance=0.7, tags=["tag1"]) + vault.read.return_value = original + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "multi-topic", "bodies": ["", " "]} + + consolidator._apply_operation("test-agent", op) + + vault.write.assert_not_called() + vault.delete.assert_not_called() + + def test_split_skips_non_string_body(self, tmp_path: Path) -> None: + """Split skips non-string bodies from malformed LLM output.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="multi-topic", importance=0.7, tags=["tag1"]) + vault.read.return_value = original + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "multi-topic", "bodies": [123, None, "valid"]} + + consolidator._apply_operation("test-agent", op) + + assert vault.write.call_count == 1 + vault.delete.assert_called_once_with("test-agent", "multi-topic") + + def test_split_delete_file_not_found(self, tmp_path: Path) -> None: + """Split continues when delete raises FileNotFoundError.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory(memory_id="multi-topic", importance=0.7, tags=["tag1"]) + vault.read.return_value = original + vault.delete.side_effect = FileNotFoundError + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "split", "id": "multi-topic", "bodies": ["part1"]} + + consolidator._apply_operation("test-agent", op) + + vault.write.assert_called_once() + + +class TestConsolidatorSharedMode: + """Shared mode rewrite, enrich, merge and split operations.""" + + def test_shared_mode_rewrite(self, tmp_path: Path) -> None: + """Rewrite in shared mode updates the shared copy.""" + settings = _make_settings(tmp_path, shared_mode="true") + vault = MagicMock() + original = _make_memory(memory_id="test-mem", body="old body") + vault.read.return_value = original + vault._memory_path.return_value = tmp_path / "test-mem.md" + vault._settings.shared_mode = True + + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "rewrite", "id": "test-mem", "body": "new body"} + + with patch("memstack.core.vault._atomic_write"): + consolidator._apply_operation("test-agent", op) + + vault._update_shared_copy.assert_called_once() + vault._invalidate_caches.assert_called_once_with("test-agent") + + def test_shared_mode_enrich(self, tmp_path: Path) -> None: + """Enrich in shared mode updates the shared copy.""" + settings = _make_settings(tmp_path, shared_mode="true") + vault = MagicMock() + original = _make_memory(memory_id="test-mem", body="likes python") + vault.read.return_value = original + vault._memory_path.return_value = tmp_path / "test-mem.md" + vault._settings.shared_mode = True + + consolidator = Consolidator(vault=vault, settings=settings) + op = { + "action": "enrich", + "id": "test-mem", + "body": "likes python — preferred for data analysis", + } + + with patch("memstack.core.vault._atomic_write"): + consolidator._apply_operation("test-agent", op) + + vault._update_shared_copy.assert_called_once() + vault._invalidate_caches.assert_called_once_with("test-agent") + + def test_shared_mode_merge(self, tmp_path: Path) -> None: + """Merge in shared mode creates new memory and deletes originals via vault.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + mem1 = _make_memory(memory_id="mem-1", importance=0.6, body="topic a") + mem2 = _make_memory(memory_id="mem-2", importance=0.4, body="topic a too") + vault.read.side_effect = [mem1, mem2] + + new_memory = _make_memory(memory_id="merged-new", importance=0.5, body="merged") + vault.write.return_value = new_memory + + consolidator = Consolidator(vault=vault, settings=settings) + op = {"action": "merge", "ids": ["mem-1", "mem-2"], "body": "merged topic a"} + consolidator._apply_operation("test-agent", op) + + vault.write.assert_called_once() + assert vault.delete.call_count == 2 + + def test_shared_mode_split(self, tmp_path: Path) -> None: + """Split in shared mode creates new memories and deletes original via vault.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + original = _make_memory( + memory_id="multi-topic", + importance=0.7, + tags=["preference"], + body="likes dark mode. uses VS Code", + ) + vault.read.return_value = original + + consolidator = Consolidator(vault=vault, settings=settings) + op = { + "action": "split", + "id": "multi-topic", + "bodies": ["Prefers dark mode", "Uses VS Code"], + } + consolidator._apply_operation("test-agent", op) + + assert vault.write.call_count == 2 + vault.delete.assert_called_once_with("test-agent", "multi-topic") + + +class TestConsolidationConfig: + """Consolidation config validators.""" + + def test_interval_validation_rejects_below_60(self, tmp_path: Path) -> None: + """consolidation_interval < 60 is rejected.""" + from pydantic import ValidationError + + vault = tmp_path / "vault" + vault.mkdir() + with pytest.MonkeyPatch.context() as mp: + mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) + mp.setenv("MEMSTACK_CONSOLIDATION_INTERVAL", "30") + with pytest.raises(ValidationError, match="consolidation_interval"): + Settings() + + def test_batch_size_validation_rejects_zero(self, tmp_path: Path) -> None: + """consolidation_batch_size < 1 is rejected.""" + from pydantic import ValidationError + + vault = tmp_path / "vault" + vault.mkdir() + with pytest.MonkeyPatch.context() as mp: + mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) + mp.setenv("MEMSTACK_CONSOLIDATION_BATCH_SIZE", "0") + with pytest.raises(ValidationError, match="consolidation_batch_size"): + Settings() + + def test_batch_size_validation_rejects_over_100(self, tmp_path: Path) -> None: + """consolidation_batch_size > 100 is rejected.""" + from pydantic import ValidationError + + vault = tmp_path / "vault" + vault.mkdir() + with pytest.MonkeyPatch.context() as mp: + mp.setenv("MEMSTACK_VAULT_PATH", str(vault)) + mp.setenv("MEMSTACK_CONSOLIDATION_BATCH_SIZE", "200") + with pytest.raises(ValidationError, match="consolidation_batch_size"): + Settings() + + +class TestConsolidatorRun: + """Tests for the Consolidator.run() async loop.""" + + def test_run_consolidates_after_interval(self, tmp_path: Path) -> None: + """run() calls _consolidate_all_agents after interval timeout.""" + settings = _make_settings(tmp_path) + settings.consolidation_interval = 0.01 + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consolidate_all_agents = AsyncMock() + + async def _run(): + task = asyncio.create_task(consolidator.run()) + await asyncio.sleep(0.05) + consolidator.stop() + await task + + asyncio.run(asyncio.wait_for(_run(), timeout=2.0)) + + consolidator._consolidate_all_agents.assert_called() + + def test_run_breaks_on_stop_set_during_wait(self, tmp_path: Path) -> None: + """run() breaks when stop event is set during wait_for.""" + settings = _make_settings(tmp_path) + settings.consolidation_interval = 60 + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consolidate_all_agents = AsyncMock() + + async def _run(): + task = asyncio.create_task(consolidator.run()) + await asyncio.sleep(0.01) + consolidator.stop() + await asyncio.wait_for(task, timeout=1.0) + + asyncio.run(_run()) + + consolidator._consolidate_all_agents.assert_not_called() + + +class TestConsolidateAllAgents: + """Tests for Consolidator._consolidate_all_agents.""" + + def test_returns_early_if_memstack_dir_missing(self, tmp_path: Path) -> None: + """Returns immediately when memstack_dir does not exist.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + memstack_dir = tmp_path / "nonexistent" + vault.memstack_dir = memstack_dir + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consolidate_agent = AsyncMock() + + asyncio.run(consolidator._consolidate_all_agents()) + + consolidator._consolidate_agent.assert_not_called() + + def test_skips_non_directory_entries(self, tmp_path: Path) -> None: + """Non-directory entries in memstack_dir are skipped.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + memstack_dir = tmp_path / "vault" / "memstack" + memstack_dir.mkdir(parents=True) + (memstack_dir / "somefile.txt").touch() + (memstack_dir / "agent-1").mkdir() + vault.memstack_dir = memstack_dir + consolidator = Consolidator(vault=vault, settings=settings) + consolidator._consolidate_agent = AsyncMock() + + asyncio.run(consolidator._consolidate_all_agents()) + + consolidator._consolidate_agent.assert_called_once_with("agent-1") + + def test_breaks_on_stop_event(self, tmp_path: Path) -> None: + """Stops iterating agents when stop event is set.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + memstack_dir = tmp_path / "vault" / "memstack" + memstack_dir.mkdir(parents=True) + (memstack_dir / "agent-1").mkdir() + (memstack_dir / "agent-2").mkdir() + vault.memstack_dir = memstack_dir + consolidator = Consolidator(vault=vault, settings=settings) + + async def consolidate_and_stop(agent_id): + consolidator.stop() + + consolidator._consolidate_agent = AsyncMock(side_effect=consolidate_and_stop) + + asyncio.run(consolidator._consolidate_all_agents()) + + consolidator._consolidate_agent.assert_called_once_with("agent-1") + + +class TestConsolidatorLLM: + """Tests for Consolidator._consult_llm.""" + + def test_consult_llm_returns_operations(self, tmp_path: Path) -> None: + """Successful LLM call returns parsed operations list.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": { + "content": '{"operations": [{"action": "rewrite", "id": "m1", "body": "new"}]}' + } + } + batch = [_make_memory(memory_id="m1", body="old")] + + with patch( + "memstack.intelligence.consolidation._get_ollama_client", + return_value=mock_client, + ): + operations = consolidator._consult_llm( + batch, "llama3", "http://localhost:11434" + ) + + assert len(operations) == 1 + assert operations[0]["action"] == "rewrite" + + def test_consult_llm_non_list_operations(self, tmp_path: Path) -> None: + """LLM returning non-list operations value returns empty list.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": {"content": '{"operations": "not-a-list"}'} + } + batch = [_make_memory(memory_id="m1", body="old")] + + with patch( + "memstack.intelligence.consolidation._get_ollama_client", + return_value=mock_client, + ): + operations = consolidator._consult_llm( + batch, "llama3", "http://localhost:11434" + ) + + assert operations == [] + + def test_consult_llm_fenced_json_stripped(self, tmp_path: Path) -> None: + """_consult_llm correctly parses LLM responses wrapped in code fences.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + consolidator = Consolidator(vault=vault, settings=settings) + + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": { + "content": '```json\n{"operations": [{"action": "rewrite", "id": "m1", "body": "new"}]}\n```' + } + } + batch = [_make_memory(memory_id="m1", body="old")] + + with patch( + "memstack.intelligence.consolidation._get_ollama_client", + return_value=mock_client, + ): + operations = consolidator._consult_llm( + batch, "llama3", "http://localhost:11434" + ) + + assert len(operations) == 1 + assert operations[0]["action"] == "rewrite" diff --git a/tests/test_embeddings.py b/tests/test_embeddings.py index 63af4fa..c40e9d6 100644 --- a/tests/test_embeddings.py +++ b/tests/test_embeddings.py @@ -115,25 +115,15 @@ def test_creates_fastembed_provider(self): provider = create_embedding_provider(settings) assert isinstance(provider, FastembedProvider) - def test_falls_back_to_fastembed_when_ollama_unavailable(self): + def test_raises_when_ollama_unavailable(self): settings = Settings( vault_path=Path("/tmp/test"), embedding_provider="ollama", - embedding_autofallback=True, ) with patch.object(OllamaProvider, "check_available", return_value=False): - with patch.object(FastembedProvider, "check_available", return_value=True): - provider = create_embedding_provider(settings) - assert isinstance(provider, FastembedProvider) - - def test_raises_when_ollama_unavailable_and_no_autofallback(self): - settings = Settings( - vault_path=Path("/tmp/test"), - embedding_provider="ollama", - embedding_autofallback=False, - ) - with patch.object(OllamaProvider, "check_available", return_value=False): - with pytest.raises(RuntimeError, match="Ollama is unavailable"): + with pytest.raises( + RuntimeError, match="Ollama embedding provider is unavailable" + ): create_embedding_provider(settings) def test_raises_on_unknown_provider(self): @@ -144,17 +134,6 @@ def test_raises_on_unknown_provider(self): with pytest.raises(ValueError, match="Unknown embedding provider"): create_embedding_provider(settings) - def test_raises_when_both_providers_unavailable(self): - settings = Settings( - vault_path=Path("/tmp/test"), - embedding_provider="ollama", - embedding_autofallback=True, - ) - with patch.object(OllamaProvider, "check_available", return_value=False): - with patch.object(FastembedProvider, "check_available", return_value=False): - with pytest.raises(RuntimeError, match="Both Ollama and fastembed"): - create_embedding_provider(settings) - def test_raises_when_fastembed_explicitly_unavailable(self): settings = Settings( vault_path=Path("/tmp/test"), diff --git a/tests/test_llm.py b/tests/test_llm.py index 0bb9956..68278e0 100644 --- a/tests/test_llm.py +++ b/tests/test_llm.py @@ -4,7 +4,11 @@ import pytest -from memstack.intelligence.llm import _reset_ollama_client, consult_llm +from memstack.intelligence.llm import ( + _reset_ollama_client, + consult_llm, + strip_code_fences, +) @pytest.fixture(autouse=True) @@ -165,3 +169,76 @@ def test_merge_decision(self) -> None: assert result.decision == "merge" assert result.reasoning == "Adds new facts to existing memory" + assert result.merged_content is None + + def test_merge_decision_with_merged_content(self) -> None: + similar = [ + {"memory_id": "mem-1", "content": "favourite bike is Honda", "score": 0.7} + ] + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": { + "content": '{"decision": "merge", "reasoning": "Same topic", "merged_content": "Favourite bikes are Honda and KTM Super Duke"}' + } + } + with patch("memstack.intelligence.llm.ollama.Client", return_value=mock_client): + result = consult_llm("favourite bike is KTM Super Duke", similar) + + assert result.decision == "merge" + assert result.merged_content == "Favourite bikes are Honda and KTM Super Duke" + + def test_add_decision_has_no_merged_content(self) -> None: + similar = [{"memory_id": "mem-1", "content": "some content", "score": 0.5}] + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": {"content": '{"decision": "add", "reasoning": "New info"}'} + } + with patch("memstack.intelligence.llm.ollama.Client", return_value=mock_client): + result = consult_llm("incoming text", similar) + + assert result.decision == "add" + assert result.merged_content is None + + +class TestStripCodeFences: + """Tests for strip_code_fences utility.""" + + def test_bare_json_passes_through(self) -> None: + """Bare JSON is returned unchanged.""" + text = '{"decision": "add", "reasoning": "New info"}' + assert strip_code_fences(text) == text + + def test_json_code_fence_stripped(self) -> None: + """```json ... ``` fences are stripped.""" + text = '```json\n{"decision": "merge", "reasoning": "test"}\n```' + assert strip_code_fences(text) == '{"decision": "merge", "reasoning": "test"}' + + def test_plain_code_fence_stripped(self) -> None: + """``` ... ``` fences without language tag are stripped.""" + text = '```\n{"decision": "update", "reasoning": "test"}\n```' + assert strip_code_fences(text) == '{"decision": "update", "reasoning": "test"}' + + def test_whitespace_around_fences(self) -> None: + """Leading/trailing whitespace around fences is handled.""" + text = ' ```json\n{"statements": ["fact 1", "fact 2"]}\n``` ' + assert strip_code_fences(text) == '{"statements": ["fact 1", "fact 2"]}' + + def test_no_match_returns_original(self) -> None: + """Text without fences is returned as-is.""" + text = "plain text, not json at all" + assert strip_code_fences(text) == text + + def test_consult_llm_with_fenced_json(self) -> None: + """consult_llm correctly parses LLM responses wrapped in code fences.""" + similar = [{"memory_id": "mem-1", "content": "content", "score": 0.5}] + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": { + "content": '```json\n{"decision": "merge", "reasoning": "Adds facts"}\n```' + } + } + with patch("memstack.intelligence.llm.ollama.Client", return_value=mock_client): + result = consult_llm("incoming text", similar) + + assert result.decision == "merge" + assert result.reasoning == "Adds facts" diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index 0fc4529..932177c 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -245,8 +245,10 @@ def test_memory_list_returns_memories(self, mcp_server_with_data): {"agent_id": "list-agent"}, ) ) - assert isinstance(data, list) - assert len(data) >= 2 + assert isinstance(data, dict) + assert "memories" in data + assert "total" in data + assert len(data["memories"]) >= 2 def test_memory_list_empty_agent(self, mcp_server_with_data): data = asyncio.run( @@ -256,8 +258,9 @@ def test_memory_list_empty_agent(self, mcp_server_with_data): {"agent_id": "no-memories-agent"}, ) ) - assert isinstance(data, list) - assert len(data) == 0 + assert isinstance(data, dict) + assert data["memories"] == [] + assert data["total"] == 0 class TestCreateMcpServer: @@ -388,7 +391,7 @@ def test_memory_delete_no_context(self): def test_memory_list_no_context(self): fn = self._get_tool_fn("memory_list") result = asyncio.run(fn(agent_id="agent", ctx=None)) - assert result == [{"error": "No context available"}] + assert result == {"error": "No context available"} class TestMcpSearchErrorPaths: @@ -630,7 +633,7 @@ def test_mcp_health_endpoint_returns_healthy(self): assert response.status_code == 200 data = response.json() assert data["status"] == "healthy" - assert data["version"] == "1.4.3" + assert data["version"] == __version__ def test_mcp_health_endpoint_has_no_mcp_port(self): """MCP /health returns status and version only, not mcp_port.""" @@ -643,11 +646,11 @@ def test_mcp_health_endpoint_has_no_mcp_port(self): data = response.json() assert "mcp_port" not in data - def test_mcp_server_version_is_1_4_2(self): + def test_mcp_server_version_matches_package(self): from memstack.interfaces.mcp import create_mcp_server mcp = create_mcp_server() - assert mcp.version == "1.4.3" + assert mcp.version == __version__ class TestMemoryGetSystemPrompt: diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index da8d5c2..f812a64 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -7,16 +7,13 @@ from memstack.intelligence.pipeline import SmartWritePipeline -def _make_settings( - tmp_path, shared_mode=False, similarity_ignore_enabled=False -) -> Settings: +def _make_settings(tmp_path, shared_mode=False) -> Settings: return Settings( vault_path=tmp_path / "vault", log_file=tmp_path / "test.log", index_path=tmp_path / "index", similarity_add_threshold=0.25, similarity_ignore_threshold=0.85, - similarity_ignore_enabled=similarity_ignore_enabled, shared_mode=shared_mode, ) @@ -78,9 +75,9 @@ def test_low_similarity_adds_new(self, tmp_path) -> None: assert result.similarity_score is None vault.write.assert_called_once() - def test_high_similarity_goes_to_llm_when_ignore_disabled(self, tmp_path) -> None: - """When similarity_ignore_enabled=False (default), high similarity goes to LLM.""" - settings = _make_settings(tmp_path, similarity_ignore_enabled=False) + def test_high_similarity_goes_to_llm(self, tmp_path) -> None: + """High similarity always goes to LLM for decision.""" + settings = _make_settings(tmp_path) vault = MagicMock() search_index = MagicMock() search_index.find_similar.return_value = [ @@ -95,7 +92,7 @@ def test_high_similarity_goes_to_llm_when_ignore_disabled(self, tmp_path) -> Non with patch("memstack.intelligence.pipeline.consult_llm") as mock_consult: mock_consult.return_value = MagicMock( - decision="update", reasoning="Updated version" + decision="update", reasoning="Updated version", merged_content=None ) pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings @@ -106,31 +103,6 @@ def test_high_similarity_goes_to_llm_when_ignore_disabled(self, tmp_path) -> Non assert result.decision == "updated" mock_consult.assert_called_once() - def test_high_similarity_auto_ignores_when_toggle_enabled(self, tmp_path) -> None: - """When similarity_ignore_enabled=True, old auto-ignore behavior restored.""" - settings = _make_settings(tmp_path, similarity_ignore_enabled=True) - vault = MagicMock() - search_index = MagicMock() - search_index.find_similar.return_value = [ - { - "memory_id": "existing-mem", - "score": 0.95, - "content": "same stuff", - "importance": 0.7, - } - ] - - pipeline = SmartWritePipeline( - vault=vault, search_index=search_index, settings=settings - ) - write = MemoryWrite(content="Same info", agent_id="test-agent") - result = pipeline.process(write) - - assert result.decision == "ignored" - assert result.memory_id == "existing-mem" - assert result.similarity_score == 0.95 - vault.write.assert_not_called() - @patch("memstack.intelligence.pipeline.consult_llm") def test_ambiguous_zone_llm_says_add(self, mock_consult, tmp_path) -> None: settings = _make_settings(tmp_path) @@ -145,7 +117,9 @@ def test_ambiguous_zone_llm_says_add(self, mock_consult, tmp_path) -> None: } ] vault.write.return_value = _make_memory("new-mem-test-agent-2025-01-01") - mock_consult.return_value = MagicMock(decision="add", reasoning="New info") + mock_consult.return_value = MagicMock( + decision="add", reasoning="New info", merged_content=None + ) pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings @@ -171,7 +145,7 @@ def test_ambiguous_zone_llm_says_update(self, mock_consult, tmp_path) -> None: ] vault.update.return_value = _make_memory("updated-mem-test-agent-2025-01-01") mock_consult.return_value = MagicMock( - decision="update", reasoning="Better version" + decision="update", reasoning="Better version", merged_content=None ) pipeline = SmartWritePipeline( @@ -198,7 +172,9 @@ def test_ambiguous_zone_llm_says_ignore(self, mock_consult, tmp_path) -> None: "importance": 0.5, } ] - mock_consult.return_value = MagicMock(decision="ignore", reasoning="Duplicate") + mock_consult.return_value = MagicMock( + decision="ignore", reasoning="Duplicate", merged_content=None + ) pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings @@ -237,7 +213,9 @@ def test_update_preserves_importance(self, tmp_path) -> None: vault.update.return_value = _make_memory("updated-mem-test-agent-2025-01-01") with patch("memstack.intelligence.pipeline.consult_llm") as mock_consult: - mock_consult.return_value = MagicMock(decision="update", reasoning="Better") + mock_consult.return_value = MagicMock( + decision="update", reasoning="Better", merged_content=None + ) pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings ) @@ -336,7 +314,9 @@ def test_llm_says_merge(self, mock_consult, tmp_path) -> None: ) vault.merge.return_value = merged_memory mock_consult.return_value = MagicMock( - decision="merge", reasoning="Adds new facts to existing memory" + decision="merge", + reasoning="Adds new facts to existing memory", + merged_content=None, ) pipeline = SmartWritePipeline( @@ -378,7 +358,7 @@ def test_merge_preserves_importance(self, mock_consult, tmp_path) -> None: merged_memory = _make_memory("old-mem", body="old content\n\nnew content") vault.merge.return_value = merged_memory mock_consult.return_value = MagicMock( - decision="merge", reasoning="Supplemental info" + decision="merge", reasoning="Supplemental info", merged_content=None ) pipeline = SmartWritePipeline( @@ -411,7 +391,9 @@ def test_merge_with_shared_mode_reindexes_shared_copy( "existing-mem", body="existing content\n\nnew content" ) vault.merge.return_value = merged_memory - mock_consult.return_value = MagicMock(decision="merge", reasoning="Adds facts") + mock_consult.return_value = MagicMock( + decision="merge", reasoning="Adds facts", merged_content=None + ) pipeline = SmartWritePipeline( vault=vault, search_index=search_index, settings=settings @@ -424,7 +406,139 @@ def test_merge_with_shared_mode_reindexes_shared_copy( assert search_index.add.call_count == 2 assert search_index.delete.call_count == 2 # old private ID + old shared slug - def test_similarity_ignore_enabled_defaults_to_false(self, tmp_path) -> None: - """similarity_ignore_enabled defaults to False.""" + @patch("memstack.intelligence.pipeline.consult_llm") + def test_merge_with_llm_merged_content(self, mock_consult, tmp_path) -> None: + """LLM-provided merged_content is passed through to vault.merge.""" + settings = _make_settings(tmp_path) + vault = MagicMock() + search_index = MagicMock() + search_index.find_similar.return_value = [ + { + "memory_id": "existing-mem", + "score": 0.7, + "content": "I drink two black coffees every morning", + "importance": 0.5, + } + ] + merged_memory = _make_memory( + "existing-mem", + body="I drink five black coffees every morning and take 3g of creatine", + ) + vault.merge.return_value = merged_memory + mock_consult.return_value = MagicMock( + decision="merge", + reasoning="Same morning routine topic", + merged_content="I drink five black coffees every morning and take 3g of creatine", + ) + + pipeline = SmartWritePipeline( + vault=vault, search_index=search_index, settings=settings + ) + write = MemoryWrite( + content="I take 3g of creatine in my morning coffee", agent_id="test-agent" + ) + result = pipeline.process(write) + + assert result.decision == "merged" + call_kwargs = vault.merge.call_args.kwargs + assert ( + call_kwargs["merged_body"] + == "I drink five black coffees every morning and take 3g of creatine" + ) + + +class TestCategoryClassification: + """Tests for category tagging in the pipeline.""" + + @patch("memstack.intelligence.pipeline.classify_content") + def test_category_tag_added_to_write(self, mock_classify, tmp_path) -> None: + mock_classify.return_value = "food" + settings = _make_settings(tmp_path) + vault = MagicMock() + search_index = MagicMock() + search_index.find_similar.return_value = [] + vault.write.return_value = _make_memory("new-mem-test-agent-2025-01-01") + + pipeline = SmartWritePipeline( + vault=vault, search_index=search_index, settings=settings + ) + write = MemoryWrite(content="I love biryani", agent_id="test-agent") + result = pipeline.process(write) + + assert result.decision == "added" + # The write.tags should now include cat:food + call_args = vault.write.call_args[0][0] + assert "cat:food" in call_args.tags + + @patch("memstack.intelligence.pipeline.classify_content") + def test_category_tag_not_duplicated(self, mock_classify, tmp_path) -> None: + mock_classify.return_value = "food" + settings = _make_settings(tmp_path) + vault = MagicMock() + search_index = MagicMock() + search_index.find_similar.return_value = [] + vault.write.return_value = _make_memory("new-mem-test-agent-2025-01-01") + + pipeline = SmartWritePipeline( + vault=vault, search_index=search_index, settings=settings + ) + write = MemoryWrite( + content="I love biryani", agent_id="test-agent", tags=["cat:food"] + ) + result = pipeline.process(write) + + assert result.decision == "added" + call_args = vault.write.call_args[0][0] + # cat:food should appear only once + assert call_args.tags.count("cat:food") == 1 + + @patch("memstack.intelligence.pipeline.classify_content") + def test_category_falls_back_to_other(self, mock_classify, tmp_path) -> None: + mock_classify.return_value = "other" settings = _make_settings(tmp_path) - assert settings.similarity_ignore_enabled is False + vault = MagicMock() + search_index = MagicMock() + search_index.find_similar.return_value = [] + vault.write.return_value = _make_memory("new-mem-test-agent-2025-01-01") + + pipeline = SmartWritePipeline( + vault=vault, search_index=search_index, settings=settings + ) + write = MemoryWrite(content="Something random", agent_id="test-agent") + result = pipeline.process(write) + + assert result.decision == "added" + call_args = vault.write.call_args[0][0] + assert "cat:other" in call_args.tags + + @patch("memstack.intelligence.pipeline.classify_content") + def test_category_matches_injected_into_similar(self, mock_classify, tmp_path) -> None: + mock_classify.return_value = "food" + settings = _make_settings(tmp_path) + vault = MagicMock() + search_index = MagicMock() + search_index.find_similar.return_value = [ + {"memory_id": "existing-mem", "score": 0.3, "content": "old stuff", "importance": 0.5} + ] + search_index.find_by_tag.return_value = [ + {"memory_id": "food-match", "content": "I like pasta", "importance": 0.6, "tags": "cat:food"} + ] + search_index.is_available.return_value = True + vault.write.return_value = _make_memory("new-mem-test-agent-2025-01-01") + + with patch("memstack.intelligence.pipeline.consult_llm") as mock_consult: + mock_consult.return_value = MagicMock( + decision="add", reasoning="Different topic", merged_content=None + ) + pipeline = SmartWritePipeline( + vault=vault, search_index=search_index, settings=settings + ) + write = MemoryWrite(content="I love biryani", agent_id="test-agent") + pipeline.process(write) + + # find_by_tag should have been called with cat:food + search_index.find_by_tag.assert_called_once_with( + tag="cat:food", agent_id="test-agent", limit=3 + ) + # consult_llm should have been called with category + assert mock_consult.call_args.kwargs.get("incoming_category") == "food" diff --git a/tests/test_routes_health.py b/tests/test_routes_health.py index 82c87a1..0131209 100644 --- a/tests/test_routes_health.py +++ b/tests/test_routes_health.py @@ -3,6 +3,7 @@ from fastapi.testclient import TestClient +from memstack import __version__ from memstack.core.config import Settings from memstack.interfaces.rest.app import create_app @@ -13,7 +14,7 @@ def test_health_returns_200_when_vault_exists(self, test_client: TestClient): assert response.status_code == 200 data = response.json() assert data["status"] == "healthy" - assert data["version"] == "1.4.3" + assert data["version"] == __version__ assert data["components"]["vault"] == "healthy" def test_health_returns_503_when_vault_missing( @@ -30,7 +31,7 @@ def test_health_includes_version(self, test_client: TestClient): response = test_client.get("/health") data = response.json() assert "version" in data - assert data["version"] == "1.4.3" + assert data["version"] == __version__ def test_health_includes_search_components(self, test_client: TestClient): response = test_client.get("/health") @@ -136,10 +137,10 @@ def test_health_overall_ignores_disabled_components( data = response.json() assert data["status"] == "healthy" - def test_health_version_is_1_4_2(self, test_client: TestClient): + def test_health_version_matches_package(self, test_client: TestClient): response = test_client.get("/health") data = response.json() - assert data["version"] == "1.4.3" + assert data["version"] == __version__ def test_health_shared_disabled_by_default(self, test_client: TestClient): response = test_client.get("/health") diff --git a/tests/test_routes_memories.py b/tests/test_routes_memories.py index afeb3e5..456ff3d 100644 --- a/tests/test_routes_memories.py +++ b/tests/test_routes_memories.py @@ -109,7 +109,8 @@ def test_read_memory_after_create(self, test_client: TestClient): body = response.json() assert body["id"] == memory_id assert body["body"] == "Read me back" - assert body["tags"] == ["test"] + assert "test" in body["tags"] + assert any(t.startswith("cat:") for t in body["tags"]) def test_read_nonexistent_memory_returns_404(self, test_client: TestClient): response = test_client.get("/agents/test-agent/memories/nonexistent-id") @@ -120,7 +121,9 @@ class TestListMemories: def test_list_memories_empty(self, test_client: TestClient): response = test_client.get("/agents/empty-agent/memories") assert response.status_code == 200 - assert response.json() == [] + data = response.json() + assert data["memories"] == [] + assert data["total"] == 0 def test_list_memories_returns_created(self, test_client: TestClient): test_client.post("/agents/list-agent/memories", json={"content": "First"}) @@ -130,10 +133,30 @@ def test_list_memories_returns_created(self, test_client: TestClient): response2 = test_client.get("/agents/list-agentb/memories") assert response1.status_code == 200 assert response2.status_code == 200 - assert len(response1.json()) == 1 - assert len(response2.json()) == 1 - assert response1.json()[0]["body"] == "First" - assert response2.json()[0]["body"] == "Second" + assert len(response1.json()["memories"]) == 1 + assert len(response2.json()["memories"]) == 1 + assert response1.json()["memories"][0]["body"] == "First" + assert response2.json()["memories"][0]["body"] == "Second" + + def test_list_memories_pagination(self, test_client: TestClient): + for i in range(5): + test_client.post( + "/agents/page-agent/memories", json={"content": f"Memory {i}"} + ) + + response = test_client.get("/agents/page-agent/memories?limit=2&offset=1") + assert response.status_code == 200 + data = response.json() + assert data["total"] == 5 + assert data["limit"] == 2 + assert data["offset"] == 1 + assert len(data["memories"]) == 2 + + def test_list_memories_default_pagination(self, test_client: TestClient): + response = test_client.get("/agents/defpage-agent/memories") + data = response.json() + assert data["limit"] == 50 + assert data["offset"] == 0 class TestDeleteMemory: @@ -159,7 +182,7 @@ def test_deleted_memory_not_in_list(self, test_client: TestClient): test_client.delete(f"/agents/deleter2/memories/{memory_id}") response = test_client.get("/agents/deleter2/memories") - assert response.json() == [] + assert response.json()["memories"] == [] def test_read_after_delete_returns_404(self, test_client: TestClient): create_response = test_client.post( diff --git a/tests/test_search_index.py b/tests/test_search_index.py index 0fd3d71..9708273 100644 --- a/tests/test_search_index.py +++ b/tests/test_search_index.py @@ -369,3 +369,171 @@ def test_cache_ttl_expiry_triggers_fresh_search( search_index.search("ttl", agent_id="agent1", limit=5) cache_time = search_index._result_cache[("ttl", "agent1", 5)][0] assert time.time() - cache_time < 1 + + +class TestValidateDimension: + """Dimension validation checks vector dimension mismatch at startup.""" + + def test_validate_dimension_no_table(self, tmp_path: Path) -> None: + """No existing table → skip validation gracefully, no error.""" + settings = Settings( + vault_path=tmp_path / "vault", + log_file=tmp_path / "test.log", + index_path=tmp_path / "index_dim_no_table", + ) + (tmp_path / "vault").mkdir(exist_ok=True) + provider = MagicMock() + provider.check_available = MagicMock(return_value=True) + provider.embed = MagicMock(return_value=[[0.1] * 384]) + idx = SearchIndex(settings=settings, embedding_provider=provider) + idx.validate_dimension() + provider.embed.assert_not_called() + + def test_validate_dimension_matching(self, tmp_path: Path) -> None: + """Matching dimensions → no reindex, no warning.""" + settings = Settings( + vault_path=tmp_path / "vault", + log_file=tmp_path / "test.log", + index_path=tmp_path / "index_dim_match", + ) + (tmp_path / "vault").mkdir(exist_ok=True) + dim = 384 + provider = MagicMock() + provider.check_available = MagicMock(return_value=True) + provider.embed = MagicMock(return_value=[[0.1] * dim]) + idx = SearchIndex(settings=settings, embedding_provider=provider) + memory = _make_memory() + idx.add(memory, ["test chunk"]) + provider.embed.reset_mock() + provider.embed = MagicMock(return_value=[[0.1] * dim]) + idx.reindex = MagicMock() + + idx.validate_dimension() + idx.reindex.assert_not_called() + assert idx.table.count_rows() == 1 + + def test_validate_dimension_mismatch_triggers_reindex(self, tmp_path: Path) -> None: + """Dimension mismatch → warning logged and reindex called.""" + settings = Settings( + vault_path=tmp_path / "vault", + log_file=tmp_path / "test.log", + index_path=tmp_path / "index_dim_mismatch", + ) + (tmp_path / "vault").mkdir(exist_ok=True) + + old_dim = 128 + provider = MagicMock() + provider.check_available = MagicMock(return_value=True) + provider.embed = MagicMock(return_value=[[0.1] * old_dim]) + idx = SearchIndex(settings=settings, embedding_provider=provider) + memory = _make_memory() + idx.add(memory, ["test chunk"]) + assert idx.table.count_rows() == 1 + + new_dim = 384 + provider.embed = MagicMock(return_value=[[0.1] * new_dim]) + idx.reindex = MagicMock() + + idx.validate_dimension() + idx.reindex.assert_called_once() + + def test_validate_dimension_no_provider(self, tmp_path: Path) -> None: + """No embedding provider → skip validation gracefully.""" + settings = Settings( + vault_path=tmp_path / "vault", + log_file=tmp_path / "test.log", + index_path=tmp_path / "index_dim_no_prov", + ) + (tmp_path / "vault").mkdir(exist_ok=True) + idx = SearchIndex(settings=settings, embedding_provider=None) + idx.validate_dimension() + + def test_validate_dimension_embed_failure(self, tmp_path: Path) -> None: + """Embed failure during validation → skip gracefully, no reindex.""" + settings = Settings( + vault_path=tmp_path / "vault", + log_file=tmp_path / "test.log", + index_path=tmp_path / "index_dim_embed_fail", + ) + (tmp_path / "vault").mkdir(exist_ok=True) + dim = 384 + provider = MagicMock() + provider.check_available = MagicMock(return_value=True) + provider.embed = MagicMock(return_value=[[0.1] * dim]) + idx = SearchIndex(settings=settings, embedding_provider=provider) + memory = _make_memory() + idx.add(memory, ["test chunk"]) + provider.embed = MagicMock(side_effect=RuntimeError("embed crashed")) + idx.reindex = MagicMock() + + idx.validate_dimension() + idx.reindex.assert_not_called() + + +class TestFindByTag: + def test_find_by_tag_returns_matching_memories(self, search_index, mock_provider): + food_mem = Memory( + id="food-1", + agent="agent1", + importance=0.5, + tags=["cat:food", "preference"], + created="2026-01-01T00:00:00+00:00", + updated="2026-01-01T00:00:00+00:00", + importance_updated="2026-01-01T00:00:00+00:00", + body="I love biryani", + ) + search_index.add(food_mem, ["I love biryani"]) + + results = search_index.find_by_tag("cat:food", "agent1", limit=3) + assert len(results) >= 1 + found_ids = [r["memory_id"] for r in results] + assert "food-1" in found_ids + + def test_find_by_tag_excludes_other_agents(self, search_index, mock_provider): + food_mem = Memory( + id="food-a1", + agent="agent1", + importance=0.5, + tags=["cat:food"], + created="2026-01-01T00:00:00+00:00", + updated="2026-01-01T00:00:00+00:00", + importance_updated="2026-01-01T00:00:00+00:00", + body="I love biryani", + ) + search_index.add(food_mem, ["I love biryani"]) + + results = search_index.find_by_tag("cat:food", "agent2", limit=3) + assert len(results) == 0 + + def test_find_by_tag_no_match_returns_empty(self, search_index, mock_provider): + work_mem = Memory( + id="work-1", + agent="agent1", + importance=0.5, + tags=["cat:work"], + created="2026-01-01T00:00:00+00:00", + updated="2026-01-01T00:00:00+00:00", + importance_updated="2026-01-01T00:00:00+00:00", + body="work content", + ) + search_index.add(work_mem, ["work content"]) + + results = search_index.find_by_tag("cat:food", "agent1", limit=3) + assert all(r["memory_id"] != "test-memory" for r in results) + + def test_find_by_tag_deduplicates(self, search_index, mock_provider): + mem = Memory( + id="multi-chunk", + agent="agent1", + importance=0.5, + tags=["cat:fitness"], + created="2026-01-01T00:00:00+00:00", + updated="2026-01-01T00:00:00+00:00", + importance_updated="2026-01-01T00:00:00+00:00", + body="chunk one chunk two", + ) + search_index.add(mem, ["chunk one", "chunk two"]) + + results = search_index.find_by_tag("cat:fitness", "agent1", limit=3) + ids = [r["memory_id"] for r in results] + assert len(ids) == len(set(ids)) diff --git a/tests/test_synthesis.py b/tests/test_synthesis.py new file mode 100644 index 0000000..d1178f3 --- /dev/null +++ b/tests/test_synthesis.py @@ -0,0 +1,128 @@ +"""Tests for LLM memory synthesis.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from memstack.intelligence.synthesis import synthesize +from memstack.intelligence.llm import _reset_ollama_client + + +@pytest.fixture(autouse=True) +def reset_client(): + """Reset the Ollama client singleton between tests.""" + _reset_ollama_client() + yield + _reset_ollama_client() + + +def _mock_ollama_response(content: dict) -> MagicMock: + """Build a mock ollama client that returns the given content dict.""" + client = MagicMock() + client.chat.return_value = { + "message": {"content": __import__("json").dumps(content)} + } + return client + + +class TestSynthesize: + def test_returns_statements(self) -> None: + """synthesize() returns a list of statements from valid LLM output.""" + mock_client = _mock_ollama_response( + {"statements": ["User prefers dark mode", "User uses Python"]} + ) + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize( + "I like dark mode and Python", "llama3", "http://localhost:11434" + ) + assert result == ["User prefers dark mode", "User uses Python"] + mock_client.chat.assert_called_once() + + def test_fallback_on_llm_failure(self) -> None: + """synthesize() falls back to [content] on LLM failure.""" + mock_client = MagicMock() + mock_client.chat.side_effect = Exception("LLM unavailable") + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Some content", "llama3", "http://localhost:11434") + assert result == ["Some content"] + + def test_fallback_on_unparseable_json(self) -> None: + """synthesize() falls back to [content] on unparseable JSON.""" + mock_client = MagicMock() + mock_client.chat.return_value = {"message": {"content": "not json at all"}} + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Raw text", "llama3", "http://localhost:11434") + assert result == ["Raw text"] + + def test_fallback_on_empty_statements(self) -> None: + """synthesize() falls back to [content] when statements list is empty.""" + mock_client = _mock_ollama_response({"statements": []}) + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Hello there", "llama3", "http://localhost:11434") + assert result == ["Hello there"] + + def test_preserves_original_in_fallback(self) -> None: + """synthesize() preserves original content in fallback.""" + original = "The user prefers Vim over VS Code" + mock_client = MagicMock() + mock_client.chat.side_effect = RuntimeError("connection failed") + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize(original, "llama3", "http://localhost:11434") + assert result == [original] + assert result[0] == original + + def test_strips_empty_strings(self) -> None: + """synthesize() filters out empty strings from statements.""" + mock_client = _mock_ollama_response( + {"statements": ["Valid statement", "", " ", "Another valid"]} + ) + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Mixed content", "llama3", "http://localhost:11434") + assert result == ["Valid statement", "Another valid"] + + def test_uses_correct_model_and_host(self) -> None: + """synthesize() passes the correct model and host to the LLM client.""" + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": {"content": '{"statements": ["fact"]}'} + } + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Content", "mistral", "http://custom:1234") + assert result == ["fact"] + mock_client.chat.assert_called_once() + call = mock_client.chat.call_args + assert call.kwargs["model"] == "mistral" + + def test_fenced_json_stripped(self) -> None: + """synthesize() correctly parses LLM responses wrapped in code fences.""" + mock_client = MagicMock() + mock_client.chat.return_value = { + "message": {"content": '```json\n{"statements": ["fact 1", "fact 2"]}\n```'} + } + with patch( + "memstack.intelligence.synthesis._get_ollama_client", + return_value=mock_client, + ): + result = synthesize("Some text", "llama3", "http://localhost:11434") + assert result == ["fact 1", "fact 2"] diff --git a/tests/test_vault.py b/tests/test_vault.py index 6dea806..1e43f24 100644 --- a/tests/test_vault.py +++ b/tests/test_vault.py @@ -28,7 +28,7 @@ def vault(tmp_path: Path) -> Path: @pytest.fixture def store(vault: Path) -> VaultStore: """Create a VaultStore pointing at the temporary vault.""" - settings = Settings(vault_path=vault, obsidian_mode=True, shared_mode=False) + settings = Settings(vault_path=vault, shared_mode=False) return VaultStore(settings=settings) @@ -106,21 +106,23 @@ class TestVaultList: def test_list_empty(self, store: VaultStore) -> None: result = store.list_memories("agent1") - assert result == [] + assert result.memories == [] + assert result.total == 0 def test_list_returns_memories(self, store: VaultStore) -> None: store.write(MemoryWrite(content="First", agent_id="agent1")) store.write(MemoryWrite(content="Second", agent_id="agent1")) result = store.list_memories("agent1") - assert len(result) == 2 + assert len(result.memories) == 2 + assert result.total == 2 def test_list_scoped_to_agent(self, store: VaultStore) -> None: store.write(MemoryWrite(content="A", agent_id="agent1")) store.write(MemoryWrite(content="B", agent_id="agent2")) a1 = store.list_memories("agent1") a2 = store.list_memories("agent2") - assert len(a1) == 1 - assert len(a2) == 1 + assert len(a1.memories) == 1 + assert len(a2.memories) == 1 def test_list_skips_invalid_files(self, store: VaultStore, vault: Path) -> None: """Malformed .md files are skipped with a warning, not crashed.""" @@ -131,7 +133,7 @@ def test_list_skips_invalid_files(self, store: VaultStore, vault: Path) -> None: ) store.write(MemoryWrite(content="Valid", agent_id="agent1")) result = store.list_memories("agent1") - assert len(result) == 1 + assert len(result.memories) == 1 class TestVaultRoundTrip: @@ -386,7 +388,7 @@ def test_update_same_slug_preserves_file(self, store: VaultStore) -> None: @pytest.fixture def shared_store(vault: Path) -> VaultStore: """Create a VaultStore with shared_mode enabled.""" - settings = Settings(vault_path=vault, obsidian_mode=True, shared_mode=True) + settings = Settings(vault_path=vault, shared_mode=True) return VaultStore(settings=settings) @@ -442,9 +444,7 @@ def test_toggle_off_does_not_delete_existing_shared_copies( self, vault: Path ) -> None: """Turning shared_mode off must not delete existing shared copies.""" - shared_settings = Settings( - vault_path=vault, obsidian_mode=True, shared_mode=True - ) + shared_settings = Settings(vault_path=vault, shared_mode=True) shared_store = VaultStore(settings=shared_settings) mw = MemoryWrite(content="Shared content", agent_id="agent1") shared_store.write(mw) @@ -453,9 +453,7 @@ def test_toggle_off_does_not_delete_existing_shared_copies( assert len(list(shared_dir.glob("*.md"))) == 1 # Now use a store with shared_mode=False - private_settings = Settings( - vault_path=vault, obsidian_mode=True, shared_mode=False - ) + private_settings = Settings(vault_path=vault, shared_mode=False) private_store = VaultStore(settings=private_settings) mw2 = MemoryWrite(content="Private only", agent_id="agent1") private_store.write(mw2) @@ -610,7 +608,7 @@ def write_memory(content): assert not errors memories = store.list_memories("agent1") - assert len(memories) == 5 + assert len(memories.memories) == 5 class TestSharedDirProperty: @@ -660,7 +658,7 @@ def test_read_shared_nonexistent_raises(self, shared_store: VaultStore) -> None: def test_delete_shared_copy_when_no_shared_dir(self, vault: Path) -> None: """Deleting a memory when shared_mode is on but shared dir doesn't exist.""" - settings = Settings(vault_path=vault, obsidian_mode=True, shared_mode=True) + settings = Settings(vault_path=vault, shared_mode=True) store = VaultStore(settings=settings) mw = MemoryWrite(content="To delete", agent_id="agent1") memory = store.write(mw) @@ -729,21 +727,21 @@ def test_list_returns_cached_data(self, store: VaultStore) -> None: result1 = store.list_memories("agent1") result2 = store.list_memories("agent1") assert result1 == result2 - assert len(result2) == 1 + assert len(result2.memories) == 1 def test_cache_invalidated_on_write(self, store: VaultStore) -> None: """Write invalidates list cache for that agent.""" store.write(MemoryWrite(content="first", agent_id="agent1")) store.list_memories("agent1") store.write(MemoryWrite(content="second", agent_id="agent1")) - assert "agent1" not in store._list_cache + assert not any(k[0] == "agent1" for k in store._list_cache) def test_cache_invalidated_on_delete(self, store: VaultStore) -> None: """Delete invalidates list cache for that agent.""" memory = store.write(MemoryWrite(content="to delete", agent_id="agent1")) store.list_memories("agent1") store.delete("agent1", memory.id) - assert "agent1" not in store._list_cache + assert not any(k[0] == "agent1" for k in store._list_cache) def test_cache_respects_max_size(self, store: VaultStore) -> None: """Cache evicts oldest entries when max size is reached.""" diff --git a/tests/test_watcher.py b/tests/test_watcher.py index d943177..f7c5d7f 100644 --- a/tests/test_watcher.py +++ b/tests/test_watcher.py @@ -43,7 +43,7 @@ def mock_search_index(): @pytest.fixture def store(tmp_vault: Path) -> VaultStore: - settings = Settings(vault_path=tmp_vault, obsidian_mode=True, shared_mode=False) + settings = Settings(vault_path=tmp_vault, shared_mode=False) return VaultStore(settings=settings) diff --git a/uv.lock b/uv.lock index 3e831a8..c015bf8 100644 --- a/uv.lock +++ b/uv.lock @@ -544,58 +544,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, ] -[[package]] -name = "memstack" -version = "1.4.4" -source = { editable = "." } -dependencies = [ - { name = "fastapi" }, - { name = "fastembed" }, - { name = "fastmcp" }, - { name = "lancedb" }, - { name = "loguru" }, - { name = "ollama" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-frontmatter" }, - { name = "tiktoken" }, - { name = "typer" }, - { name = "uvicorn", extra = ["standard"] }, - { name = "watchfiles" }, -] - -[package.optional-dependencies] -dev = [ - { name = "httpx" }, - { name = "hypothesis" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastapi", specifier = ">=0.104.0" }, - { name = "fastembed", specifier = ">=0.2.0" }, - { name = "fastmcp", specifier = ">=3.2.4" }, - { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.25.0" }, - { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.0.0" }, - { name = "lancedb", specifier = ">=0.6.0" }, - { name = "loguru", specifier = ">=0.7.0" }, - { name = "ollama", specifier = ">=0.1.0" }, - { name = "pydantic", specifier = ">=2.5.0" }, - { name = "pydantic-settings", specifier = ">=2.1.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, - { name = "python-frontmatter", specifier = ">=1.1.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4.0" }, - { name = "tiktoken", specifier = ">=0.5.0" }, - { name = "typer", specifier = ">=0.9.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.24.0" }, - { name = "watchfiles", specifier = ">=0.21.0" }, -] -provides-extras = ["dev"] - [[package]] name = "exceptiongroup" version = "1.3.1" @@ -1123,6 +1071,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "memstack" +version = "1.4.5" +source = { editable = "." } +dependencies = [ + { name = "fastapi" }, + { name = "fastembed" }, + { name = "fastmcp" }, + { name = "lancedb" }, + { name = "loguru" }, + { name = "ollama" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-frontmatter" }, + { name = "tiktoken" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "watchfiles" }, +] + +[package.optional-dependencies] +dev = [ + { name = "httpx" }, + { name = "hypothesis" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.104.0" }, + { name = "fastembed", specifier = ">=0.2.0" }, + { name = "fastmcp", specifier = ">=3.2.4" }, + { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.25.0" }, + { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.0.0" }, + { name = "lancedb", specifier = ">=0.6.0" }, + { name = "loguru", specifier = ">=0.7.0" }, + { name = "ollama", specifier = ">=0.1.0" }, + { name = "pydantic", specifier = ">=2.5.0" }, + { name = "pydantic-settings", specifier = ">=2.1.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, + { name = "python-frontmatter", specifier = ">=1.1.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4.0" }, + { name = "tiktoken", specifier = ">=0.5.0" }, + { name = "typer", specifier = ">=0.9.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.24.0" }, + { name = "watchfiles", specifier = ">=0.21.0" }, +] +provides-extras = ["dev"] + [[package]] name = "mmh3" version = "5.2.1"