@litfamily/lithermes 1.0.25 → 1.0.26
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.
- package/README.md +10 -10
- package/README_Ko-KR.md +10 -10
- package/assets/lithermes-plugin/diagnostics.py +0 -19
- package/assets/lithermes-plugin/payload-version.json +6 -6
- package/assets/lithermes-plugin/plugin.yaml +1 -1
- package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -2
- package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +2 -2
- package/package.json +1 -1
- package/readme-assets/badge-version.svg +1 -1
- package/src/lib/files.js +25 -0
- package/src/lib/install.js +2 -1
- package/src/lib/skillPayload.js +20 -1
package/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitHermes robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
5
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
4
|
+
<a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-version.svg" alt="1.0.26" /></a>
|
|
5
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-license.svg" alt="MIT license" /></a>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
11
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
9
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-book-open.svg" width="16" alt="" /> Guide</a>
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition film</a>
|
|
11
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
# LitHermes
|
|
15
15
|
|
|
16
16
|
**Keep the work lit.**
|
|
17
17
|
|
|
18
|
-
A plugin for **Hermes Agent**. Put `lit` in front of a request, and Hermes plans the task, does it, checks
|
|
18
|
+
A plugin for **Hermes Agent**. Put `lit` in front of a request, and Hermes plans the task, does it, checks the result and writes down where things stand, so the next session can pick up where this one stopped.
|
|
19
19
|
|
|
20
|
-
**[README and docs on GitHub](https://github.com/wjgoarxiv/lithermes#readme)** · [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
20
|
+
**[README and docs on GitHub](https://github.com/wjgoarxiv/lithermes#readme)** · [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/README_Ko-KR.md)
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ You need Hermes Agent, Node.js 18 or later, and write access to your Hermes home
|
|
|
27
27
|
npx --yes --package @litfamily/lithermes@latest -- lithermes install --yes --no-style
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The command copies the plugin into your Hermes home and switches it on, without stopping to ask; `--no-style` skips the question about reply style. To try it in a separate profile first, point `HERMES_HOME` at an empty directory before you install. [More install options](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.md).
|
|
31
31
|
|
|
32
32
|
## Your first task
|
|
33
33
|
|
|
@@ -37,7 +37,7 @@ Restart the Hermes CLI or gateway, then type:
|
|
|
37
37
|
lit Build a to-do list in one HTML file without external dependencies. Implement add, complete, and delete; record what you checked and what remains.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
The reply opens with a `🔥 **LIT IGNITED · <discipline>** 🔥` line once the lit workflow has started. When the task is done, check the file yourself, then run `/lit-handoff` so the next session in this project can carry on from the note it writes. [Automatic handoff](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.md) is off by default; `/lit-handoff auto on 60` (a whole number from 1 to 99) asks for a handoff before you run `/compact`.
|
|
41
41
|
|
|
42
42
|
## What you get
|
|
43
43
|
|
package/README_Ko-KR.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitHermes 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
5
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
4
|
+
<a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-version.svg" alt="1.0.26" /></a>
|
|
5
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-license.svg" alt="MIT license" /></a>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
11
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
9
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-book-open.svg" width="16" alt="" /> 안내</a>
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition 영상</a>
|
|
11
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
# LitHermes
|
|
15
15
|
|
|
16
16
|
**Keep the work lit.**
|
|
17
17
|
|
|
18
|
-
**Hermes Agent**용 플러그인입니다. 요청 앞에 `lit`을 붙이면 Hermes가 작업을 계획하고, 해내고,
|
|
18
|
+
**Hermes Agent**용 플러그인입니다. 요청 앞에 `lit`을 붙이면 Hermes가 작업을 계획하고, 해내고, 결과를 확인한 뒤 어디까지 왔는지 적어 둡니다. 그래서 다음 세션이 그 기록을 읽고 멈춘 자리에서 이어 갈 수 있습니다.
|
|
19
19
|
|
|
20
|
-
**[GitHub의 README와 문서](https://github.com/wjgoarxiv/lithermes/blob/main/README_Ko-KR.md)** · [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
20
|
+
**[GitHub의 README와 문서](https://github.com/wjgoarxiv/lithermes/blob/main/README_Ko-KR.md)** · [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/README.md)
|
|
21
21
|
|
|
22
22
|
## 설치
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ Hermes Agent와 Node.js 18 이상, 그리고 Hermes 홈(기본 `~/.hermes`)에
|
|
|
27
27
|
npx --yes --package @litfamily/lithermes@latest -- lithermes install --yes --no-style
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
플러그인을
|
|
30
|
+
이 명령은 플러그인을 Hermes 홈에 복사하고 켜 두며, 중간에 승인을 묻지 않습니다. `--no-style`은 답변 문체를 묻는 단계를 건너뜁니다. 따로 떼어 둔 프로필에서 먼저 써 보려면 설치 전에 `HERMES_HOME`을 빈 디렉터리로 지정하세요. 다른 옵션은 [설치 문서](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.ko.md)에 있습니다.
|
|
31
31
|
|
|
32
32
|
## 첫 작업
|
|
33
33
|
|
|
@@ -37,7 +37,7 @@ Hermes CLI 또는 gateway를 다시 시작한 뒤 이렇게 입력하세요.
|
|
|
37
37
|
lit 외부 의존성 없이 HTML 파일 하나로 할 일 목록을 만들어줘. 추가·완료·삭제를 구현하고 확인한 내용과 다음 행동을 남겨줘.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
응답 첫 줄에 `🔥 **LIT IGNITED · <discipline>** 🔥`가 나오면 작업이 시작된 것입니다. 끝나면 파일을 직접 확인하고 `/lit-handoff`를 실행하세요. 같은 프로젝트의 다음 세션이 그 기록을 읽고
|
|
40
|
+
응답 첫 줄에 `🔥 **LIT IGNITED · <discipline>** 🔥`가 나오면 lit 작업이 시작된 것입니다. 끝나면 파일을 직접 확인하고 `/lit-handoff`를 실행하세요. 같은 프로젝트의 다음 세션이 그 기록을 읽고 이어 갑니다. [자동 핸드오프](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.ko.md)는 기본값이 꺼짐이고, `/lit-handoff auto on 60`(1에서 99 사이의 정수)을 켜 두면 `/compact` 전에 인계 기록을 먼저 요청합니다.
|
|
41
41
|
|
|
42
42
|
## 할 수 있는 일
|
|
43
43
|
|
|
@@ -185,22 +185,6 @@ def _host_config_path() -> Path:
|
|
|
185
185
|
return home / "config.yaml"
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
def _retired_hint_data_line() -> str | None:
|
|
189
|
-
# The Jev skill hint is gone. Files it wrote under Hermes home are left for the
|
|
190
|
-
# user to delete; doctor only says where they are.
|
|
191
|
-
state = _host_config_path().parent / "lithermes"
|
|
192
|
-
try:
|
|
193
|
-
found = any(
|
|
194
|
-
entry.name == "jev-trace.jsonl" or entry.name.startswith("jev-last-")
|
|
195
|
-
for entry in state.iterdir()
|
|
196
|
-
)
|
|
197
|
-
except OSError:
|
|
198
|
-
return None
|
|
199
|
-
if not found:
|
|
200
|
-
return None
|
|
201
|
-
return f"old Jev data can be deleted at {state}/jev-*"
|
|
202
|
-
|
|
203
|
-
|
|
204
188
|
def _is_secret_looking_scalar(value: object) -> bool:
|
|
205
189
|
if not isinstance(value, str):
|
|
206
190
|
return False
|
|
@@ -583,9 +567,6 @@ def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
|
|
|
583
567
|
)
|
|
584
568
|
healthy = healthy and knowledge_ok
|
|
585
569
|
lines.append(f"[NOTE] {_provider_metrics.capability_line()}")
|
|
586
|
-
retired_hint = _retired_hint_data_line()
|
|
587
|
-
if retired_hint:
|
|
588
|
-
lines.append(f"[NOTE] {retired_hint}")
|
|
589
570
|
handoff_tag, handoff_text = _auto_handoff.doctor_line()
|
|
590
571
|
lines.append(f"[{handoff_tag}] {handoff_text}")
|
|
591
572
|
if version != "unknown":
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-10-
|
|
2
|
+
"syncedAt": "2026-10-18T12:47:41.644Z",
|
|
3
3
|
"source": "bundled-payload",
|
|
4
|
-
"sourceHash": "
|
|
4
|
+
"sourceHash": "509d6b1fd0205b3d3e0817896975e01a9dfa709994485c645f67ef5f49d44ded",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "NOTICE.md",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "diagnostics.py",
|
|
92
|
-
"sha256": "
|
|
92
|
+
"sha256": "13fdef893d10cecdffdbb61daf64629e5ccbf4777964e75930ca34017472828e"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "handoff.py",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
"path": "plugin.yaml",
|
|
180
|
-
"sha256": "
|
|
180
|
+
"sha256": "51608ca52a559fb7ab2ddfeea89f5c5a3e976ebb347b22cff8cf5906473fbb35"
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"path": "provider_metrics.py",
|
|
@@ -1281,7 +1281,7 @@
|
|
|
1281
1281
|
},
|
|
1282
1282
|
{
|
|
1283
1283
|
"path": "skills/frontend-ui-ux/references/complete-contract.md",
|
|
1284
|
-
"sha256": "
|
|
1284
|
+
"sha256": "6bf0d7d03ede7521da24a267a7f71229fe150a35850b97c797a839607cdfb9fe"
|
|
1285
1285
|
},
|
|
1286
1286
|
{
|
|
1287
1287
|
"path": "skills/frontend-ui-ux/references/composition.md",
|
|
@@ -4401,7 +4401,7 @@
|
|
|
4401
4401
|
},
|
|
4402
4402
|
{
|
|
4403
4403
|
"path": "skills/visual-qa/references/complete-contract.md",
|
|
4404
|
-
"sha256": "
|
|
4404
|
+
"sha256": "f7a55c0000bb48c576a0998d661d07183ad2b197bace99cf5a4435cc6ebfbac7"
|
|
4405
4405
|
},
|
|
4406
4406
|
{
|
|
4407
4407
|
"path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
|
|
@@ -445,7 +445,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
|
|
|
445
445
|
|
|
446
446
|
```text
|
|
447
447
|
$ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
|
|
448
|
-
Installed LitHermes 1.0.
|
|
448
|
+
Installed LitHermes 1.0.26
|
|
449
449
|
plugin: $ISOLATED/plugins/lithermes
|
|
450
450
|
model config: updated
|
|
451
451
|
|
|
@@ -462,7 +462,7 @@ installed payload: PASS
|
|
|
462
462
|
enabled config: PASS
|
|
463
463
|
|
|
464
464
|
$ hermes lithermes doctor
|
|
465
|
-
[OK] plugin.yaml readable (version 1.0.
|
|
465
|
+
[OK] plugin.yaml readable (version 1.0.26)
|
|
466
466
|
[OK] skills bundled: 36
|
|
467
467
|
[OK] litgoal durable runtime importable
|
|
468
468
|
```
|
|
@@ -485,7 +485,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
|
|
|
485
485
|
|
|
486
486
|
```text
|
|
487
487
|
$ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
|
|
488
|
-
Installed LitHermes 1.0.
|
|
488
|
+
Installed LitHermes 1.0.26
|
|
489
489
|
plugin: $ISOLATED/plugins/lithermes
|
|
490
490
|
model config: updated
|
|
491
491
|
|
|
@@ -502,7 +502,7 @@ installed payload: PASS
|
|
|
502
502
|
enabled config: PASS
|
|
503
503
|
|
|
504
504
|
$ hermes lithermes doctor
|
|
505
|
-
[OK] plugin.yaml readable (version 1.0.
|
|
505
|
+
[OK] plugin.yaml readable (version 1.0.26)
|
|
506
506
|
[OK] skills bundled: 36
|
|
507
507
|
[OK] litgoal durable runtime importable
|
|
508
508
|
```
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.26"><title>release: 1.0.26</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.26</text></g></svg>
|
package/src/lib/files.js
CHANGED
|
@@ -106,6 +106,29 @@ function writeFileAtomic(filePath, data, encoding) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// The payload-relative source a host-written bytecode path belongs to, or null.
|
|
110
|
+
// Only CPython's own layout counts: <dir>/__pycache__/<module>.<tag>[.opt-N].pyc.
|
|
111
|
+
function hostBytecodeSource(relative) {
|
|
112
|
+
const segments = relative.split("/");
|
|
113
|
+
if (segments.length < 2 || segments[segments.length - 2] !== "__pycache__") return null;
|
|
114
|
+
const match = /^([^.]+)\.[a-zA-Z][a-zA-Z0-9_-]*(?:\.opt-[0-9]+)?\.pyc$/.exec(segments[segments.length - 1]);
|
|
115
|
+
return match ? [...segments.slice(0, -2), `${match[1]}.py`].join("/") : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Removes the host bytecode for one removed source module, then its cache
|
|
119
|
+
// folder once empty. Anything else in that folder stays where it is.
|
|
120
|
+
function removeHostBytecode(sourceFile) {
|
|
121
|
+
const cacheDir = path.join(path.dirname(sourceFile), "__pycache__");
|
|
122
|
+
if (!fs.lstatSync(cacheDir, { throwIfNoEntry: false })?.isDirectory()) return;
|
|
123
|
+
const stem = path.basename(sourceFile, ".py");
|
|
124
|
+
for (const entry of fs.readdirSync(cacheDir, { withFileTypes: true })) {
|
|
125
|
+
if (entry.isFile() && hostBytecodeSource(`__pycache__/${entry.name}`) === `${stem}.py`) {
|
|
126
|
+
fs.unlinkSync(path.join(cacheDir, entry.name));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (fs.readdirSync(cacheDir).length === 0) fs.rmdirSync(cacheDir);
|
|
130
|
+
}
|
|
131
|
+
|
|
109
132
|
function removeEmptyDirs(dir, stopAt) {
|
|
110
133
|
let current = dir;
|
|
111
134
|
while (current && current !== stopAt && current.startsWith(stopAt) && fs.existsSync(current)) {
|
|
@@ -118,9 +141,11 @@ function removeEmptyDirs(dir, stopAt) {
|
|
|
118
141
|
module.exports = {
|
|
119
142
|
cleanPythonCache,
|
|
120
143
|
copyTree,
|
|
144
|
+
hostBytecodeSource,
|
|
121
145
|
inspectPythonCache,
|
|
122
146
|
listFiles,
|
|
123
147
|
removeEmptyDirs,
|
|
148
|
+
removeHostBytecode,
|
|
124
149
|
sha256,
|
|
125
150
|
writeFileAtomic,
|
|
126
151
|
};
|
package/src/lib/install.js
CHANGED
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
revalidateModelConfigPlan,
|
|
16
16
|
writeConfig,
|
|
17
17
|
} = require("./config");
|
|
18
|
-
const { copyTree, removeEmptyDirs, sha256, writeFileAtomic } = require("./files");
|
|
18
|
+
const { copyTree, removeEmptyDirs, removeHostBytecode, sha256, writeFileAtomic } = require("./files");
|
|
19
19
|
const { assertStateParent, assertPluginParent, defaultHermesHome, defaultHermesRepo, detectHermesRuntimeRepo, detectHermesVersion, ensureHermesHome, inspectHermesHostCapabilities, LitHermesError } = require("./hermesDiscovery");
|
|
20
20
|
const { NOT_PROBED_HOST_VERSION } = require("./modelConfigBoundary");
|
|
21
21
|
const { patchInstalledHermes, patchManifestPath, rollbackPatches } = require("./patch");
|
|
@@ -802,6 +802,7 @@ function uninstallLitHermes(flags = {}) {
|
|
|
802
802
|
const file = path.join(dest, entry.path);
|
|
803
803
|
if (isInstalledRegularFile(dest, entry.path) && sha256(file) === entry.sha256) {
|
|
804
804
|
fs.unlinkSync(file);
|
|
805
|
+
if (entry.path.endsWith(".py")) removeHostBytecode(file);
|
|
805
806
|
removeEmptyDirs(path.dirname(file), dest);
|
|
806
807
|
}
|
|
807
808
|
}
|
package/src/lib/skillPayload.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const crypto = require("node:crypto");
|
|
2
2
|
const fs = require("node:fs");
|
|
3
3
|
const path = require("node:path");
|
|
4
|
-
const { sha256 } = require("./files");
|
|
4
|
+
const { hostBytecodeSource, sha256 } = require("./files");
|
|
5
5
|
const {
|
|
6
6
|
captureRegularFile,
|
|
7
7
|
parseJsonRejectingDuplicateKeys,
|
|
@@ -229,12 +229,31 @@ function inspectSkillPayload(pluginPath, manifest, verifySourceHash = false, opt
|
|
|
229
229
|
allowedDirectories.add(`${segments.slice(0, index).join("/")}/`);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
+
// Hermes imports the plugin with bytecode writing on, so CPython caches for
|
|
233
|
+
// shipped modules are expected host output; any other cache content is not.
|
|
234
|
+
const hostCacheDirectories = new Set();
|
|
235
|
+
for (const entryPath of inspected.entries.keys()) {
|
|
236
|
+
if (!entryPath.endsWith(".py")) continue;
|
|
237
|
+
const directory = path.posix.dirname(entryPath);
|
|
238
|
+
hostCacheDirectories.add(directory === "." ? "__pycache__/" : `${directory}/__pycache__/`);
|
|
239
|
+
}
|
|
240
|
+
const isHostBytecode = (entryPath) => {
|
|
241
|
+
const source = hostBytecodeSource(entryPath);
|
|
242
|
+
if (!source || !inspected.entries.has(source)) return false;
|
|
243
|
+
try {
|
|
244
|
+
return fs.lstatSync(path.join(pluginPath, ...entryPath.split("/"))).isFile();
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
232
249
|
const traversal = listPayloadEntries(pluginPath, options.readDirectory);
|
|
233
250
|
for (const entryPath of traversal.entries) {
|
|
234
251
|
if (
|
|
235
252
|
inspected.entries.has(entryPath)
|
|
236
253
|
|| allowedUnlisted.has(entryPath)
|
|
237
254
|
|| allowedDirectories.has(entryPath)
|
|
255
|
+
|| hostCacheDirectories.has(entryPath)
|
|
256
|
+
|| isHostBytecode(entryPath)
|
|
238
257
|
) continue;
|
|
239
258
|
failures.push({ ...entryLabel(entryPath), reason: "unexpected" });
|
|
240
259
|
}
|