memstash 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. memstash-0.15.0/.github/workflows/ci.yml +26 -0
  2. memstash-0.15.0/.github/workflows/publish.yml +47 -0
  3. memstash-0.15.0/.gitignore +25 -0
  4. memstash-0.15.0/CHANGELOG.md +293 -0
  5. memstash-0.15.0/LICENSE +21 -0
  6. memstash-0.15.0/PKG-INFO +471 -0
  7. memstash-0.15.0/README.md +418 -0
  8. memstash-0.15.0/ROADMAP.md +118 -0
  9. memstash-0.15.0/examples/basic.py +22 -0
  10. memstash-0.15.0/memstash/__init__.py +18 -0
  11. memstash-0.15.0/memstash/adapters/__init__.py +165 -0
  12. memstash-0.15.0/memstash/adapters/anthropic_adapter.py +75 -0
  13. memstash-0.15.0/memstash/adapters/base.py +61 -0
  14. memstash-0.15.0/memstash/adapters/gemini_adapter.py +77 -0
  15. memstash-0.15.0/memstash/adapters/openai_adapter.py +90 -0
  16. memstash-0.15.0/memstash/bench.py +150 -0
  17. memstash-0.15.0/memstash/cli.py +998 -0
  18. memstash-0.15.0/memstash/config.py +80 -0
  19. memstash-0.15.0/memstash/core.py +594 -0
  20. memstash-0.15.0/memstash/dashboard/__init__.py +1 -0
  21. memstash-0.15.0/memstash/dashboard/server.py +143 -0
  22. memstash-0.15.0/memstash/evals.py +98 -0
  23. memstash-0.15.0/memstash/extract.py +53 -0
  24. memstash-0.15.0/memstash/extract_llm.py +90 -0
  25. memstash-0.15.0/memstash/graph_extract.py +77 -0
  26. memstash-0.15.0/memstash/ingest.py +57 -0
  27. memstash-0.15.0/memstash/instrument.py +121 -0
  28. memstash-0.15.0/memstash/mcp_server.py +108 -0
  29. memstash-0.15.0/memstash/memory.py +388 -0
  30. memstash-0.15.0/memstash/otel_export.py +92 -0
  31. memstash-0.15.0/memstash/pricing.py +140 -0
  32. memstash-0.15.0/memstash/prompts.py +39 -0
  33. memstash-0.15.0/memstash/reconcile.py +109 -0
  34. memstash-0.15.0/memstash/store.py +738 -0
  35. memstash-0.15.0/pyproject.toml +70 -0
  36. memstash-0.15.0/tests/test_bench.py +52 -0
  37. memstash-0.15.0/tests/test_bitemporal.py +66 -0
  38. memstash-0.15.0/tests/test_core.py +103 -0
  39. memstash-0.15.0/tests/test_embeddings.py +87 -0
  40. memstash-0.15.0/tests/test_eval_suites.py +92 -0
  41. memstash-0.15.0/tests/test_evals.py +84 -0
  42. memstash-0.15.0/tests/test_features.py +118 -0
  43. memstash-0.15.0/tests/test_graph.py +66 -0
  44. memstash-0.15.0/tests/test_graph_retrieval.py +57 -0
  45. memstash-0.15.0/tests/test_ingest.py +87 -0
  46. memstash-0.15.0/tests/test_instrument.py +88 -0
  47. memstash-0.15.0/tests/test_lifecycle.py +113 -0
  48. memstash-0.15.0/tests/test_memory_ops.py +112 -0
  49. memstash-0.15.0/tests/test_multiturn.py +64 -0
  50. memstash-0.15.0/tests/test_otel.py +32 -0
  51. memstash-0.15.0/tests/test_plugins.py +44 -0
  52. memstash-0.15.0/tests/test_pricing_map.py +47 -0
  53. memstash-0.15.0/tests/test_prompts.py +50 -0
  54. memstash-0.15.0/tests/test_provenance.py +54 -0
  55. memstash-0.15.0/tests/test_reconcile.py +105 -0
  56. memstash-0.15.0/tests/test_retrieval_budget.py +127 -0
  57. memstash-0.15.0/tests/test_robustness.py +83 -0
  58. memstash-0.15.0/tests/test_trace_tree.py +76 -0
  59. memstash-0.15.0/tests/test_v3.py +114 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.9", "3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: |
