memleaf 0.1.2__tar.gz → 0.1.4__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 (102) hide show
  1. memleaf-0.1.4/CHANGELOG.md +61 -0
  2. {memleaf-0.1.2 → memleaf-0.1.4}/MANIFEST.in +1 -0
  3. {memleaf-0.1.2 → memleaf-0.1.4}/PKG-INFO +37 -39
  4. {memleaf-0.1.2 → memleaf-0.1.4}/README.en.md +36 -37
  5. {memleaf-0.1.2 → memleaf-0.1.4}/README.md +35 -36
  6. memleaf-0.1.4/install.ps1 +88 -0
  7. {memleaf-0.1.2 → memleaf-0.1.4}/integrations/hermes/memleaf/plugin.yaml +1 -1
  8. {memleaf-0.1.2 → memleaf-0.1.4}/pyproject.toml +3 -3
  9. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/__init__.py +1 -1
  10. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/adapters/base.py +13 -2
  11. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/adapters/hermes.py +58 -6
  12. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/cli.py +9 -4
  13. memleaf-0.1.4/src/memleaf/credentials.py +57 -0
  14. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  15. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/installer.py +15 -10
  16. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/router.py +5 -4
  17. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/locking.py +4 -1
  18. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/model_discovery.py +32 -15
  19. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf.egg-info/PKG-INFO +37 -39
  20. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf.egg-info/SOURCES.txt +3 -1
  21. memleaf-0.1.4/tests/test_credential_safety.py +98 -0
  22. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_model_discovery.py +102 -0
  23. memleaf-0.1.4/tests/test_pypi_install.py +112 -0
  24. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_c1_mcp.py +1 -1
  25. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_c3_packaging.py +3 -3
  26. memleaf-0.1.2/CHANGELOG.md +0 -54
  27. memleaf-0.1.2/LICENSE +0 -235
  28. memleaf-0.1.2/tests/test_pypi_install.py +0 -52
  29. {memleaf-0.1.2 → memleaf-0.1.4}/IMPLEMENTATION_PLAN.md +0 -0
  30. /memleaf-0.1.2/LICENSES/MIT-0.1.0-0.1.1.txt → /memleaf-0.1.4/LICENSE +0 -0
  31. {memleaf-0.1.2 → memleaf-0.1.4}/RELEASE_CHECKLIST.md +0 -0
  32. {memleaf-0.1.2 → memleaf-0.1.4}/RELEASE_NOTES_v0.1.md +0 -0
  33. {memleaf-0.1.2 → memleaf-0.1.4}/V2_IMPLEMENTATION_PLAN.md +0 -0
  34. {memleaf-0.1.2 → memleaf-0.1.4}/examples/README.md +0 -0
  35. {memleaf-0.1.2 → memleaf-0.1.4}/examples/basic_usage.py +0 -0
  36. {memleaf-0.1.2 → memleaf-0.1.4}/examples/mcp_stdio.ndjson +0 -0
  37. {memleaf-0.1.2 → memleaf-0.1.4}/install.sh +0 -0
  38. {memleaf-0.1.2 → memleaf-0.1.4}/integrations/hermes/memleaf/README.md +0 -0
  39. {memleaf-0.1.2 → memleaf-0.1.4}/integrations/hermes/memleaf/__init__.py +0 -0
  40. {memleaf-0.1.2 → memleaf-0.1.4}/setup.cfg +0 -0
  41. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/__main__.py +0 -0
  42. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/adapters/__init__.py +0 -0
  43. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/adapters/antigravity.py +0 -0
  44. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/adapters/codex.py +0 -0
  45. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/budget.py +0 -0
  46. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/capture.py +0 -0
  47. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/compaction.py +0 -0
  48. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/config.py +0 -0
  49. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/frontmatter.py +0 -0
  50. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/hermes_provider/README.md +0 -0
  51. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/hermes_provider/__init__.py +0 -0
  52. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/host_events.py +0 -0
  53. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/inbox.py +0 -0
  54. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/index.py +0 -0
  55. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/__init__.py +0 -0
  56. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/base.py +0 -0
  57. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/claude_compatible.py +0 -0
  58. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/gemini.py +0 -0
  59. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/llm/openai_compatible.py +0 -0
  60. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/mcp_server.py +0 -0
  61. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/memory_writer.py +0 -0
  62. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/models.py +0 -0
  63. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/native_index.py +0 -0
  64. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/processing.py +0 -0
  65. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/prompts.py +0 -0
  66. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/redaction.py +0 -0
  67. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/retrieval.py +0 -0
  68. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/retrieval_gate.py +0 -0
  69. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/scope_maintenance.py +0 -0
  70. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/scope_state.py +0 -0
  71. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/service.py +0 -0
  72. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/validation.py +0 -0
  73. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf/vault.py +0 -0
  74. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf.egg-info/dependency_links.txt +0 -0
  75. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf.egg-info/entry_points.txt +0 -0
  76. {memleaf-0.1.2 → memleaf-0.1.4}/src/memleaf.egg-info/top_level.txt +0 -0
  77. {memleaf-0.1.2 → memleaf-0.1.4}/tests/__init__.py +0 -0
  78. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_admission_noise.py +0 -0
  79. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_context_budget.py +0 -0
  80. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_hermes_provider.py +0 -0
  81. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_host_events.py +0 -0
  82. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_install.py +0 -0
  83. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_maintenance_v2.py +0 -0
  84. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_retrieval_gate.py +0 -0
  85. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_retrieval_v2.py +0 -0
  86. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_a.py +0 -0
  87. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b1.py +0 -0
  88. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b2a.py +0 -0
  89. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b2b.py +0 -0
  90. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3a_commit.py +0 -0
  91. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3a_contract.py +0 -0
  92. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3b_native_context.py +0 -0
  93. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3b_native_index.py +0 -0
  94. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3b_scope.py +0 -0
  95. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3c_retrieval.py +0 -0
  96. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_b3d_scope_maintenance.py +0 -0
  97. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_stage_c2_init.py +0 -0
  98. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_v2_gate_limits.py +0 -0
  99. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_v2_host_flow.py +0 -0
  100. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_v2_mcp_flow.py +0 -0
  101. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_v2_nomatch_semantics.py +0 -0
  102. {memleaf-0.1.2 → memleaf-0.1.4}/tests/test_v2_search_gate_acceptance.py +0 -0
