memcord 4.0.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 (48) hide show
  1. memcord-4.0.0/.gitignore +148 -0
  2. memcord-4.0.0/LICENSE +21 -0
  3. memcord-4.0.0/PKG-INFO +311 -0
  4. memcord-4.0.0/README.md +267 -0
  5. memcord-4.0.0/config-templates/README.md +196 -0
  6. memcord-4.0.0/pyproject.toml +221 -0
  7. memcord-4.0.0/src/memcord/__init__.py +7 -0
  8. memcord-4.0.0/src/memcord/archival.py +415 -0
  9. memcord-4.0.0/src/memcord/batch_operations.py +623 -0
  10. memcord-4.0.0/src/memcord/cache.py +821 -0
  11. memcord-4.0.0/src/memcord/compression.py +233 -0
  12. memcord-4.0.0/src/memcord/constants.py +38 -0
  13. memcord-4.0.0/src/memcord/errors.py +393 -0
  14. memcord-4.0.0/src/memcord/feedback_messages.py +786 -0
  15. memcord-4.0.0/src/memcord/handler_registry.py +135 -0
  16. memcord-4.0.0/src/memcord/importer.py +373 -0
  17. memcord-4.0.0/src/memcord/llm_summarizer.py +245 -0
  18. memcord-4.0.0/src/memcord/logging_config.py +147 -0
  19. memcord-4.0.0/src/memcord/memory_manager.py +647 -0
  20. memcord-4.0.0/src/memcord/merger.py +282 -0
  21. memcord-4.0.0/src/memcord/models.py +603 -0
  22. memcord-4.0.0/src/memcord/optimized_schemas.py +337 -0
  23. memcord-4.0.0/src/memcord/optimized_server.py +300 -0
  24. memcord-4.0.0/src/memcord/progress_integration.py +315 -0
  25. memcord-4.0.0/src/memcord/progress_tracker.py +584 -0
  26. memcord-4.0.0/src/memcord/prompts.py +377 -0
  27. memcord-4.0.0/src/memcord/query.py +278 -0
  28. memcord-4.0.0/src/memcord/response_builder.py +141 -0
  29. memcord-4.0.0/src/memcord/response_optimizer.py +339 -0
  30. memcord-4.0.0/src/memcord/search.py +385 -0
  31. memcord-4.0.0/src/memcord/security.py +388 -0
  32. memcord-4.0.0/src/memcord/server.py +2853 -0
  33. memcord-4.0.0/src/memcord/services/__init__.py +78 -0
  34. memcord-4.0.0/src/memcord/services/archive_service.py +290 -0
  35. memcord-4.0.0/src/memcord/services/compression_service.py +283 -0
  36. memcord-4.0.0/src/memcord/services/import_service.py +160 -0
  37. memcord-4.0.0/src/memcord/services/merge_service.py +297 -0
  38. memcord-4.0.0/src/memcord/services/monitoring_service.py +397 -0
  39. memcord-4.0.0/src/memcord/services/select_entry_service.py +230 -0
  40. memcord-4.0.0/src/memcord/smart_defaults.py +924 -0
  41. memcord-4.0.0/src/memcord/status_monitoring.py +809 -0
  42. memcord-4.0.0/src/memcord/storage.py +1383 -0
  43. memcord-4.0.0/src/memcord/storage_efficiency.py +1258 -0
  44. memcord-4.0.0/src/memcord/summarizer.py +415 -0
  45. memcord-4.0.0/src/memcord/summarizer_base.py +39 -0
  46. memcord-4.0.0/src/memcord/summarizer_factory.py +84 -0
  47. memcord-4.0.0/src/memcord/temporal_parser.py +211 -0
  48. memcord-4.0.0/src/memcord/workflow_templates.py +1032 -0