21
+ python -m pip install --upgrade pip
22
+ pip install -e '.[dev]'
23
+ - name: Lint
24
+ run: ruff check memstash
25
+ - name: Test
26
+ run: pytest -q
@@ -0,0 +1,47 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes memstash to PyPI when a version tag is pushed (e.g. v0.3.0).
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token stored in the repo.
5
+ #
6
+ # One-time setup on PyPI:
7
+ # 1. Create the project `memstash` (or let the first manual upload create it).
8
+ # 2. PyPI → project → Publishing → add a trusted publisher:
9
+ # owner: zionLyl repo: recall workflow: publish.yml environment: pypi
10
+ #
11
+ # Then releasing is just: git tag v0.3.0 && git push origin v0.3.0
12
+
13
+ on:
14
+ push:
15
+ tags:
16
+ - "v*"
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.11"
26
+ - name: Build sdist + wheel
27
+ run: |
28
+ python -m pip install --upgrade build
29
+ python -m build
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ environment: pypi
39
+ permissions:
40
+ id-token: write # required for trusted publishing
41
+ steps:
42
+ - uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+ - name: Publish to PyPI
47
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # recall local data
13
+ .recall/
14
+ *.db
15
+ *.sqlite
16
+
17
+ # tooling
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+ .mypy_cache/
21
+
22
+ # OS / editor
23
+ .DS_Store
24
+ .vscode/
25
+ .idea/
@@ -0,0 +1,293 @@
1
+ # Changelog
2
+
3
+ ## [0.15.0]
4
+
5
+ Renamed to **memstash** (the PyPI name `engram-ai` collided with the existing
6
+ `engram` project). Brand, CLI command, and import are all `memstash` now.
7
+
8
+ ### Changed
9
+ - PyPI package + CLI + import are now **`memstash`** (`pip install memstash`,
10
+ `memstash ...`, `import memstash`). The `Memstash` class is exported (the
11
+ `Recall` class name still works as an alias).
12
+ - Env vars are `MEMSTASH_*`; the default store is `~/.memstash/memstash.db`;
13
+ the adapter entry-point group is `memstash.adapters`.
14
+ - (The unreleased `engram-ai` 0.14.0 was never published; this supersedes it.)
15
+
16
+ ## [0.14.0]
17
+
18
+ Rebrand to **Engram**.
19
+
20
+ ### Changed
21
+ - Project renamed from `zion-recall-ai` to **`engram-ai`** (PyPI) with the
22
+ **`engram`** CLI command and `import engram`. The `Engram` class is exported
23
+ (the old `Recall` class name still works as an alias).
24
+ - Env vars are now `ENGRAM_*` (was `RECALL_*`); the default store moved to
25
+ `~/.engram/engram.db`; the adapter entry-point group is `engram.adapters`.
26
+ - README reworked with a sharper pitch, a comparison table, and CI/PyPI badges.
27
+
28
+ ## [0.13.0]
29
+
30
+ New capability (3/3): capture other libraries' LLM calls.
31
+
32
+ ### Added
33
+ - **`recall.instrument()`**: makes recall a *local* OpenInference/OpenTelemetry
34
+ span sink — LLM calls made by LangChain, LlamaIndex, the OpenAI SDK, etc.
35
+ (instrumented via OpenInference) get written to `~/.engram/recall.db` as
36
+ traces (model, tokens, cost, latency, `kind="instrumented"`), visible in
37
+ `engram recent` / `stats` / `trace`. No server, no cloud — the local
38
+ counterpart to Phoenix/Langfuse auto-instrumentation. Best-effort enables the
39
+ OpenAI/LangChain instrumentors if installed. New `recall/instrument.py`
40
+ (`span_to_trace`, `instrument`). Requires `[otel]`.
41
+
42
+ ## [0.12.0]
43
+
44
+ New capability (2/3): document ingestion.
45
+
46
+ ### Added
47
+ - **`engram ingest <file>`**: read a `.txt` / `.md` (or `.pdf` with the `[pdf]`
48
+ extra), split it into reasonably-sized chunks, and store each as a searchable
49
+ memory tagged with the filename — so `engram search` and chat memory-injection
50
+ cover your notes/docs. 100% local and deterministic (no LLM). Dedupes on
51
+ re-ingest. `recall/ingest.py` (`chunk_text`, `read_file`) + `Recall.ingest`.
52
+
53
+ ## [0.11.0]
54
+
55
+ New capability (1/3): bi-temporal memory.
56
+
57
+ ### Added
58
+ - **Point-in-time queries**: `engram list --at <when>` shows the memories that
59
+ were valid at a past time — `YYYY-MM-DD`, a relative `30d`/`12h`/`45m` ago, or
60
+ an epoch. Includes since-forgotten memories whose validity window covers that
61
+ moment. `Store.memories_as_of()` / `Recall.as_of()`.
62
+
63
+ ### Changed
64
+ - **Conflict-resolution UPDATE now supersedes instead of overwriting**: the old
65
+ fact's validity window is closed (kept as history, deactivated) and the new
66
+ fact is added — so "what did I know on date X?" stays answerable.
67
+
68
+ ## [0.10.0]
69
+
70
+ Credibility — reproducible quality numbers.
71
+
72
+ ### Added
73
+ - **Benchmark harness** (`engram benchmark`): deterministic, key-free. Seeds a
74
+ fixed, hand-labeled memory set into a throwaway store and reports retrieval
75
+ quality — **recall@1, recall@k, precision@k, MRR** — plus a heuristic
76
+ extraction fact-recall / false-capture check. Honestly labels whether the run
77
+ used the semantic or keyword/BM25 backend, so numbers are comparable and not
78
+ overstated. New `recall/bench.py` (`run_retrieval_benchmark`,
79
+ `run_extraction_benchmark`, `run_all`). Baseline (keyword): recall@1 ≈ 0.50,
80
+ MRR ≈ 0.69, extraction fact-recall 1.00 with 0 false captures; semantic
81
+ embeddings score higher.
82
+
83
+ ## [0.9.0]
84
+
85
+ Robustness & scale.
86
+
87
+ ### Added
88
+ - **Vectorized retrieval**: semantic ranking now uses numpy (when present) to
89
+ cosine-score all memories in one matrix op, with a pure-Python fallback —
90
+ keeping recall fast as the store grows to thousands of memories. No new
91
+ required dependency.
92
+
93
+ ### Fixed
94
+ - **Embedding dimension safety**: stored vectors whose dimension differs from
95
+ the query (e.g. after switching embedding models/backends) are now skipped
96
+ instead of producing silently-wrong cosine scores. `_cosine` returns 0 on
97
+ mismatch, protecting search, near-dup suppression, and dedupe.
98
+
99
+ ### Changed
100
+ - SQLite now opens in **WAL** mode (`synchronous=NORMAL`) for safer concurrent
101
+ reads/writes (e.g. the dashboard reading while the CLI writes).
102
+
103
+ ## [0.8.0]
104
+
105
+ ### Added
106
+ - **Pluggable embedding backend**: semantic search can now use any
107
+ OpenAI-compatible `/embeddings` endpoint instead of the local
108
+ sentence-transformers model — point at a local Ollama / LM Studio (no PyTorch
109
+ download) or a cloud provider. Config: `embedding_backend = api`,
110
+ `embedding_model`, `embedding_base_url`, `embedding_api_key_env`. Falls back to
111
+ keyword/BM25 search if the endpoint is unreachable. Default stays `local`.
112
+
113
+ ### Changed
114
+ - Published to PyPI as **`engram-ai`** (`pip install engram-ai`); the
115
+ import package and `recall` CLI command are unchanged. README gains a PyPI
116
+ badge + prominent install line.
117
+
118
+ ## [0.7.0]
119
+
120
+ Usability & onboarding polish — informed by a competitive scan of how peers
121
+ (simonw/llm, Jan, Phoenix, mem0, …) handle ease-of-adoption.
122
+
123
+ ### Added
124
+ - **Interactive chat REPL**: bare `engram chat` (no prompt) drops into a
125
+ multi-turn conversation loop — memory injected + auto-captured each turn,
126
+ cost traced, `/exit` or Ctrl-D to quit. Matches `llm chat`'s ergonomics.
127
+ - **Multi-turn conversation history**: adapters and `Recall.chat/stream` accept
128
+ a `history` of prior turns, so chats are no longer single-shot. OpenAI,
129
+ Anthropic, and Gemini adapters all thread it natively.
130
+ - **Zero-key local default**: `engram init` now detects a running Ollama
131
+ (localhost:11434) and offers it as the default provider — no API key needed.
132
+
133
+ ### Changed
134
+ - **Onboarding**: README leads with `pipx install 'engram-ai[all]'` (and `uv tool
135
+ install`); the quickstart's first chat command now installs a working provider
136
+ extra instead of failing on the base install.
137
+ - First semantic search prints a one-time "loading embedding model (~80MB)"
138
+ notice to stderr instead of hanging silently.
139
+
140
+ ## [0.6.0]
141
+
142
+ The rest of the roadmap — extensibility, interop, auditing, and eval workflow.
143
+
144
+ ### Added
145
+ - **Provenance**: auto-captured memories record the chat trace they came from
146
+ (`source_trace`); `engram show <id>` prints a memory's detail plus the source
147
+ call's model and prompt snippet for end-to-end auditing.
148
+ - **Plugin hooks**: `recall.register_adapter(...)` for runtime custom providers,
149
+ and auto-discovery of adapters published under the `recall.adapters`
150
+ entry-point group. *(simonw/llm-style.)*
151
+ - **OpenTelemetry export** (opt-in): `config otel_export true` mirrors calls as
152
+ OpenInference LLM spans to an OTLP backend (Phoenix/Langfuse) or the console —
153
+ no server dependency. `pip install 'engram-ai[otel]'`.
154
+ - **Eval ergonomics**: saved eval suites (`engram eval-suite save/list/rm`,
155
+ `engram eval <id> --suite NAME`), an eval pass-rate/score summary in
156
+ `engram stats`, and opt-in **auto-eval after chat** (`config auto_eval_suite`).
157
+
158
+ ## [0.5.0]
159
+
160
+ Retrieval, cost accuracy, and quality — the next batch from the roadmap.
161
+
162
+ ### Added
163
+ - **Graph-aware retrieval**: with `graph_weight > 0`, recall finds entities
164
+ named in the query, expands to their neighbors via the graph-lite relations,
165
+ and pulls in memories about those neighbors as an extra RRF signal — surfacing
166
+ connected memories the query never mentions. *(mem0/Zep-style.)*
167
+ - **Maintained pricing map**: provider-prefixed ids resolve
168
+ (`openrouter/openai/gpt-4o` → `gpt-4o`); override without editing the package
169
+ via `ENGRAM_PRICING_FILE` (JSON file) layered under `ENGRAM_PRICING` (inline);
170
+ more current models added; `engram pricing [model]` to inspect. *(LiteLLM-style.)*
171
+ - **Quality evals**: attach checks to a traced reply — local rules
172
+ (`--contains` / `--not-contains` / `--regex` / `--max-tokens`, free &
173
+ deterministic) and an opt-in `--judge "criterion"` LLM score (1–5 → 0–1).
174
+ `engram eval <trace_id> ...` and `engram evals` list results; `engram recent`
175
+ now shows trace IDs and kind. *(Langfuse/Phoenix-style, local.)*
176
+
177
+ ## [0.4.0]
178
+
179
+ Memory intelligence + deeper local observability — closing the biggest gaps vs
180
+ mem0 / Letta / Zep / Langfuse, while staying single-file and server-free.
181
+
182
+ ### Added
183
+ - **Memory lifecycle**: memories track `hit_count` / `last_used` and a
184
+ `valid_from` / `valid_to` / `active` window. Retrieval records hits and skips
185
+ inactive memories. Soft-forget via `engram forget --soft` and bulk
186
+ `engram prune --older-than DAYS --unused`. Opt-in recency/usage ranking
187
+ (`config recency_weight`) blends a lifecycle signal into retrieval via
188
+ weighted RRF. *(Zep/Letta-inspired.)*
189
+ - **Conflict resolution (ADD/UPDATE/DELETE/NOOP)**: opt-in `memory_ops = "llm"`
190
+ reconciles each new fact against its most-related memories so contradictions
191
+ are updated/removed instead of piling up. New `reconcile.py`; cost traced;
192
+ degrades to a plain ADD on any failure. *(mem0's signature.)*
193
+ - **Graph-lite**: a `relations` table stores `(subject, predicate, object)`
194
+ triples per scope — relational memory without a graph DB. Opt-in LLM mining
195
+ (`graph_extract`) from chats; `engram graph [entity]` to query, `--add` to add
196
+ manually. *(mem0/Zep/cognee-style, single-SQLite.)*
197
+ - **Local trace tree**: traces gain `session_id` / `parent_id` / `kind`, so a
198
+ turn's chat + its extraction/reconcile/graph calls form a tree. `engram trace`
199
+ renders per-turn call trees with token/cost totals; the dashboard gains a
200
+ "Recent turns" section.
201
+ - **Prompt templates / fragments**: `engram prompt save/list/show/use/rm` with
202
+ `{var}` substitution; `engram chat --template NAME --var k=v`. *(simonw/llm-style.)*
203
+
204
+ ## [0.3.0]
205
+
206
+ Streaming, smarter memory, and the MCP bridge.
207
+
208
+ ### Added
209
+ - **Streaming chat**: replies now type out token-by-token. `Recall.stream(...)`
210
+ yields chunks via an `on_token` callback and still returns the full
211
+ `ChatOutcome` (text, tokens, cost, auto-memory, budget). CLI: streams by
212
+ default; toggle with `engram chat --no-stream` or `config set stream false`.
213
+ Every adapter supports it — native OpenAI / Anthropic / Gemini adapters stream
214
+ for real, others transparently fall back to a single chunk.
215
+ - **LLM-based memory extraction** (opt-in): set `extraction_mode = "llm"` to let
216
+ a model pull durable first-person facts from each message — higher recall than
217
+ the heuristic patterns. Configurable extraction model (`extraction_model`),
218
+ its cost is traced, and it falls back to the heuristic extractor on any error.
219
+ - **MCP server** (`engram mcp`): exposes your memory to any MCP-aware agent
220
+ (Claude Desktop, Claude Code, Cursor, …) as tools — `remember`,
221
+ `recall_search`, `list_memories`, `forget`, `usage_stats`. Same local SQLite
222
+ store, nothing leaves your machine. Install with `pip install 'engram-ai[mcp]'`.
223
+ - **Memory editing**: `engram edit <id> "new content" [--tags ...]` updates a
224
+ memory in place and re-embeds it. Library: `Recall.edit(...)`.
225
+ - **Similarity merge / dedupe**: `engram dedupe [--threshold 0.9] [--all]
226
+ [--dry-run]` clusters near-duplicate memories by cosine similarity, keeps the
227
+ earliest, and unions their tags. Optional on-add suppression via
228
+ `config set dedupe_similarity 0.95` (both need embeddings; exact dedupe still
229
+ works without them).
230
+ - **Hybrid retrieval**: memory search now fuses semantic (embeddings) with
231
+ BM25 keyword ranking (SQLite FTS5) via Reciprocal Rank Fusion — exact terms,
232
+ names, and IDs the embeddings blur now surface, while semantically-close
233
+ memories still rank. FTS5 stays in sync via triggers and falls back to LIKE
234
+ where unavailable. No new dependencies. *(mem0 / Zep do the same.)*
235
+ - **Budget hard-stop**: `config set budget_enforce true` makes recall *refuse*
236
+ a call once today's spend hits `daily_budget_usd` (raises `BudgetExceeded`),
237
+ instead of only warning. *(LiteLLM-style.)*
238
+
239
+ ### Fixed
240
+ - Install hints (`engram-ai[dashboard]`, `engram-ai[mcp]`) and model output are
241
+ no longer mangled by Rich markup parsing.
242
+
243
+ ### Packaging
244
+ - PyPI Trusted Publishing workflow (`.github/workflows/publish.yml`): push a
245
+ `vX.Y.Z` tag to publish. CI workflow runs lint + tests on 3.9 / 3.11 / 3.12.
246
+
247
+ ## [0.2.0]
248
+
249
+ Major feature round.
250
+
251
+ ### Added
252
+ - **Auto-memory**: after each chat, recall heuristically captures durable
253
+ first-person facts/preferences (English + Chinese), tagged as `auto`.
254
+ - **Memory scopes**: isolate memories per project/context
255
+ (`engram scope work`, `--scope`, `--all`). Active scope stored in config.
256
+ - **Config system** (`~/.engram/config.json`): default provider/model, daily
257
+ budget, auto-memory toggle, inject limit, active scope.
258
+ CLI: `engram config show|set|path`.
259
+ - **Daily budget + warnings**: set `daily_budget_usd`; chat and `stats` warn at
260
+ 80% and 100% of the day's spend.
261
+ - **Guided setup**: `engram init` (pick defaults, detect keys) and
262
+ `engram doctor` (which providers are ready).
263
+ - **Export / import**: `engram export mem.json` / `engram import mem.json`
264
+ (JSON, dedupe-aware).
265
+ - **Deduplication**: identical memories in the same scope are skipped.
266
+ - **Flexible chat**: `engram chat "prompt"` uses configured defaults; the
267
+ three-arg form still works.
268
+ - **Dashboard upgrades**: auto-refresh, daily-budget bar, scope chips.
269
+ - Safe schema migrations for existing databases.
270
+
271
+ ### Changed
272
+ - `Recall.chat()` now returns a richer `ChatOutcome` (cost, latency,
273
+ auto-remembered list, budget warning).
274
+
275
+ ## [0.1.0]
276
+
277
+ First MVP.
278
+
279
+ ### Added
280
+ - Local-first SQLite store (`~/.engram/recall.db`) for memories + call traces.
281
+ - Memory engine with semantic search (sentence-transformers) and automatic
282
+ keyword fallback when embeddings are not installed.
283
+ - **22 model providers** out of the box: OpenAI, Anthropic, Gemini, DeepSeek,
284
+ Qwen, Moonshot (Kimi), Zhipu (GLM), MiniMax, Baichuan, 01.AI (Yi), StepFun,
285
+ Mistral, xAI (Grok), Groq, Together, Fireworks, DeepInfra, Perplexity,
286
+ OpenRouter, Ollama, LM Studio, and any OpenAI-compatible endpoint.
287
+ - Per-provider API key env var resolution with `ENGRAM_API_KEY` fallback.
288
+ - Expanded pricing table covering the new providers.
289
+ - Automatic per-call tracing: tokens, estimated cost, latency.
290
+ - CLI: `add`, `search`, `list`, `forget`, `chat`, `stats`, `recent`, `models`,
291
+ `dashboard`, `version`.
292
+ - Minimal local web dashboard (FastAPI, single page, no build step).
293
+ - Library API via `from recall import Recall`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zionLyl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.