@@ -0,0 +1,61 @@
1
+ # Changelog
2
+
3
+ All notable changes to memleaf are documented here.
4
+
5
+ ## 0.1.4 — 2026-08-28
6
+
7
+ - Reject Hermes display-redacted API credentials such as `***` and head/tail masks instead of treating them as callable keys.
8
+ - Fall through from a redacted direct credential to configured environment variables and Hermes `.env` values.
9
+ - Reject masked credentials in existing memleaf routes and at runtime so an old bad route cannot remain silently usable.
10
+ - Add regression coverage for masked Hermes credentials on Linux and Windows.
11
+
12
+ ## 0.1.3 — 2026-08-28
13
+
14
+ - Restored the MIT License for the current release.
15
+ - Added native Windows Hermes path discovery using `HERMES_HOME` or the official
16
+ `%LOCALAPPDATA%\hermes` default.
17
+ - Added discovery for the official Windows Hermes launchers under
18
+ `bin\hermes.exe`, `bin\hermes.cmd`, and
19
+ `hermes-agent\venv\Scripts\hermes.exe`.
20
+ - Added a one-line PowerShell installer that uses Hermes' managed Python when
21
+ available, installs memleaf from PyPI, and completes Hermes integration.
22
+ - Made atomic file writes and Windows command-launcher detection portable.
23
+ - Added Windows GitHub Actions coverage for Python 3.11, 3.12, and 3.13.
24
+
25
+ ## 0.1.2 — 2026-08-28
26
+
27
+ - This release was published under AGPL-3.0-only before the project returned to
28
+ MIT in 0.1.3. Its original license remains valid for copies of 0.1.2.
29
+ - Product behavior otherwise remained aligned with 0.1.1.
30
+
31
+ ## 0.1.1 — 2026-08-28 (GitHub tag: v0.1.1)
32
+
33
+ - Added a first-class `memleaf install` command for complete Hermes setup
34
+ after installing the package from PyPI.
35
+ - Packaged the Hermes MemoryProvider inside the Python distribution, removing
36
+ the need for a Git checkout during normal installation.
37
+ - Added safe migration from the source-installed provider symlink to the
38
+ packaged provider.
39
+ - Added `python -m memleaf` so the complete one-line install works even when
40
+ the console-script directory is not already on PATH.
41
+ - The installer initializes the Vault, discovers or preserves a model route,
42
+ activates the Hermes MemoryProvider, configures MCP lifecycle settings, and
43
+ verifies the 11-tool MCP surface.
44
+
45
+ ## 0.1.0 — 2026-08-28 (GitHub tag: v0.1)
46
+
47
+ - Added the local-first, dependency-free Markdown vault core with capture,
48
+ memory creation, deterministic search/context retrieval, statistics, and
49
+ rebuildable indexes.
50
+ - Added safe vault paths, file locking/atomic writes, frontmatter handling, and
51
+ secret redaction for captured text.
52
+ - Added model-backed admission, extraction, state updates with history,
53
+ compaction, retryable processing, and scope-aware maintenance without adding
54
+ runtime dependencies.
55
+ - Added the `memleaf` initialization CLI and the `memleaf-mcp` stdio entry
56
+ point.
57
+ - Added Scope Map → directory search → bounded read retrieval with per-turn
58
+ retrieval state, pagination, and read budgets.
59
+ - Added a Hermes-native MemoryProvider plus MCP setup.
60
+ - Added packaging metadata, examples, offline local installation, and CI for
61
+ Python 3.11–3.13.
@@ -1,6 +1,7 @@
1
1
  include CHANGELOG.md
2
2
  include LICENSE
3
3
  include install.sh
4
+ include install.ps1
4
5
  include README.md
5
6
  include README.en.md
6
7
  include RELEASE_CHECKLIST.md
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
- License-Expression: AGPL-3.0-only
6
+ License-Expression: MIT
7
7
  Keywords: ai-agents,local-first,markdown,memory
8
8
  Classifier: Intended Audience :: Developers
9
9
  Classifier: Programming Language :: Python :: 3
@@ -15,7 +15,6 @@ Classifier: Topic :: Software Development :: Libraries
15
15
  Requires-Python: >=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- License-File: LICENSES/MIT-0.1.0-0.1.1.txt
19
18
  Dynamic: license-file
20
19
 