@@ -0,0 +1,148 @@
1
+ # Claude Code internal files (not for repository)
2
+ OPTIMIZE_PLAN.md
3
+ SMART_SAVE.md
4
+
5
+ # Release sync configuration (dev repo only)
6
+ CLAUDE-RECOVERY-PROMPT.md
7
+ SESSION-RECOVERY.md
8
+ TASK-MATRIX.md
9
+ DRY_MAINTAINABILITY_ANALYSIS.md
10
+ TESTING_METHODOLOGY_VALIDATION.md
11
+ PR-DESCRIPTION*.md
12
+ PR-READINESS*.md
13
+ TODO.md
14
+ .claude/agents/
15
+ .claude/utils/
16
+ .claude/skills/
17
+ .claude/settings.json
18
+ .claude/settings.local.json
19
+ .claude-plugin/
20
+ commands/
21
+ docs/superpowers/
22
+ hooks/
23
+ tmpclaude-*
24
+
25
+ # Python
26
+ __pycache__/
27
+ *.py[cod]
28
+ *$py.class
29
+ *.so
30
+ .Python
31
+ build/
32
+ develop-eggs/
33
+ dist/
34
+ downloads/
35
+ eggs/
36
+ .eggs/
37
+ lib/
38
+ lib64/
39
+ parts/
40
+ sdist/
41
+ var/
42
+ wheels/
43
+ *.egg-info/
44
+ .installed.cfg
45
+ *.egg
46
+ MANIFEST
47
+
48
+ # PyInstaller
49
+ *.manifest
50
+ *.spec
51
+
52
+ # Unit test / coverage reports
53
+ htmlcov/
54
+ .tox/
55
+ .nox/
56
+ .coverage
57
+ .coverage.*
58
+ .cache
59
+ nosetests.xml
60
+ coverage.xml
61
+ *.cover
62
+ .hypothesis/
63
+ .pytest_cache/
64
+
65
+ # Virtual environments
66
+ .env
67
+ .venv
68
+ env/
69
+ venv/
70
+ ENV/
71
+ env.bak/
72
+ venv.bak/
73
+
74
+ # IDEs and editors
75
+ .idea/
76
+ *.swp
77
+ *.swo
78
+ *~
79
+
80
+ # Generated MCP configuration files (templates are in config-templates/)
81
+ .mcp.json
82
+ claude_desktop_config.json
83
+ .vscode/mcp.json
84
+ .antigravity/
85
+ .memcord
86
+
87
+ # OS generated files
88
+ .DS_Store
89
+ .DS_Store?
90
+ ._*
91
+ .Spotlight-V100
92
+ .Trashes
93
+ ehthumbs.db
94
+ Thumbs.db
95
+ nul
96
+ *.txt
97
+
98
+ # Project specific
99
+ memory_slots/
100
+ shared_memories/
101
+ archives/
102
+ cache/
103
+ logs/
104
+ *.log
105
+
106
+ # Temporary files
107
+ *.tmp
108
+ *.temp
109
+ .tmp/
110
+ .temp/
111
+
112
+ # Development
113
+ .env.local
114
+ .env.development
115
+ .env.test
116
+ .env.production
117
+
118
+ # Database
119
+ *.db
120
+ *.sqlite
121
+ *.sqlite3
122
+
123
+ # Jupyter Notebooks
124
+ .ipynb_checkpoints
125
+
126
+ # pyenv
127
+ .python-version
128
+
129
+ # UV
130
+ .uv/
131
+
132
+ # mypy
133
+ .mypy_cache/
134
+ .dmypy.json
135
+ dmypy.json
136
+
137
+ # Pyre type checker
138
+ .pyre/
139
+
140
+ # PyCharm
141
+ .idea/
142
+
143
+ # Ruff
144
+ .ruff_cache/
145
+
146
+ # Dev-only scripts
147
+ scripts/publish-to-main.sh
148
+ scripts/publish-to-main.ps1
memcord-4.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Neeraj Tikku
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.
memcord-4.0.0/PKG-INFO ADDED
@@ -0,0 +1,311 @@
1
+ Metadata-Version: 2.4
2
+ Name: memcord
3
+ Version: 4.0.0
4
+ Summary: privacy-first, self-hosted MCP server helps you organize chat history, summarize messages, search across past chats with AI — and keeps everything secure and fully under your control.
5
+ Project-URL: Homepage, https://github.com/ukkit/memcord
6
+ Project-URL: Repository, https://github.com/ukkit/memcord
7
+ Project-URL: Bug Tracker, https://github.com/ukkit/memcord/issues
8
+ Author-email: MemCord <memcord@ultrafastidio.us>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: aiofiles>=23.0
13
+ Requires-Dist: beautifulsoup4>=4.12.0
14
+ Requires-Dist: mcp<2,>=1.27.1
15
+ Requires-Dist: nltk>=3.8
16
+ Requires-Dist: pandas>=2.0.0
17
+ Requires-Dist: pdfplumber>=0.10.0
18
+ Requires-Dist: pydantic>=2.0.0
19
+ Requires-Dist: pypdfium2>=5.0.0
20
+ Requires-Dist: python-magic>=0.4.27
21
+ Requires-Dist: requests>=2.31.0
22
+ Requires-Dist: sumy>=0.11.0
23
+ Requires-Dist: trafilatura>=1.8.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: bandit>=1.7.0; extra == 'dev'
26
+ Requires-Dist: black>=23.0.0; extra == 'dev'
27
+ Requires-Dist: factory-boy>=3.3.0; extra == 'dev'
28
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
29
+ Requires-Dist: psutil>=5.9.0; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
31
+ Requires-Dist: pytest-benchmark>=4.0.0; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
33
+ Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
34
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
36
+ Requires-Dist: selenium>=4.15.0; extra == 'dev'
37
+ Requires-Dist: webdriver-manager>=4.0.0; extra == 'dev'
38
+ Provides-Extra: semantic
39
+ Requires-Dist: sentence-transformers>=3.0.0; extra == 'semantic'
40
+ Provides-Extra: transformers
41
+ Requires-Dist: torch>=2.0.0; extra == 'transformers'
42
+ Requires-Dist: transformers>=4.40.0; extra == 'transformers'
43
+ Description-Content-Type: text/markdown
44
+
45
+ <div align="center">
46
+ <img src="assets/image/memcord_1024.png" width="256">
47
+ <h3>MEMCORD v4.0.0 (mcp server)</h3>
48
+ <p>This privacy-first, self-hosted MCP server helps you organize chat history, summarize messages, search across past chats with AI — and keeps everything secure and fully under your control.</p>
49
+ </div>
50
+
51
+ <p align="center">
52
+ <a href="https://github.com/modelcontextprotocol"><img src="https://img.shields.io/badge/MCP-Server-blue" alt="MCP Server"></a>
53
+ <a href="https://docs.anthropic.com/claude/docs/claude-code"><img src="https://img.shields.io/badge/Claude-Code-purple" alt="Claude Code"></a>
54
+ <a href="https://claude.ai/desktop"><img src="https://img.shields.io/badge/Claude-Desktop-orange" alt="Claude Desktop"></a>
55
+ <a href="https://code.visualstudio.com/"><img src="https://img.shields.io/badge/Visual_Studio-Code-orange" alt="VSCode"></a>
56
+ <a href="https://antigravity.google"><img src="https://img.shields.io/badge/Google-Antigravity-4285F4" alt="Google Antigravity"></a>
57
+ <a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.10+-green" alt="Python"></a>
58
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License"></a>
59
+ <a href="https://buymeacoffee.com/ukkit"><img src="https://img.shields.io/badge/Buy%20Me%20A-Coffee-white" alt="Buy Me a Coffee"></a>
60
+ </p>
61
+
62
+ <h2 align="center">Never Lose Context Again</h2>
63
+ <p align="center"><em>Transform your Claude conversations into a searchable, organized knowledge base that grows with you</em></p>
64
+
65
+ > **[What's new in v4.0.0](docs/versions.md#v400---openclaw--clawhub-compatibility)** — OpenClaw / ClaWHub compatibility: new `memcord_auto_save` tool for zero-setup gateway integrations, `MEMCORD_DEFAULT_SLOT` env var fallback in slot resolution, and `SKILL.md` for ClaWHub discovery.
66
+
67
+ ## Table of Contents
68
+
69
+ - [Core Benefits](#core-benefits)
70
+ - [Prerequisites](#prerequisites)
71
+ - [Quick Start](#quick-start)
72
+ - [Demo](#demo)
73
+ - [IDE Configuration](#ide-configuration)
74
+ - [Keeping Memcord Updated](#keeping-memcord-updated)
75
+ - [Using Memcord in a Project](#using-memcord-in-a-project)
76
+ - [Basic Usage](#basic-usage)
77
+ - [Summarizer Backends](#summarizer-backends)
78
+ - [Documentation](#documentation)
79
+
80
+ ## Core Benefits
81
+
82
+ * **Infinite Memory** - Claude remembers everything across unlimited conversations with intelligent auto-summarization
83
+ * **Your Data, Your Control** - 100% local storage with zero cloud dependencies or privacy concerns
84
+ * **Effortless Organization** - Per-project memory slots with timeline navigation and smart tagging
85
+ * **Intelligent Merging** - Automatically combines related conversations while eliminating duplicates
86
+
87
+ ## Prerequisites
88
+
89
+ <details>
90
+ <summary>Python 3.10+ and uv are required. The installer handles both — click to expand manual instructions.</summary>
91
+
92
+ - **Python 3.10+** — [python.org](https://python.org)
93
+ - **uv** (Python package manager) — install with:
94
+
95
+ **macOS / Linux:**
96
+ ```bash
97
+ curl -LsSf https://astral.sh/uv/install.sh | sh
98
+ ```
99
+ **Windows (PowerShell):**
100
+ ```powershell
101
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
102
+ ```
103
+
104
+ </details>
105
+
106
+ ## Quick Start
107
+
108
+ **macOS / Linux:**
109
+ ```bash
110
+ curl -fsSL https://github.com/ukkit/memcord/raw/main/install.sh | bash
111
+ ```
112
+
113
+ **Windows (PowerShell):**
114
+ ```powershell
115
+ irm https://github.com/ukkit/memcord/raw/main/install.ps1 | iex
116
+ ```
117
+
118
+ This will:
119
+ - ✅ Download and setup **memcord**
120
+ - ✅ Set up Python virtual environment using uv
121
+ - ✅ Generate platform-specific MCP configuration files
122
+ - ✅ Configure Claude Desktop, Claude Code, VSCode, and Antigravity IDE
123
+
124
+ ## Demo
125
+
126
+ _A demo GIF or terminal recording will be added here. Contributions welcome!_
127
+
128
+ ## IDE Configuration
129
+
130
+ The installer auto-configures all supported IDEs. For manual setup or troubleshooting, see the detailed guides:
131
+
132
+ | IDE / Client | Guide |
133
+ |---|---|
134
+ | Claude Code CLI | [Installation Guide — Claude Code](docs/installation.md#claude-code-cli-recommended-) |
135
+ | Claude Desktop | [Installation Guide — Claude Desktop](docs/installation.md#claude-desktop) |
136
+ | VSCode + GitHub Copilot | [VSCode Setup Guide](docs/vscode-setup.md) |
137
+ | Google Antigravity | [Installation Guide — Other MCP Apps](docs/installation.md#other-mcp-applications) |
138
+ | Configuration templates | [`config-templates/`](config-templates/) ([README](config-templates/README.md)) |
139
+
140
+ ### Manual Installation
141
+
142
+ ```bash
143
+ git clone https://github.com/ukkit/memcord.git
144
+ cd memcord
145
+ uv venv && uv pip install -e .
146
+ uv run python scripts/generate-config.py
147
+ ```
148
+
149
+ See the **[Complete Installation Guide](docs/installation.md)** for updating, advanced options, and custom commands.
150
+
151
+ ## Keeping Memcord Updated
152
+
153
+ ```bash
154
+ cd /path/to/memcord
155
+ git pull
156
+ uv pip install -e .
157
+ uv run python scripts/generate-config.py # Regenerate configs
158
+
159
+ # Optional: Enable auto-save hooks (new in v2.5.0)
160
+ uv run python scripts/generate-config.py --install-hooks
161
+ ```
162
+
163
+ The `--install-hooks` flag is idempotent — it merges into existing `.claude/settings.json` without overwriting other settings or hooks.
164
+
165
+ <a id="using-memcord-in-a-project"></a>
166
+
167
+ ## Using Memcord in a Project
168
+
169
+ ### First-Time Setup (New Project)
170
+
171
+ ```bash
172
+ # 1. Once you are in claude code, initialize the project with a memory slot (one-time setup)
173
+ memcord_init "." "my-project-name"
174
+ # OR
175
+ memcord_init "my_project_name"
176
+ # Creates .memcord file containing "my-project-name"
177
+
178
+ # 2. Start saving your conversations
179
+ /memcord-save-progress # Auto-detects slot from .memcord file
180
+ ```
181
+
182
+ ### Subsequent Sessions (Returning to Project)
183
+
184
+ ```bash
185
+ # Just use slash commands - no slot name needed!
186
+ /memcord-read # Reads from bound slot automatically
187
+
188
+ /memcord-save # Saves to bound slot automatically
189
+ /memcord-save-progress # Summarizes and saves automatically
190
+ ```
191
+
192
+ ### Enable Auto-Save (Optional)
193
+
194
+ ```bash
195
+ uv run python scripts/generate-config.py --install-hooks
196
+ ```
197
+
198
+ Automatically saves conversation progress before context compaction and on session end. See [config-templates/README.md](config-templates/README.md#auto-save-hooks-optional) for details.
199
+
200
+ ### How Auto-Detection Works
201
+
202
+ All read **and write** operations follow the same slot resolution priority:
203
+
204
+ 1. Explicit `slot_name` argument (always wins)
205
+ 2. Currently active slot (set by `memcord_use` or `memcord_name`)
206
+ 3. `.memcord` binding file in the current working directory
207
+
208
+ When the `.memcord` binding is used and the slot already exists, it is also **auto-activated** for the rest of the session — so subsequent operations skip re-detection automatically.
209
+
210
+ This means after `memcord_init`, a fresh session (no `memcord_use` call needed) will correctly route `memcord_save`, `memcord_save_progress`, `memcord_configure`, and `memcord_read` to the bound slot.
211
+
212
+ ## Basic Usage
213
+
214
+ ### Saving & Retrieving
215
+
216
+ ```bash
217
+ memcord_name "project_meeting" # Create or select a slot
218
+ memcord_save "Our discussion about..." # Save exact text
219
+ memcord_save_progress # Save a compressed summary
220
+ memcord_read # Read the slot
221
+ ```
222
+
223
+ ### Navigating & Searching
224
+
225
+ ```bash
226
+ memcord_select_entry "2 hours ago" # Jump to a point in the timeline
227
+ memcord_list # List all slots
228
+ memcord_search "API design" # Full-text search
229
+ memcord_query "What did we decide?" # Natural language query
230
+ ```
231
+
232
+ ### Project & Privacy
233
+
234
+ ```bash
235
+ memcord_init "." "my-project" # Bind a memory slot to this directory
236
+ memcord_zero # Privacy mode — nothing gets saved
237
+ ```
238
+
239
+ See **[Complete Tools Reference](docs/tools-reference.md)** for all 23 tools with full parameters and examples.
240
+
241
+ ## Summarizer Backends
242
+
243
+ Memcord supports four summarizer backends. New slots default to **sumy** (graph-based, no downloads required). Existing slots keep **nltk** to preserve prior behavior.
244
+
245
+ | Backend | Type | Speed | Quality | Extra install |
246
+ |---|---|---|---|---|
247
+ | `nltk` | Extractive | Fast | Good | None (built-in) |
248
+ | `sumy` | Extractive (graph) | Fast | Better | None (built-in) |
249
+ | `semantic` | Extractive (embeddings) | Medium | Best extractive | `uv pip install "memcord[semantic]"` (~80 MB) |
250
+ | `transformers` | Abstractive (BART) | Slow | Best overall | `uv pip install "memcord[transformers]"` (~400 MB) |
251
+
252
+ ### Switching Backends
253
+
254
+ Use `memcord_configure` to change the backend for any slot — no restart required:
255
+
256
+ ```bash
257
+ # Check current config
258
+ memcord_configure action="get"
259
+
260
+ # Switch to the BART abstractive summarizer (best for conversations)
261
+ memcord_configure action="set" key="summarizer_backend" value="transformers"
262
+
263
+ # Switch to embedding-based semantic summarizer
264
+ memcord_configure action="set" key="summarizer_backend" value="semantic"
265
+
266
+ # Switch sumy algorithm (lexrank / lsa / edmundson)
267
+ memcord_configure action="set" key="sumy_algorithm" value="lsa"
268
+
269
+ # Reset to defaults
270
+ memcord_configure action="reset"
271
+ ```
272
+
273
+ To apply one backend to **all slots** (e.g. in Docker or CI), set the environment variable:
274
+
275
+ ```bash
276
+ export MEMCORD_SUMMARIZER=transformers
277
+ ```
278
+
279
+ See **[Tools Reference — memcord_configure](docs/tools-reference.md)** for the full parameter list.
280
+
281
+ ## Documentation
282
+
283
+ | Guide | Description |
284
+ |---|---|
285
+ | **[Installation Guide](docs/installation.md)** | Complete setup instructions for all MCP applications |
286
+ | **[Feature Guide](docs/features-guide.md)** | Complete list of features |
287
+ | **[Tools Reference](docs/tools-reference.md)** | Detailed documentation for all 23 tools |
288
+ | **[Import & Merge Guide](docs/import-and-merge.md)** | Comprehensive guide for Phase 3 features |
289
+ | **[Search & Query Guide](docs/search-and-query.md)** | Advanced search features and natural language queries |
290
+ | **[Usage Examples](docs/examples.md)** | Real-world workflows and practical use cases |
291
+ | **[Data Format Specification](docs/data-format.md)** | Technical details and file formats |
292
+ | **[Troubleshooting](docs/troubleshooting.md)** | Common issues and solutions |
293
+ | **[Version History](docs/versions.md)** | Changelog for all releases |
294
+
295
+ ---
296
+
297
+ If you find this project helpful, consider:
298
+
299
+ - ⭐ Starring the repository on GitHub
300
+ - ☕ [Support Development](https://buymeacoffee.com/ukkit)
301
+ - 🐛 Reporting bugs and suggesting features
302
+
303
+ ---
304
+
305
+ **MIT License** - see LICENSE file for details.
306
+
307
+ ---
308
+
309
+ ## Star History
310
+
311
+ [![Star History Chart](https://api.star-history.com/svg?repos=ukkit/memcord&type=date&legend=top-left)](https://www.star-history.com/#ukkit/memcord&type=date&legend=top-left)