21
20
  # 冥想盆 · memleaf
@@ -24,8 +23,8 @@ Dynamic: license-file
24
23
 
25
24
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
26
25
 
27
- > **当前版本:0.1.2。**
28
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.1.2 已发布到 PyPI。
26
+ > **当前版本:0.1.4。**
27
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.1.4 通过 PyPI 分发。
29
28
  > **当前版本仅支持 Hermes。** Codex 与反重力不检测、不安装、不配置,也不扫描其模型配置。
30
29
 
31
30
  ## 项目定位
@@ -102,21 +101,40 @@ memleaf 不把每句话都保存为记忆。处理一轮完整的 user + assista
102
101
 
103
102
  ## 安装
104
103
 
105
- 要求:Python 3.11+;当前版本的完整自动接入仅支持 Hermes。
104
+ 要求 Python 3.11+;当前版本的完整自动接入支持 Hermes。
106
105
 
107
- 普通用户只需要复制执行这一行:
106
+ ### Windows
107
+
108
+ 已经安装 Hermes 的 Windows 10/11 用户,在 **PowerShell** 中只需要执行一行:
109
+
110
+ ```powershell
111
+ irm https://raw.githubusercontent.com/miffyblueboo/memleaf/main/install.ps1 | iex
112
+ ```
113
+
114
+ Windows 安装器会优先使用 Hermes 自带的 Python 环境,因此不要求 Hermes 的 Python 已加入系统 PATH。它会从 PyPI 安装或升级 memleaf,然后自动完成 Hermes 接入。
115
+
116
+ 默认情况下会识别 Hermes 官方 Windows 安装位置:
117
+
118
+ ```text
119
+ %USERPROFILE%\.memleaf\ # memleaf 数据 Vault
120
+ %LOCALAPPDATA%\hermes\plugins\memleaf\ # Hermes MemoryProvider
121
+ %LOCALAPPDATA%\hermes\memleaf.json # Provider 配置
122
+ %LOCALAPPDATA%\hermes\bin\hermes.exe / hermes.cmd # Hermes 命令入口
123
+ ```
124
+
125
+ 如果设置了 `HERMES_HOME`,memleaf 会优先使用该目录。
126
+
127
+ ### macOS / Linux
108
128
 
109
129
  ```bash
110
130
  python -m pip install -U memleaf && python -m memleaf install
111
131
  ```
112
132
 
113
- 这一行会完成两件事:先从 PyPI 安装或升级 memleaf,然后立即执行完整 Hermes 接入。用户不需要 `git clone`、`cd` 或运行 `install.sh`。
114
-
115
- `memleaf install` 会自动:
133
+ 两种安装方式最终都会自动:
116
134
 
117
- 1. 初始化默认 Vault:`$HOME/.memleaf`;
118
- 2. 从已安装的 PyPI 包中安装/升级 Hermes MemoryProvider 到 `$HERMES_HOME/plugins/memleaf`;
119
- 3. 发现并保存可用的聊天模型路由;已有有效 memleaf 路由会保留;
135
+ 1. 初始化默认 Vault;
136
+ 2. 安装或升级 Hermes MemoryProvider;
137
+ 3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
120
138
  4. 激活 `memory.provider=memleaf`;
121
139
  5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
122
140
  6. 配置 MCP lazy/idle 生命周期;
@@ -125,25 +143,9 @@ python -m pip install -U memleaf && python -m memleaf install
125
143
 
126
144
  完成后重启 Hermes。
127
145
 
128
- 默认用户数据和 Hermes 接入位置:
146
+ 如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 的 11 工具验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
129
147
 
130
- ```text
131
- $HOME/.memleaf/ # memleaf 数据 Vault
132
- $HERMES_HOME/plugins/memleaf/ # Hermes MemoryProvider
133
- $HERMES_HOME/memleaf.json # Provider 的 Vault、MCP 命令和超时配置
134
- ```
135
-
136
- Python 包和 `memleaf` / `memleaf-mcp` 命令由当前 Python/pip 环境管理,不再要求固定的 `$HOME/memleaf` 源码目录。
137
-
138
- 如需自定义 Vault,也仍是一行:
139
-
140
- ```bash
141
- python -m pip install -U memleaf && python -m memleaf install --vault /path/to/vault
142
- ```
143
-
144
- 如果没有检测到 Hermes 可执行文件、无法取得完整模型路由、Provider 激活失败或 MCP 的 11 工具验证失败,安装命令会明确返回失败,不会把未完成的接入报告为成功。
145
-
146
- 仓库中的 `install.sh` 继续保留给源码开发、离线源码安装和故障排查;**普通 PyPI 用户不需要执行它**。
148
+ 仓库中的 `install.sh` 保留给源码开发、离线源码安装和故障排查;普通 PyPI 用户不需要执行它。
147
149
 
148
150
  Codex 和 Antigravity(反重力)当前暂不支持:安装流程不会检测、安装或修改它们的 MCP、Hook 或模型配置。
149
151
 
@@ -290,7 +292,7 @@ python -m memleaf.mcp_server --vault /path/to/your/vault \
290
292
  - `host`:只使用 Python API 显式传入的宿主回调;
291
293
  - `api`:只使用本地配置的 HTTP API。
292
294
 
293
- `memleaf init` 仅从 Hermes 的可读取配置中寻找完整的聊天模型路由,过滤非聊天模型,并确定性选择轻量模型作为提炼模型。未发现可用路由时复用已有的有效 memleaf 配置;要保留自选路由并跳过扫描,使用 `--no-model-discovery`。不能调用的 OAuth-only 或只有模型名的配置不会被误判为可用。
295
+ `memleaf init` 从 Hermes 的可读取模型配置中寻找完整的聊天模型路由,过滤非聊天模型,并确定性选择轻量模型作为提炼模型。Hermes CLI 输出中的 `***`、`sk-p...7890` 等显示脱敏值会被视为缺失凭证,并继续尝试环境变量或 Hermes `.env`;若仍找不到真实凭证则安装明确失败,不会写入一个看似成功但不可调用的模型路由。未发现可用路由时复用已有的有效 memleaf 配置;要保留自选路由并跳过扫描,使用 `--no-model-discovery`。不能调用的 OAuth-only 或只有模型名的配置不会被误判为可用。
294
296
 
295
297
  API 配置示例:
296
298
 
@@ -385,7 +387,7 @@ python3.11 -m compileall -q src tests examples
385
387
  git diff --check
386
388
  ```
387
389
 
388
- GitHub Actions 已通过 Python 3.11、3.12、3.13 测试、wheel/source distribution 构建和源码包独立测试。构建发行包需要 `build`:
390
+ GitHub Actions 覆盖 Linux 与 Windows 的 Python 3.11、3.12、3.13 测试,并验证 wheel/source distribution 构建、源码包测试和 Windows PowerShell 安装器语法。构建发行包需要 `build`:
389
391
 
390
392
  ```bash
391
393
  python -m pip install build
@@ -397,7 +399,7 @@ python -m build --wheel --sdist
397
399
 
398
400
  以下内容不应被 README 或安装结果误解为已交付能力:
399
401
 
400
- - PyPI 安装可通过一行命令完成 Hermes 自动接入;源码 `install.sh` 仅保留给开发、离线源码安装和故障排查;
402
+ - Windows、macOS 和 Linux 都提供一行安装入口完成 Hermes 自动接入;源码 `install.sh` 仅保留给开发、离线源码安装和故障排查;
401
403
  - 当前仅支持 Hermes;Codex 和 Antigravity(反重力)不检测、不安装、不配置;
402
404
  - Hermes 的检索门控是 Soft Gate,不保证阻止所有未检索回答;
403
405
  - 没有模型路由时只能捕获和检索,不能完成自动提炼、显式记忆或压缩;
@@ -408,11 +410,7 @@ python -m build --wheel --sdist
408
410
 
409
411
  ## License
410
412
 
411
- memleaf 0.1.2 及后续版本采用 [AGPL-3.0-only](LICENSE)。
412
-
413
- AGPL 允许个人和商业使用,但修改、分发或通过网络提供受 AGPL 覆盖的版本时,需要遵守相应的源代码开放义务。希望在闭源商业产品或专有服务中使用 memleaf、且不希望受 AGPL 条款约束的企业,可以申请单独的商业授权,见 [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md)。
414
-
415
- 0.1.0 和 0.1.1 已经按 MIT License 发布,既有 MIT 授权继续有效。历史许可证见 [LICENSES/MIT-0.1.0-0.1.1.txt](LICENSES/MIT-0.1.0-0.1.1.txt)。
413
+ MIT,见 [LICENSE](LICENSE)。
416
414
 
417
415
  **memleaf**
418
416
  *Your memories, in files you own.*
@@ -4,8 +4,8 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.1.2.**
8
- > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. memleaf 0.1.2 is published on PyPI.
7
+ > **Version: 0.1.4.**
8
+ > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. memleaf 0.1.4 is distributed through PyPI.
9
9
  > **The current release supports only Hermes.** Codex and Antigravity are not detected, installed, configured, or scanned for models.
10
10
 
11
11
  ## Project scope
@@ -82,48 +82,51 @@ Additional rules:
82
82
 
83
83
  ## Installation
84
84
 
85
- Requirements: Python 3.11+; full automatic host setup currently supports Hermes only.
85
+ Python 3.11+ is required. Full automatic host setup currently supports Hermes.
86
86
 
87
- For normal users, copy and run this single line:
87
+ ### Windows
88
88
 
89
- ```bash
90
- python -m pip install -U memleaf && python -m memleaf install
91
- ```
92
-
93
- That one line first installs or upgrades memleaf from PyPI and then immediately performs the complete Hermes integration. Users do not need to `git clone`, `cd`, or run `install.sh`.
89
+ If Hermes is already installed on Windows 10/11, run this single line in **PowerShell**:
94
90
 
95
- `memleaf install` automatically:
96
-
97
- 1. Initializes the default Vault at `$HOME/.memleaf`.
98
- 2. Installs or upgrades the packaged Hermes MemoryProvider at `$HERMES_HOME/plugins/memleaf`.
99
- 3. Discovers and saves a callable chat-model route while preserving an existing valid memleaf route.
100
- 4. Activates `memory.provider=memleaf`.
101
- 5. Configures the memleaf MCP entry through Hermes' official CLI.
102
- 6. Configures MCP lazy/idle lifecycle settings.
103
- 7. Verifies that the MCP server exposes all 11 tools.
104
- 8. Records the local Agent integration status.
91
+ ```powershell
92
+ irm https://raw.githubusercontent.com/miffyblueboo/memleaf/main/install.ps1 | iex
93
+ ```
105
94
 
106
- Restart Hermes after installation.
95
+ The Windows installer prefers Hermes' managed Python environment, so Hermes' Python does not need to be on the system PATH. It installs or upgrades memleaf from PyPI and then completes the Hermes integration automatically.
107
96
 
108
- Default user-data and Hermes integration locations:
97
+ The official native Windows Hermes locations are detected by default:
109
98
 
110
99
  ```text
111
- $HOME/.memleaf/ # memleaf data Vault
112
- $HERMES_HOME/plugins/memleaf/ # Hermes MemoryProvider
113
- $HERMES_HOME/memleaf.json # Provider Vault, MCP command, and timeout config
100
+ %USERPROFILE%\.memleaf\ # memleaf data Vault
101
+ %LOCALAPPDATA%\hermes\plugins\memleaf\ # Hermes MemoryProvider
102
+ %LOCALAPPDATA%\hermes\memleaf.json # Provider configuration
103
+ %LOCALAPPDATA%\hermes\bin\hermes.exe / hermes.cmd # Hermes launcher
114
104
  ```
115
105
 
116
- The Python package and the `memleaf` / `memleaf-mcp` commands are managed by the active Python/pip environment. A fixed `$HOME/memleaf` source checkout is no longer required.
106
+ If `HERMES_HOME` is set, memleaf uses it instead.
117
107
 
118
- A custom Vault still fits in one line:
108
+ ### macOS / Linux
119
109
 
120
110
  ```bash
121
- python -m pip install -U memleaf && python -m memleaf install --vault /path/to/vault
111
+ python -m pip install -U memleaf && python -m memleaf install
122
112
  ```
123
113
 
124
- If the Hermes executable is unavailable, no complete model route can be configured, Provider activation fails, or the 11-tool MCP verification fails, the installer returns an explicit failure instead of reporting an incomplete integration as successful.
114
+ Both installation paths automatically:
115
+
116
+ 1. Initialize the default Vault.
117
+ 2. Install or upgrade the Hermes MemoryProvider.
118
+ 3. Discover and save a callable chat-model route. Redacted credentials returned by the Hermes CLI are never treated as real API keys; discovery falls through to environment variables and Hermes `.env`, while preserving an existing valid memleaf route.
119
+ 4. Activate `memory.provider=memleaf`.
120
+ 5. Configure the memleaf MCP entry through Hermes' official CLI.
121
+ 6. Configure MCP lazy/idle lifecycle settings.
122
+ 7. Verify that the MCP server exposes all 11 tools.
123
+ 8. Record the local Agent integration status.
124
+
125
+ Restart Hermes after installation.
125
126
 
126
- The repository `install.sh` remains available for source development, offline source installation, and troubleshooting; **normal PyPI users do not need to run it**.
127
+ If Hermes cannot be detected, no complete model route can be configured, Provider activation fails, or the 11-tool MCP verification fails, the installer returns an explicit failure rather than reporting an incomplete integration as successful.
128
+
129
+ The repository `install.sh` remains for source development, offline source installation, and troubleshooting. Normal PyPI users do not need to run it.
127
130
 
128
131
  Codex and Antigravity are not currently supported. The install flow does not detect, install, or modify their MCP, hook, or model configuration.
129
132
 
@@ -270,7 +273,7 @@ Capture, indexing, directory retrieval, and reading work offline. `process()`, `
270
273
  - `host`: use only a host callback explicitly supplied to the Python API;
271
274
  - `api`: use only the locally configured HTTP API.
272
275
 
273
- `memleaf init` looks for complete chat-model routes only in readable Hermes configuration, filters out non-chat models, and deterministically selects a lightweight model for extraction. An existing valid memleaf route is reused when discovery finds none; use `--no-model-discovery` to preserve a custom route without scanning. OAuth-only routes and model names without a callable route are not treated as usable.
276
+ `memleaf init` looks for complete chat-model routes in readable Hermes model configuration, filters out non-chat models, and deterministically selects a lightweight model for extraction. Display-redacted values such as `***` or `sk-p...7890` are treated as missing credentials; discovery then tries environment variables and Hermes `.env`, and fails explicitly if no real credential is available instead of writing a false-success route. An existing valid memleaf route is reused when discovery finds none; use `--no-model-discovery` to preserve a custom route without scanning. OAuth-only routes and model names without a callable route are not treated as usable.
274
277
 
275
278
  API configuration example:
276
279
 
@@ -365,7 +368,7 @@ python3.11 -m compileall -q src tests examples
365
368
  git diff --check
366
369
  ```
367
370
 
368
- GitHub Actions passed Python 3.11, 3.12, and 3.13 tests, wheel/source-distribution builds, and tests from the source archive. Build packages with:
371
+ GitHub Actions covers Python 3.11, 3.12, and 3.13 on Linux and Windows, plus wheel/source-distribution builds, source-archive tests, and PowerShell installer syntax validation. Build packages with:
369
372
 
370
373
  ```bash
371
374
  python -m pip install build
@@ -377,7 +380,7 @@ python -m build --wheel --sdist
377
380
 
378
381
  The following should not be interpreted as delivered capabilities:
379
382
 
380
- - The PyPI flow can complete Hermes integration from one command line; the source `install.sh` remains only for development, offline source installation, and troubleshooting.
383
+ - Windows, macOS, and Linux each provide a one-line Hermes integration entry point; the source `install.sh` remains only for development, offline source installation, and troubleshooting.
381
384
  - The current release supports only Hermes. Codex and Antigravity are not detected, installed, or configured.
382
385
  - Hermes retrieval gating is a Soft Gate and does not guarantee that every answer performed retrieval.
383
386
  - Without a model route, memleaf can capture and retrieve but cannot perform automatic extraction, explicit model-backed memory, or compaction.
@@ -388,11 +391,7 @@ The following should not be interpreted as delivered capabilities:
388
391
 
389
392
  ## License
390
393
 
391
- memleaf 0.1.2 and later are licensed under [AGPL-3.0-only](LICENSE).
392
-
393
- The AGPL permits both personal and commercial use, subject to its source-code obligations for covered modification, distribution, and network use. Organizations that need to use memleaf in proprietary products or services without the AGPL obligations can obtain a separate commercial license; see [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md).
394
-
395
- Versions 0.1.0 and 0.1.1 were released under the MIT License, and the rights already granted for those releases remain in effect. See [LICENSES/MIT-0.1.0-0.1.1.txt](LICENSES/MIT-0.1.0-0.1.1.txt).
394
+ MIT; see [LICENSE](LICENSE).
396
395
 
397
396
  **memleaf**
398
397
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **当前版本:0.1.2。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.1.2 已发布到 PyPI。
7
+ > **当前版本:0.1.4。**
8
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.1.4 通过 PyPI 分发。
9
9
  > **当前版本仅支持 Hermes。** Codex 与反重力不检测、不安装、不配置,也不扫描其模型配置。
10
10
 
11
11
  ## 项目定位
@@ -82,21 +82,40 @@ memleaf 不把每句话都保存为记忆。处理一轮完整的 user + assista
82
82
 
83
83
  ## 安装
84
84
 
85
- 要求:Python 3.11+;当前版本的完整自动接入仅支持 Hermes。
85
+ 要求 Python 3.11+;当前版本的完整自动接入支持 Hermes。
86
86
 
87
- 普通用户只需要复制执行这一行:
87
+ ### Windows
88
+
89
+ 已经安装 Hermes 的 Windows 10/11 用户,在 **PowerShell** 中只需要执行一行:
90
+
91
+ ```powershell
92
+ irm https://raw.githubusercontent.com/miffyblueboo/memleaf/main/install.ps1 | iex
93
+ ```
94
+
95
+ Windows 安装器会优先使用 Hermes 自带的 Python 环境,因此不要求 Hermes 的 Python 已加入系统 PATH。它会从 PyPI 安装或升级 memleaf,然后自动完成 Hermes 接入。
96
+
97
+ 默认情况下会识别 Hermes 官方 Windows 安装位置:
98
+
99
+ ```text
100
+ %USERPROFILE%\.memleaf\ # memleaf 数据 Vault
101
+ %LOCALAPPDATA%\hermes\plugins\memleaf\ # Hermes MemoryProvider
102
+ %LOCALAPPDATA%\hermes\memleaf.json # Provider 配置
103
+ %LOCALAPPDATA%\hermes\bin\hermes.exe / hermes.cmd # Hermes 命令入口
104
+ ```
105
+
106
+ 如果设置了 `HERMES_HOME`,memleaf 会优先使用该目录。
107
+
108
+ ### macOS / Linux
88
109
 
89
110
  ```bash
90
111
  python -m pip install -U memleaf && python -m memleaf install
91
112
  ```
92
113
 
93
- 这一行会完成两件事:先从 PyPI 安装或升级 memleaf,然后立即执行完整 Hermes 接入。用户不需要 `git clone`、`cd` 或运行 `install.sh`。
94
-
95
- `memleaf install` 会自动:
114
+ 两种安装方式最终都会自动:
96
115
 
97
- 1. 初始化默认 Vault:`$HOME/.memleaf`;
98
- 2. 从已安装的 PyPI 包中安装/升级 Hermes MemoryProvider 到 `$HERMES_HOME/plugins/memleaf`;
99
- 3. 发现并保存可用的聊天模型路由;已有有效 memleaf 路由会保留;
116
+ 1. 初始化默认 Vault;
117
+ 2. 安装或升级 Hermes MemoryProvider;
118
+ 3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
100
119
  4. 激活 `memory.provider=memleaf`;
101
120
  5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
102
121
  6. 配置 MCP lazy/idle 生命周期;
@@ -105,25 +124,9 @@ python -m pip install -U memleaf && python -m memleaf install
105
124
 
106
125
  完成后重启 Hermes。
107
126
 
108
- 默认用户数据和 Hermes 接入位置:
127
+ 如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 的 11 工具验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
109
128
 
110
- ```text
111
- $HOME/.memleaf/ # memleaf 数据 Vault
112
- $HERMES_HOME/plugins/memleaf/ # Hermes MemoryProvider
113
- $HERMES_HOME/memleaf.json # Provider 的 Vault、MCP 命令和超时配置
114
- ```
115
-
116
- Python 包和 `memleaf` / `memleaf-mcp` 命令由当前 Python/pip 环境管理,不再要求固定的 `$HOME/memleaf` 源码目录。
117
-
118
- 如需自定义 Vault,也仍是一行:
119
-
120
- ```bash
121
- python -m pip install -U memleaf && python -m memleaf install --vault /path/to/vault
122
- ```
123
-
124
- 如果没有检测到 Hermes 可执行文件、无法取得完整模型路由、Provider 激活失败或 MCP 的 11 工具验证失败,安装命令会明确返回失败,不会把未完成的接入报告为成功。
125
-
126
- 仓库中的 `install.sh` 继续保留给源码开发、离线源码安装和故障排查;**普通 PyPI 用户不需要执行它**。
129
+ 仓库中的 `install.sh` 保留给源码开发、离线源码安装和故障排查;普通 PyPI 用户不需要执行它。
127
130
 
128
131
  Codex 和 Antigravity(反重力)当前暂不支持:安装流程不会检测、安装或修改它们的 MCP、Hook 或模型配置。
129
132
 
@@ -270,7 +273,7 @@ python -m memleaf.mcp_server --vault /path/to/your/vault \
270
273
  - `host`:只使用 Python API 显式传入的宿主回调;
271
274
  - `api`:只使用本地配置的 HTTP API。
272
275
 
273
- `memleaf init` 仅从 Hermes 的可读取配置中寻找完整的聊天模型路由,过滤非聊天模型,并确定性选择轻量模型作为提炼模型。未发现可用路由时复用已有的有效 memleaf 配置;要保留自选路由并跳过扫描,使用 `--no-model-discovery`。不能调用的 OAuth-only 或只有模型名的配置不会被误判为可用。
276
+ `memleaf init` 从 Hermes 的可读取模型配置中寻找完整的聊天模型路由,过滤非聊天模型,并确定性选择轻量模型作为提炼模型。Hermes CLI 输出中的 `***`、`sk-p...7890` 等显示脱敏值会被视为缺失凭证,并继续尝试环境变量或 Hermes `.env`;若仍找不到真实凭证则安装明确失败,不会写入一个看似成功但不可调用的模型路由。未发现可用路由时复用已有的有效 memleaf 配置;要保留自选路由并跳过扫描,使用 `--no-model-discovery`。不能调用的 OAuth-only 或只有模型名的配置不会被误判为可用。
274
277
 
275
278
  API 配置示例:
276
279
 
@@ -365,7 +368,7 @@ python3.11 -m compileall -q src tests examples
365
368
  git diff --check
366
369
  ```
367
370
 
368
- GitHub Actions 已通过 Python 3.11、3.12、3.13 测试、wheel/source distribution 构建和源码包独立测试。构建发行包需要 `build`:
371
+ GitHub Actions 覆盖 Linux 与 Windows 的 Python 3.11、3.12、3.13 测试,并验证 wheel/source distribution 构建、源码包测试和 Windows PowerShell 安装器语法。构建发行包需要 `build`:
369
372
 
370
373
  ```bash
371
374
  python -m pip install build
@@ -377,7 +380,7 @@ python -m build --wheel --sdist
377
380
 
378
381
  以下内容不应被 README 或安装结果误解为已交付能力:
379
382
 
380
- - PyPI 安装可通过一行命令完成 Hermes 自动接入;源码 `install.sh` 仅保留给开发、离线源码安装和故障排查;
383
+ - Windows、macOS 和 Linux 都提供一行安装入口完成 Hermes 自动接入;源码 `install.sh` 仅保留给开发、离线源码安装和故障排查;
381
384
  - 当前仅支持 Hermes;Codex 和 Antigravity(反重力)不检测、不安装、不配置;
382
385
  - Hermes 的检索门控是 Soft Gate,不保证阻止所有未检索回答;
383
386
  - 没有模型路由时只能捕获和检索,不能完成自动提炼、显式记忆或压缩;
@@ -388,11 +391,7 @@ python -m build --wheel --sdist
388
391
 
389
392
  ## License
390
393
 
391
- memleaf 0.1.2 及后续版本采用 [AGPL-3.0-only](LICENSE)。
392
-
393
- AGPL 允许个人和商业使用,但修改、分发或通过网络提供受 AGPL 覆盖的版本时,需要遵守相应的源代码开放义务。希望在闭源商业产品或专有服务中使用 memleaf、且不希望受 AGPL 条款约束的企业,可以申请单独的商业授权,见 [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md)。
394
-
395
- 0.1.0 和 0.1.1 已经按 MIT License 发布,既有 MIT 授权继续有效。历史许可证见 [LICENSES/MIT-0.1.0-0.1.1.txt](LICENSES/MIT-0.1.0-0.1.1.txt)。
394
+ MIT,见 [LICENSE](LICENSE)。
396
395
 
397
396
  **memleaf**
398
397
  *Your memories, in files you own.*
@@ -0,0 +1,88 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ function Invoke-MemleafPython {
4
+ param([Parameter(Mandatory = $true)][string[]]$PythonArgs)
5
+
6
+ & $script:MemleafPython @script:MemleafPythonPrefix @PythonArgs
7
+ if ($LASTEXITCODE -ne 0) {
8
+ throw "Python command failed with exit code $LASTEXITCODE"
9
+ }
10
+ }
11
+
12
+ $hermesHome = $env:HERMES_HOME
13
+ if ([string]::IsNullOrWhiteSpace($hermesHome)) {
14
+ if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
15
+ throw "LOCALAPPDATA is unavailable and HERMES_HOME is not set."
16
+ }
17
+ $hermesHome = Join-Path $env:LOCALAPPDATA "hermes"
18
+ }
19
+ $hermesHome = [System.IO.Path]::GetFullPath($hermesHome)
20
+
21
+ $managedPython = Join-Path $hermesHome "hermes-agent\venv\Scripts\python.exe"
22
+ $script:MemleafPythonPrefix = @()
23
+
24
+ if (Test-Path -LiteralPath $managedPython -PathType Leaf) {
25
+ $script:MemleafPython = $managedPython
26
+ } else {
27
+ $pythonCommand = Get-Command python -ErrorAction SilentlyContinue
28
+ if ($pythonCommand) {
29
+ $script:MemleafPython = $pythonCommand.Source
30
+ } else {
31
+ $pyCommand = Get-Command py -ErrorAction SilentlyContinue
32
+ if ($pyCommand) {
33
+ $script:MemleafPython = $pyCommand.Source
34
+ $script:MemleafPythonPrefix = @("-3.11")
35
+ } else {
36
+ throw "Python 3.11+ was not found. Reinstall Hermes with its official Windows installer or install Python 3.11+."
37
+ }
38
+ }
39
+ }
40
+
41
+ Invoke-MemleafPython -PythonArgs @(
42
+ "-c",
43
+ "import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)"
44
+ )
45
+
46
+ $oldHermesHome = $env:HERMES_HOME
47
+ try {
48
+ $env:HERMES_HOME = $hermesHome
49
+
50
+ Write-Host "Installing memleaf from PyPI..."
51
+
52
+ $managedUv = Join-Path $hermesHome "bin\uv.exe"
53
+ $uvCommand = $null
54
+ if (Test-Path -LiteralPath $managedUv -PathType Leaf) {
55
+ $uvCommand = $managedUv
56
+ } else {
57
+ $foundUv = Get-Command uv -ErrorAction SilentlyContinue
58
+ if ($foundUv) {
59
+ $uvCommand = $foundUv.Source
60
+ }
61
+ }
62
+
63
+ if ($uvCommand -and $script:MemleafPythonPrefix.Count -eq 0) {
64
+ & $uvCommand pip install --python $script:MemleafPython --upgrade memleaf
65
+ if ($LASTEXITCODE -ne 0) {
66
+ throw "uv could not install memleaf (exit code $LASTEXITCODE)"
67
+ }
68
+ } else {
69
+ & $script:MemleafPython @script:MemleafPythonPrefix -m pip --version *> $null
70
+ if ($LASTEXITCODE -ne 0) {
71
+ Invoke-MemleafPython -PythonArgs @("-m", "ensurepip", "--upgrade")
72
+ }
73
+ Invoke-MemleafPython -PythonArgs @("-m", "pip", "install", "--upgrade", "memleaf")
74
+ }
75
+
76
+ Write-Host "Configuring Hermes to use memleaf..."
77
+ Invoke-MemleafPython -PythonArgs @("-m", "memleaf", "install")
78
+
79
+ Write-Host ""
80
+ Write-Host "memleaf is installed and configured for Hermes."
81
+ Write-Host "Restart Hermes to use memleaf."
82
+ } finally {
83
+ if ($null -eq $oldHermesHome) {
84
+ Remove-Item Env:HERMES_HOME -ErrorAction SilentlyContinue
85
+ } else {
86
+ $env:HERMES_HOME = $oldHermesHome
87
+ }
88
+ }
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.1.2
2
+ version: 0.1.4
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
- license = "AGPL-3.0-only"
12
- license-files = ["LICENSE", "LICENSES/MIT-0.1.0-0.1.1.txt"]
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
13
  authors = [{name = "memleaf contributors"}]
14
14
  keywords = ["ai-agents", "local-first", "markdown", "memory"]
15
15
  classifiers = [
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.4"
4
4
 
5
5
  from .config import DEFAULT_CONFIG, default_config, load_config, save_config
6
6
  from .frontmatter import FrontmatterError, dump_frontmatter, dump_yaml, load_yaml, parse_frontmatter
@@ -151,7 +151,15 @@ def resolve_executable(
151
151
  candidates.extend(Path(candidate).expanduser() for candidate in known_paths)
152
152
  for candidate in candidates:
153
153
  try:
154
- if candidate.is_file() and os.access(candidate, os.X_OK):
154
+ if not candidate.is_file():
155
+ continue
156
+ windows_launcher = os.name == "nt" and candidate.suffix.casefold() in {
157
+ ".exe",
158
+ ".cmd",
159
+ ".bat",
160
+ ".com",
161
+ }
162
+ if windows_launcher or os.access(candidate, os.X_OK):
155
163
  return str(candidate.resolve())
156
164
  except OSError:
157
165
  continue
@@ -511,7 +519,10 @@ def atomic_replace_bytes(path: Path | str, data: bytes, *, mode: int = 0o600) ->
511
519
  prefix=f".{target.name}.", suffix=".tmp", dir=str(target.parent)
512
520
  )
513
521
  temporary = Path(temporary_name)
514
- os.fchmod(descriptor, mode)
522
+ try:
523
+ os.fchmod(descriptor, mode)
524
+ except (AttributeError, OSError):
525
+ pass
515
526
  with os.fdopen(descriptor, "wb") as stream:
516
527
  descriptor = None
517
528
  stream.write(data)