claude-token-saver 3.38.0 โ 3.40.0
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.ko.md +19 -4
- package/README.md +16 -1
- package/bin/cli.js +11 -1
- package/package.json +6 -5
- package/src/commands/feedback.js +132 -0
- package/src/commands/install.js +29 -1
- package/src/commands/korean.js +1 -0
- package/src/installer.js +10 -1
- package/src/korean-lint.cjs +51 -1
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claude-token-saver)
|
|
4
4
|
|
|
5
|
-
๐ **[
|
|
5
|
+
๐ **[sprag.io](https://sprag.io)** ยท ํ๋ก์ ํธ ์ฌ์ดํธ (claude-token-saver๋ **Sprag**๋ก ์ด๋ฆ์ ๋ฐ๊พธ๋ ์ค์
๋๋ค)
|
|
6
6
|
|
|
7
7
|
# claude-token-saver
|
|
8
8
|
|
|
@@ -395,7 +395,7 @@ Claude Code์ output style๋ก๋ ๊ฐ์ ์ผ์ ํ ์ ์์ง๋ง, output style
|
|
|
395
395
|
|
|
396
396
|
**์ด์ ** (์ง์นจ ๊บผ์ง)
|
|
397
397
|
|
|
398
|
-
> ๋ ๋ ์คํจ ์์ธ ์ขํ๊ธฐ 3๋จ๊ณ: ๋จผ์ `hn:still --check`๋ก ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง(FRESH/STALE)
|
|
398
|
+
> ๋ ๋ ์คํจ ์์ธ ์ขํ๊ธฐ 3๋จ๊ณ: ๋จผ์ `hn:still --check`๋ก ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง(FRESH/STALE) ํ์ธํฉ๋๋ค. STALE์ด๋ฉด ์ฌ์์ฑ ์ ํด์ ์ ์์ฐ์ผ๋ก ๋ ๋ ์๋ํ ๊ฒ ์์ธ. ๋ค์ caption-blocks๊ฐ ์ต์ ๋๋ ์ด์
๊ณผ ๋จ์ด์ด ์ผ์นํ๋์ง ์ง์ ๋์กฐํฉ๋๋ค. TTS ์ฌ์์ฑ ํ blocks ์ฌ์์ฑ ๋น ์ง๋ฉด ์๋งยท์์ฑ ์ด๊ธ๋ ๋ ๋ ๊นจ์ง.
|
|
399
399
|
|
|
400
400
|
**์ดํ** (์ง์นจ ์ผ์ง)
|
|
401
401
|
|
|
@@ -413,7 +413,7 @@ Claude Code์ output style๋ก๋ ๊ฐ์ ์ผ์ ํ ์ ์์ง๋ง, output style
|
|
|
413
413
|
|
|
414
414
|
์ธ ์ธต์ด ๋ํด์ง๋๋ค.
|
|
415
415
|
|
|
416
|
-
- **๋ฒ์ญํฌ**: ์ด์ค ํผ๋, ์ผ๋ณธ์ด์์ ์จ ๊ตณ์ ํํ,
|
|
416
|
+
- **๋ฒ์ญํฌ**: ์ด์ค ํผ๋, ์ผ๋ณธ์ด์์ ์จ ๊ตณ์ ํํ, `~์ ์์ด์`, ์์ด have๋ฅผ ์ง์ญํ '๊ฐ์ง๋ค' ๋จ์ฉ. ๊ธฐ๊ณ๊ฐ ํ์ ํ ์ ์๋ ๊ฒ์ ์ฐ๊ธฐ ์์ ๊ฒ์ฌ(์๋)์๋ ํจ๊ป ๋ค์ด๊ฐ๋๋ค.
|
|
417
417
|
- **์ํฌ ํจํด**: ์๋์ผ๋ก ๋ถ๋ ์์์ด("๋ค์ํ", "ํต์ฌ์ ์ธ"), ํ์งํ ๋ฌธ์ฅ, ์์ฌ์ ์ง๋ฌธ ๋ค ์ฆ๋ต, ๊ทผ๊ฑฐ ์๋ ๊ธ์ ๋ง๋ฌด๋ฆฌ.
|
|
418
418
|
- **์์ง์ฑ**: ๋ฌธ์ฅ์ด ์ด์ด์ง๋ ๋ฐฉ์์ด๋ผ ์ ๊ท์์ผ๋ก๋ ๊ฒ์ฌํ์ง ๋ชปํฉ๋๋ค. ์ด ์ ์ ๋ฐฉํฅ์ ์ ํ ์ฐ๊ตฌ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค. ์ ์์ดยท์ง์์ด ๊ฐ์ ํ์ธต ์ฐ๊ฒฐ ์ฅ์น๋ ๊ธ์ ํ์ง๊ณผ ์๊ด์ด ์๊ฑฐ๋ ์คํ๋ ค ๋ถ์ ์๊ด์ด๊ณ , ์ ๋ฌธ์ฅ์ด ๋ด๋์ ์ ๋ณด๋ฅผ ๋ค์ ๋ฌธ์ฅ์ด ๋ฐ์ ํ์ด ์ฃผ๋ ์์ ํ ์ฐ๊ฒฐ๋ง ์ ์ ์๊ด์ ๋ณด์์ต๋๋ค. ๊ทธ๋์ ์ง์นจ์ ์ฐ๊ฒฐ์ด ์ด์ํ ๋ ์ ์์ด๋ฅผ ๋ํ์ง ๋ง๊ณ ์ ๋ณด์ ๋ฐฐ์ด(์๋ ๊ฒ ๋จผ์ , ์ ๊ฒ ๋์ค)์ ๊ณ ์น๋ผ๊ณ ๋งํฉ๋๋ค.
|
|
419
419
|
|
|
@@ -503,7 +503,7 @@ claude-token-saver doc2md install-converter # ์ค์น๋ฅผ ๋ฏธ๋ฆฌ ๋๋ด ๋๊ณ
|
|
|
503
503
|
๋ช ๊ฐ์ง๋ ์๋์ ์ผ๋ก ํ์ง ์์ต๋๋ค.
|
|
504
504
|
|
|
505
505
|
- **์ด๋ฏธ์ง๋ ๋ณํํ์ง ์์ต๋๋ค.** markitdown ์ด ๋น ๊ฒฐ๊ณผ๋ฅผ ๋๋ ค์ฃผ๊ณ , OCR ์ ์ค์ธก์์ ๋ฆฌ์์ค ์ด๋ฆ์ ํ๋ฆฌ๊ฒ ์ฝ์์ต๋๋ค(`c5.xlarge` ๋ฅผ `c.xlarge` ๋ก). ์ด๋ฆ ์์ฒด๊ฐ ๋ด์ฉ์ธ ๋ฌธ์์์๋ ํ
์คํธ๊ฐ ์๋๋๋ง ๋ชปํฉ๋๋ค. ๋ชจ๋ธ์ด ์ด๋ฏธ์ง๋ ์ง์ ์ฝ์ต๋๋ค.
|
|
506
|
-
- **๋ณํ๊ธฐ๊ฐ
|
|
506
|
+
- **๋ณํ๊ธฐ๊ฐ ์์ด๋ ์๋ฆฌ์ง ์๊ณ ์ง๋๊ฐ์ง ์์ต๋๋ค.** ์ค์น ๋ช
๋ น์ ํ ๋ฒ ์๋ดํ ๋ค ์๋ณธ `Read` ๋ฅผ ๊ทธ๋๋ก ํต๊ณผ์ํต๋๋ค. ๋งค๋ฒ ์๋ฆฌ๋ฉด ๊ทธ๊ฒ๋๋ก ๋ฐฉํด๊ฐ ๋๊ณ , ์๋ฌด ๋ง๋ ํ์ง ์์ผ๋ฉด ๊ณ ์ฅ์ ์จ๊ธฐ๊ฒ ๋ฉ๋๋ค. `doc2md` ๋ฅผ ์ธ์ ์์ด ์คํํ๋ฉด ๋ณํ๊ธฐ์ ํ
๋ฑ๋ก ์ํ๋ฅผ ํ ๋ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
|
|
507
507
|
- **๋ณํ๋ณธ์ ํ๋ก์ ํธ ์์ ๋จ๊ธฐ์ง ์์ต๋๋ค.** ๋๊ตฌ์ ์ํ ๋๋ ํฐ๋ฆฌ ์๋ ๊ถํ `0700` ์ผ๋ก ์ ์ฅํ๋ฏ๋ก `.gitignore` ์ ๋ฌด์์ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค. ํ์ผ๋ช
์ด ๊ธ์ฌยท๊ณ์ฝยท๊ฐ์ธ์ ๋ณด ๊ฐ์ ํจํด์ ๊ฑธ๋ฆฌ๋ฉด ์์ ๋ณํํ์ง ์์ต๋๋ค.
|
|
508
508
|
- **์์ถ ํญํ์ ๋ง์ต๋๋ค.** pptxยทxlsxยทdocx ๋ zip ์ปจํ
์ด๋์
๋๋ค. ์ ์ธ๋ ํฌ๊ธฐ๋ฅผ ๋จผ์ ๊ฑธ๋ฌ ๋ด๊ณ , ์ ์ธ์ ์กฐ์๋ ์ ์์ผ๋ฏ๋ก ์ค์ ํด์ ๋ฐ์ดํธ๋ ์ํ๊ณผ ๋์กฐํฉ๋๋ค.
|
|
509
509
|
- **์์
์ ํ ์๋ก ์๋ฆ
๋๋ค.** ๋ณํ ์๊ฐ์ ํ์ผ ํฌ๊ธฐ๊ฐ ์๋๋ผ ํ ์๋ฅผ ๋ฐ๋ฆ
๋๋ค(์ค์ธก: PDF 6.3MB 0.9์ด, ์์
5.8MB 47.75์ด). 5๋ง ํ์ ๋์ผ๋ฉด ์๋ถ๋ถ๋ง ๋ณํํ๊ณ , **์๋๋ค๋ ์ฌ์ค๊ณผ ์ ์ฒด ํ ์๋ฅผ ์๋ด์ ํจ๊ป ์ ์ต๋๋ค.**
|
|
@@ -602,11 +602,26 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
602
602
|
|
|
603
603
|
์ ์ฒด ๋ด์ญ์ [CHANGELOG.md](./CHANGELOG.md)๋ก ์ฎ๊ฒผ์ต๋๋ค. ์ต๊ทผ ๋ณ๊ฒฝ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
|
|
604
604
|
|
|
605
|
+
- **v3.39.0**: `feedback` ์๋ธ์ปค๋งจ๋๊ฐ ํฐ๋ฏธ๋์ด๋ Claude ์ธ์
์์ ๋ฒ๊ทธ ์ ๋ณดยท๊ธฐ๋ฅ ์ ์์ ๋ฐ๋ก ์ ์ถํฉ๋๋ค. gh CLI๊ฐ ์์ผ๋ฉด ์ด์ ์ง์ ๋ฑ๋ก, ์์ผ๋ฉด ๋ก๊ทธ์ธ ์๋ ์ต๋ช
์ ์ถ(๋ฆด๋ ์ด๊ฐ ์ด์๋ก ์๋ ๋ฑ๋ก), ์คํ๋ผ์ธ์ด๋ฉด ๋ก์ปฌ ์ ์ฅ. `install`์ ๊ธฐ์กด statusline์ด ์์ผ๋ฉด ์กฐ์ฉํ ๊ฑด๋๋ฐ์ง ์๊ณ ๊ต์ฒด ์ฌ๋ถ๋ฅผ ๋ฌป์ต๋๋ค.
|
|
605
606
|
- **v3.38.0**: `cohesion on`์ด ์์ง์ฑ ๋ค์ฏ ์์น์ ์์ด ๋ธ๋ก์ผ๋ก ์ฃผ์
ํฉ๋๋ค. ์ตํธ์ธ์ด๊ณ , korean ์ง์นจ์ด ์ผ์ ธ ์์ผ๋ฉด ์ค๋ณต ์ฃผ์
์ ๋ง์ต๋๋ค.
|
|
606
607
|
- **v3.37.0**: ํ๊ตญ์ด ์ง์นจ์ ๋ณด๊ฐ ์ง์นจ(supplement)์ด ๋ถ์ต๋๋ค. ๋ฒ์ญํฌยท์ํฌ ํจํดยท์์ง์ฑ(๋ฌธ์ฅ ์ด์ด์ง) ์กฐํญ์ด๋ฉฐ, ์ฐ๊ธฐ ์์ ๊ฒ์ฌ์๋ ๋ฒ์ญํฌ 5์ข
์ด ์ถ๊ฐ๋์ต๋๋ค. ์คํ์ผ 255๊ฐ ์ค์ธก์์ ์คํ 1๊ฑด์ผ๋ก ๊ฒ์ฆํ์ต๋๋ค.
|
|
607
608
|
- **v3.35.0**: ์ด๋ฒ ๋ฌ 1์ผ 00์ ์ดํ ์ง์ถ ์ถ์ ์น๋ฅผ `๐ต Sep $42` ์ธ๊ทธ๋จผํธ๋ก ์์ ํ์ํฉ๋๋ค. LiteLLM ๊ฒ์ดํธ์จ์ด ์ฌ์ฉ์๋ ํค์ max_budget/spend ๋ฅผ `๐ budget` ๊ฒ์ด์ง๋ก ๋ด
๋๋ค (5h/7d cap ์ด ์๋ BedrockยทLiteLLM ํ๊ฒฝ ๋์).
|
|
608
609
|
- **v3.34.0**: seed ํ๋ฆฌ์
์ ์, ์ค์น ์ ์ถ๋ ฅ ์ธ์ด ์ ํ, ์ปจํ
์คํธ ๊ฒฝ๊ณ 500k ์ํฅ. ์์ธ๋ CHANGELOG ์ฐธ๊ณ .
|
|
609
610
|
|
|
611
|
+
## ํผ๋๋ฐฑ
|
|
612
|
+
|
|
613
|
+
๋ฒ๊ทธ ์ ๋ณด์ ๊ธฐ๋ฅ ์ ์์ GitHub ์ด์๋ก ๋ฐ์ต๋๋ค: https://github.com/rootstudioyaml/sprag/issues
|
|
614
|
+
|
|
615
|
+
๋ธ๋ผ์ฐ์ ๋ GitHub ๋ก๊ทธ์ธ์ด ์ด๋ ค์ด ํ๊ฒฝ(์ฌ๋ด๋ง, ์ธ์
๋์ค)์ด๋ผ๋ฉด ํฐ๋ฏธ๋์์ ๋ฐ๋ก ์ ์ถํ ์ ์๊ณ , Claude์๊ฒ ๋์ ์ ์ถํด ๋ฌ๋ผ๊ณ ์์ฒญํด๋ ๋ฉ๋๋ค.
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
claude-token-saver feedback "Bedrock์์ 5m TTL ์นฉ์ด ์ฌ๋ผ์ง์ง ์์์"
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
`gh` CLI๊ฐ ์ธ์ฆ๋์ด ์์ผ๋ฉด GitHub ์ด์๋ก ๋ฐ๋ก ๋ฑ๋กํ๊ณ , ์์ผ๋ฉด ๋ก๊ทธ์ธ์ด ํ์ ์๋ ์ต๋ช
๊ฒฝ๋ก๋ก ์ ์ถํฉ๋๋ค(github.com์ด ๋งํ ์ฌ๋ด๋ง์์๋ ๋์ํฉ๋๋ค). `gh` ๊ฒฝ๋ก๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด `--anonymous`๋ฅผ ๋ถ์
๋๋ค. ๋๊ตฌ ๋ฒ์ ๊ณผ OS ์ ๋ณด๋ ์๋์ผ๋ก ์ฒจ๋ถ๋ฉ๋๋ค.
|
|
622
|
+
|
|
623
|
+
๋ฒ๊ทธ๋ฅผ ์ ๋ณดํ์ค ๋๋ ๋๊ตฌ ๋ฒ์ (`claude-token-saver --version`)๊ณผ OS๋ฅผ ํจ๊ป ์ ์ด ์ฃผ์๊ณ , statusline์ด๋ ๊ฒฝ๊ณ ๊ด๋ จ ๋ฌธ์ ๋ผ๋ฉด statusline ์ถ๋ ฅ์ด๋ `claude-token-saver last` ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ถ์ฌ ์ฃผ์๋ฉด ์์ธ์ ๋นจ๋ฆฌ ์ฐพ์ ์ ์์ต๋๋ค.
|
|
624
|
+
|
|
610
625
|
## ๋ผ์ด์ ์ค
|
|
611
626
|
|
|
612
627
|
MIT
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claude-token-saver)
|
|
4
4
|
|
|
5
|
-
๐ **[
|
|
5
|
+
๐ **[sprag.io](https://sprag.io)** ยท project site (claude-token-saver is being renamed to **Sprag**)
|
|
6
6
|
|
|
7
7
|
# claude-token-saver
|
|
8
8
|
|
|
@@ -723,11 +723,26 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
723
723
|
|
|
724
724
|
The full history moved to [CHANGELOG.md](./CHANGELOG.md) (Korean; version headings and command names are language-neutral). Recent changes:
|
|
725
725
|
|
|
726
|
+
- **v3.39.0**: `feedback` subcommand โ file bug reports and feature requests straight from the terminal or a Claude session, via the gh CLI, an anonymous no-login form (auto-filed as a GitHub issue by an Apps Script relay), or a local fallback. `install` now asks before replacing an existing statusline instead of silently skipping.
|
|
726
727
|
- **v3.38.0**: `cohesion on` โ the language-neutral cohesion rules from the Korean supplement become a standalone English injection (given-before-new, one referent per pronoun, subject consistency, bridging, merging choppy sentences). Opt-in, ~0.5k tokens per session, suppressed while `korean on` already carries them.
|
|
727
728
|
- **v3.37.0**: Korean guidance grows a conservative supplement (translationese, AI-writing tics, a research-backed cohesion section whose principles apply to English prose too) and the write-time lint gains 5 translationese patterns, validated at 1 false positive across 255 real files.
|
|
728
729
|
- **v3.35.0**: A `๐ต Sep $42` segment now shows estimated spend since 00:00 on the 1st of the current month, always on โ including gateway setups with no 5h/7d caps. LiteLLM gateway users get a `๐ budget โฐโฑ 34% $34/$100` gauge built from the key's budget (`GET /key/info` + `GET /user/info`, team-membership budget first, then key, then internal user โ verified against a Dockerized LiteLLM).
|
|
729
730
|
- **v3.34.0**: seed presets offered one at a time, output-language choice at install, context warning raised to 500k.
|
|
730
731
|
|
|
732
|
+
## Feedback
|
|
733
|
+
|
|
734
|
+
Found a bug, or want a feature? Open an issue: https://github.com/rootstudioyaml/sprag/issues
|
|
735
|
+
|
|
736
|
+
No browser or GitHub login handy (corporate network, mid-session)? Submit straight from the terminal โ or ask Claude to do it for you:
|
|
737
|
+
|
|
738
|
+
```bash
|
|
739
|
+
claude-token-saver feedback "the 5m TTL chip never clears on Bedrock"
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
It files a GitHub issue via the `gh` CLI when one is authenticated; otherwise it submits anonymously (no login, works where github.com is blocked). Pass `--anonymous` to skip the `gh` path. Version and OS metadata are attached automatically.
|
|
743
|
+
|
|
744
|
+
When reporting a bug, please include the tool version (`claude-token-saver --version`), your OS, and โ if it is a statusline or warning issue โ the statusline output or the `claude-token-saver last` result.
|
|
745
|
+
|
|
731
746
|
## License
|
|
732
747
|
|
|
733
748
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -87,7 +87,7 @@ function readUpdateChip() {
|
|
|
87
87
|
const KNOWN_SUBCOMMANDS = new Set([
|
|
88
88
|
'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean', 'cohesion',
|
|
89
89
|
'doc2md', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
|
|
90
|
-
'seed', 'litellm-budget',
|
|
90
|
+
'seed', 'litellm-budget', 'feedback',
|
|
91
91
|
]);
|
|
92
92
|
|
|
93
93
|
const USAGE = `claude-token-saver โ Claude Code token usage, cache health, and model routing
|
|
@@ -107,6 +107,7 @@ Usage:
|
|
|
107
107
|
claude-token-saver last most recent warning + how to handle it
|
|
108
108
|
claude-token-saver history recent warning transitions
|
|
109
109
|
claude-token-saver handoff write a session handoff file
|
|
110
|
+
claude-token-saver feedback "<msg>" file a bug report / feature request (no browser needed)
|
|
110
111
|
claude-token-saver upgrade install the latest release
|
|
111
112
|
claude-token-saver --install-hook install cache-monitor PostToolUse hook
|
|
112
113
|
claude-token-saver --uninstall-hook remove that hook
|
|
@@ -114,6 +115,7 @@ Usage:
|
|
|
114
115
|
--verbose / --no-color / --icon / --no-timer / --single-line
|
|
115
116
|
|
|
116
117
|
Run any subcommand with --help for its own options where available.
|
|
118
|
+
Bug reports & feature requests: https://github.com/rootstudioyaml/claude-token-saver/issues
|
|
117
119
|
`;
|
|
118
120
|
|
|
119
121
|
async function main() {
|
|
@@ -161,6 +163,14 @@ async function main() {
|
|
|
161
163
|
// hits.
|
|
162
164
|
// claude-token-saver handoff # write to cwd
|
|
163
165
|
// claude-token-saver handoff --cwd PATH # custom directory
|
|
166
|
+
// Subcommand: feedback โ file a bug report / feature request without a
|
|
167
|
+
// browser. Tries gh CLI, then an anonymous form POST, then a local save
|
|
168
|
+
// with a prefilled GitHub issue URL. See src/commands/feedback.js.
|
|
169
|
+
if (args[0] === 'feedback') {
|
|
170
|
+
const { userDataDir } = await import('../src/paths.js');
|
|
171
|
+
return (await import('../src/commands/feedback.js')).run({ args, getArg, version: PKG_VERSION, dataDir: userDataDir() });
|
|
172
|
+
}
|
|
173
|
+
|
|
164
174
|
if (args[0] === 'handoff') {
|
|
165
175
|
return (await import('../src/commands/handoff.js')).run({ getArg });
|
|
166
176
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet
|
|
3
|
+
"version": "3.40.0",
|
|
4
|
+
"description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet \u2014 post-hoc session analysis, no realtime router, no extra LLM calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
+
"sprag": "bin/cli.js",
|
|
7
8
|
"claude-token-saver": "bin/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
@@ -48,11 +49,11 @@
|
|
|
48
49
|
"license": "MIT",
|
|
49
50
|
"repository": {
|
|
50
51
|
"type": "git",
|
|
51
|
-
"url": "git+https://github.com/rootstudioyaml/
|
|
52
|
+
"url": "git+https://github.com/rootstudioyaml/sprag.git"
|
|
52
53
|
},
|
|
53
|
-
"homepage": "https://
|
|
54
|
+
"homepage": "https://sprag.io",
|
|
54
55
|
"bugs": {
|
|
55
|
-
"url": "https://github.com/rootstudioyaml/
|
|
56
|
+
"url": "https://github.com/rootstudioyaml/sprag/issues"
|
|
56
57
|
},
|
|
57
58
|
"author": "DeepPulse (https://www.youtube.com/@DeepPulseKR)"
|
|
58
59
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subcommand: feedback โ submit a bug report or feature request from the
|
|
3
|
+
* terminal (or from a Claude Code session) without opening a browser.
|
|
4
|
+
*
|
|
5
|
+
* claude-token-saver feedback "statusline์ด IntelliJ์์ ๊นจ์ ธ์"
|
|
6
|
+
* claude-token-saver feedback --title "cache chip" "5m TTL ์นฉ์ด ์ ์ฌ๋ผ์ง"
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: GitHub issues require a logged-in browser session, and
|
|
9
|
+
* corporate networks often block github.com entirely. This command tries
|
|
10
|
+
* three transports in order and reports which one carried the message:
|
|
11
|
+
*
|
|
12
|
+
* 1. `gh` CLI, if installed and authenticated โ files a real GitHub issue.
|
|
13
|
+
* 2. Anonymous Google Form POST โ no login, no GitHub access needed.
|
|
14
|
+
* (Only when the form endpoint below is configured for this build.)
|
|
15
|
+
* 3. Local fallback โ saves the report to a Markdown file and prints a
|
|
16
|
+
* prefilled GitHub new-issue URL to use from an unblocked machine.
|
|
17
|
+
*
|
|
18
|
+
* Metadata (tool version, OS, Node version) is attached automatically so a
|
|
19
|
+
* report is diagnosable without a follow-up round trip.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import os from 'node:os';
|
|
26
|
+
import { debug } from '../debug.js';
|
|
27
|
+
|
|
28
|
+
const REPO = 'rootstudioyaml/claude-token-saver';
|
|
29
|
+
const ISSUES_URL = `https://github.com/${REPO}/issues`;
|
|
30
|
+
|
|
31
|
+
// Anonymous submission endpoint (Google Form). A Google Form's formResponse
|
|
32
|
+
// URL accepts unauthenticated POSTs, which is exactly the property a
|
|
33
|
+
// login-free, GitHub-blocked-network path needs. Responses land in the
|
|
34
|
+
// maintainer's "claude-token-saver ํผ๋๋ฐฑ (Feedback)" form (published
|
|
35
|
+
// 2026-09-13, responder access: anyone with the link).
|
|
36
|
+
// id โ the /d/e/<id>/ segment of the form URL
|
|
37
|
+
// message โ entry.NNNN field id of the message question
|
|
38
|
+
// meta โ entry.NNNN field id of the metadata question
|
|
39
|
+
const FORM = {
|
|
40
|
+
id: '1FAIpQLScISjU8_t9y8xfp-X3jvLV6_RcSjQIJ4aydATC8NazkxcJbgg',
|
|
41
|
+
message: 'entry.79517542',
|
|
42
|
+
meta: 'entry.112678638',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function metadata(version) {
|
|
46
|
+
return [
|
|
47
|
+
`version: ${version}`,
|
|
48
|
+
`os: ${process.platform} ${os.release()}`,
|
|
49
|
+
`node: ${process.version}`,
|
|
50
|
+
].join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tryGhCli(title, body) {
|
|
54
|
+
try {
|
|
55
|
+
const auth = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 10_000 });
|
|
56
|
+
if (auth.status !== 0) return null;
|
|
57
|
+
const res = spawnSync('gh', ['issue', 'create', '-R', REPO, '--title', title, '--body', body],
|
|
58
|
+
{ encoding: 'utf8', timeout: 30_000 });
|
|
59
|
+
if (res.status !== 0) { debug('feedback:gh', res.stderr); return null; }
|
|
60
|
+
const url = String(res.stdout).trim().split('\n').pop();
|
|
61
|
+
return { transport: 'gh', url };
|
|
62
|
+
} catch (e) { debug('feedback:gh', e); return null; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function tryForm(title, body, meta) {
|
|
66
|
+
if (!FORM) return null;
|
|
67
|
+
try {
|
|
68
|
+
const params = new URLSearchParams();
|
|
69
|
+
params.set(FORM.message, `${title}\n\n${body}`);
|
|
70
|
+
params.set(FORM.meta, meta);
|
|
71
|
+
const res = await fetch(`https://docs.google.com/forms/d/e/${FORM.id}/formResponse`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
74
|
+
body: params.toString(),
|
|
75
|
+
signal: AbortSignal.timeout(15_000),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) { debug('feedback:form', `HTTP ${res.status}`); return null; }
|
|
78
|
+
return { transport: 'form' };
|
|
79
|
+
} catch (e) { debug('feedback:form', e); return null; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function saveLocal(title, body, dataDir) {
|
|
83
|
+
const dir = join(dataDir, 'feedback');
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
86
|
+
const file = join(dir, `${stamp}.md`);
|
|
87
|
+
writeFileSync(file, `# ${title}\n\n${body}\n`);
|
|
88
|
+
const prefilled = `${ISSUES_URL}/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
|
89
|
+
return { transport: 'local', file, prefilled };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function run({ args, getArg, version, dataDir }) {
|
|
93
|
+
// --anonymous skips the gh transport: for people whose gh CLI is signed in
|
|
94
|
+
// with an account they do not want attached to the report.
|
|
95
|
+
const anonymous = args.includes('--anonymous');
|
|
96
|
+
const rest = args.slice(1).filter((a, i, all) => {
|
|
97
|
+
if (a === '--title') return false;
|
|
98
|
+
if (all[i - 1] === '--title') return false;
|
|
99
|
+
return !a.startsWith('--');
|
|
100
|
+
});
|
|
101
|
+
const message = rest.join(' ').trim();
|
|
102
|
+
const explicitTitle = getArg('--title');
|
|
103
|
+
|
|
104
|
+
if (!message) {
|
|
105
|
+
console.log('usage: claude-token-saver feedback [--title "<์ ๋ชฉ>"] "<๋ด์ฉ>"');
|
|
106
|
+
console.log(` (GitHub์์ ์ง์ ์ ๋ณด: ${ISSUES_URL})`);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const title = explicitTitle || (message.length > 60 ? `${message.slice(0, 57)}...` : message);
|
|
112
|
+
const meta = metadata(version);
|
|
113
|
+
const body = `${message}\n\n---\n${meta}`;
|
|
114
|
+
|
|
115
|
+
const viaGh = anonymous ? null : tryGhCli(title, body);
|
|
116
|
+
if (viaGh) {
|
|
117
|
+
console.log(`feedback: GitHub ์ด์๋ก ๋ฑ๋กํ์ต๋๋ค โ ${viaGh.url}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const viaForm = await tryForm(title, message, meta);
|
|
122
|
+
if (viaForm) {
|
|
123
|
+
console.log('feedback: ์ ์ถํ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค. (์ต๋ช
์ ์ถ์ด๋ผ ๋ต๋ณ ์ถ์ ์ GitHub ์ด์์์๋ง ๊ฐ๋ฅํฉ๋๋ค)');
|
|
124
|
+
console.log(` ๊ณต๊ฐ ํธ๋์ปค: ${ISSUES_URL}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const local = saveLocal(title, body, dataDir);
|
|
129
|
+
console.log(`feedback: ์จ๋ผ์ธ ์ ์ถ ๊ฒฝ๋ก๊ฐ ์์ด ๋ก์ปฌ์ ์ ์ฅํ์ต๋๋ค: ${local.file}`);
|
|
130
|
+
console.log(' GitHub ์ ๊ทผ์ด ๊ฐ๋ฅํ ํ๊ฒฝ์์ ์๋ ์ฃผ์๋ฅผ ์ด๋ฉด ๋ด์ฉ์ด ์ฑ์์ง ์ด์ ์์ฑ ํ๋ฉด์ด ๋์ต๋๋ค:');
|
|
131
|
+
console.log(` ${local.prefilled}`);
|
|
132
|
+
}
|
package/src/commands/install.js
CHANGED
|
@@ -73,7 +73,31 @@ export async function run({ hasFlag }) {
|
|
|
73
73
|
print('SessionStart hook (route-scan)', r.sessionStartHook);
|
|
74
74
|
print('UserPromptSubmit hook (brief)', r.briefHook);
|
|
75
75
|
{
|
|
76
|
-
|
|
76
|
+
let s = r.statusline;
|
|
77
|
+
// A different statusline is already installed. Replacing it silently
|
|
78
|
+
// would be rude and leaving it silently loses the tool's main surface,
|
|
79
|
+
// so with a human attached the install asks. Default is "keep yours":
|
|
80
|
+
// an accidental Enter must not clobber someone's custom statusline.
|
|
81
|
+
if (s.action === 'skipped' && s.conflict && interactive) {
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log(lang === 'ko'
|
|
84
|
+
? ` statusline: ๊ธฐ์กด statusline์ด ์ด๋ฏธ ์ค์ ๋์ด ์์ต๋๋ค: ${s.existingCommand}`
|
|
85
|
+
: ` statusline: an existing statusline is already configured: ${s.existingCommand}`);
|
|
86
|
+
console.log(lang === 'ko'
|
|
87
|
+
? ' ๊ต์ฒดํ๋ฉด ํ ํฐยท์บ์ยท์ํ ๊ฒฝ๊ณ ๊ฐ statusline์ ํ์๋ฉ๋๋ค. ๊ธฐ์กด ์ค์ ์ ์ฌ๋ผ์ง๋๋ค.'
|
|
88
|
+
: ' replacing it shows token/cache/cap warnings in the statusline; the current one is removed.');
|
|
89
|
+
const replace = await confirm(lang === 'ko'
|
|
90
|
+
? ' claude-token-saver statusline์ผ๋ก ๊ต์ฒดํ ๊น์?'
|
|
91
|
+
: ' Replace it with the claude-token-saver statusline?', { defaultValue: false });
|
|
92
|
+
if (replace) {
|
|
93
|
+
const { installStatusline } = await import('../installer.js');
|
|
94
|
+
s = installStatusline({ force: true });
|
|
95
|
+
} else {
|
|
96
|
+
s = { ...s, reason: lang === 'ko'
|
|
97
|
+
? '๊ธฐ์กด statusline์ ์ ์งํ์ต๋๋ค. ๊ต์ฒดํ๋ ค๋ฉด `claude-token-saver install --force`'
|
|
98
|
+
: 'kept your statusline โ replace later with `claude-token-saver install --force`' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
77
101
|
const verb = s.action === 'exists' ? 'already configured (refreshInterval=5)'
|
|
78
102
|
: s.action === 'skipped' ? `skipped โ ${s.reason}`
|
|
79
103
|
: s.reason ? `${s.action} โ ${s.reason}`
|
|
@@ -326,5 +350,9 @@ export async function run({ hasFlag }) {
|
|
|
326
350
|
console.log('');
|
|
327
351
|
console.log('Tip: re-run with --force to overwrite the existing skill file.');
|
|
328
352
|
}
|
|
353
|
+
console.log('');
|
|
354
|
+
console.log(lang === 'ko'
|
|
355
|
+
? '๋ฒ๊ทธ ์ ๋ณดยท๊ธฐ๋ฅ ์ ์: https://github.com/rootstudioyaml/claude-token-saver/issues'
|
|
356
|
+
: 'Bug reports & feature requests: https://github.com/rootstudioyaml/claude-token-saver/issues');
|
|
329
357
|
return;
|
|
330
358
|
}
|
package/src/commands/korean.js
CHANGED
|
@@ -86,6 +86,7 @@ export async function run({ args, hasFlag }) {
|
|
|
86
86
|
console.error(`${file}: ${e.message}`);
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
+
if (lint.isHtmlFile(file)) text = lint.stripHtml(text);
|
|
89
90
|
const findings = lint.lintKoreanText(text, { code: !lint.isProseFile(file) });
|
|
90
91
|
total += findings.length;
|
|
91
92
|
if (findings.length) console.log(lint.formatFindings(file, findings));
|
package/src/installer.js
CHANGED
|
@@ -114,6 +114,12 @@ honor that for the rest of the turn without changing the saved setting.
|
|
|
114
114
|
capturing git status + cap snapshot, so a fresh session can resume cleanly.
|
|
115
115
|
- \`claude-token-saver mode\` โ show statusline preferences.
|
|
116
116
|
- \`claude-token-saver mode icon verbose 1d\` โ change preferences.
|
|
117
|
+
- \`claude-token-saver feedback "<message>"\` โ file a bug report or feature
|
|
118
|
+
request for this tool right from the session (tries the gh CLI, then an
|
|
119
|
+
anonymous no-login submission, then saves locally with a prefilled GitHub
|
|
120
|
+
issue URL; \`--anonymous\` skips the gh path).
|
|
121
|
+
Use it whenever the user says the tool itself misbehaves or wishes it did
|
|
122
|
+
something it does not โ offer to submit the report for them.
|
|
117
123
|
|
|
118
124
|
## Storage layout (for reference)
|
|
119
125
|
|
|
@@ -197,7 +203,10 @@ export function installStatusline({ force = false } = {}) {
|
|
|
197
203
|
}
|
|
198
204
|
|
|
199
205
|
if (!force) {
|
|
200
|
-
|
|
206
|
+
// `conflict` lets the interactive install distinguish "someone else's
|
|
207
|
+
// statusline is here" (worth asking about) from other skip reasons
|
|
208
|
+
// (unreadable JSON), which a prompt cannot fix.
|
|
209
|
+
return { path: file, action: 'skipped', conflict: true, existingCommand: cur.command, reason: `existing statusLine command (${cur.command}) โ re-run with --force to overwrite` };
|
|
201
210
|
}
|
|
202
211
|
settings.statusLine = {
|
|
203
212
|
type: 'command',
|
package/src/korean-lint.cjs
CHANGED
|
@@ -35,12 +35,21 @@
|
|
|
35
35
|
// Documents. Under the `prose` scope these are the only files checked.
|
|
36
36
|
const PROSE_EXTENSIONS = new Set(['.md', '.mdx', '.markdown', '.txt', '.rst', '.adoc']);
|
|
37
37
|
|
|
38
|
+
// HTML is a document a reader receives, so it is checked under BOTH scopes,
|
|
39
|
+
// like prose โ but through `stripHtml` first, so tags, comments, and CSS never
|
|
40
|
+
// trip the rules. Script bodies are kept: on a static page the Korean UI copy
|
|
41
|
+
// (i18n dictionaries, template strings) lives exactly there, and that gap is
|
|
42
|
+
// how an em dash shipped to a real landing page unchecked (2026-09-13).
|
|
43
|
+
const HTML_EXTENSIONS = new Set(['.html', '.htm', '.xhtml', '.vue', '.svelte']);
|
|
44
|
+
|
|
38
45
|
// Only two kinds of path are skipped: installed dependencies and VCS
|
|
39
46
|
// internals, neither of which anyone in this session wrote. Build output is
|
|
40
47
|
// deliberately NOT on this list. Generated artifacts are the files a reader
|
|
41
48
|
// actually receives, so exempting `dist/` or `build/` would exempt the very
|
|
42
49
|
// documents the check exists for.
|
|
43
50
|
const SKIP_PATH = /(^|[\\/])(node_modules|\.git|.*\.min\.[a-z]+|.*-lock\.json|.*\.lock)([\\/]|$)/i;
|
|
51
|
+
// This module and the hook copy that embeds it quote every banned form.
|
|
52
|
+
const SELF_PATH = /(^|[\\/])(korean-lint\.cjs|cache-monitor-hook\.cjs)$/i;
|
|
44
53
|
const BINARY_EXTENSIONS = new Set([
|
|
45
54
|
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.pdf', '.zip', '.gz', '.tar',
|
|
46
55
|
'.mp3', '.mp4', '.wav', '.mov', '.woff', '.woff2', '.ttf', '.otf', '.map', '.bin',
|
|
@@ -71,6 +80,18 @@ const METAPHOR_LEXICON = [
|
|
|
71
80
|
{ re: /๋ฒฝ์ ๋ถ๋ช|๊ธธ์ ์ด|๋ฌธ์ ์ด/, fix: '๋ฌด์์ด ๋งํ๊ณ ๋ฌด์์ด ๊ฐ๋ฅํด์ง๋์ง ๊ทธ๋๋ก ์ ์ต๋๋ค' },
|
|
72
81
|
{ re: /๊ฐ๋ฆผ๊ธธ|๊ฐ๋ฆฌ๋ ์ง์ /, fix: "'๊ฒฐ์ ์ด ๋๋๋ ์กฐ๊ฑด'์ผ๋ก ๋ฐ๊ฟ๋๋ค" },
|
|
73
82
|
{ re: /๊นจ์ด๋|์ ๋ค์ด ์/, fix: "'๋์์ ์์ํ๋ค'ยท'์คํ๋์ง ์๊ณ ์๋ค'๋ก ๋ฐ๊ฟ๋๋ค" },
|
|
83
|
+
// Added 2026-09-13. "fails loudly / fails silently" reads as idiom in English
|
|
84
|
+
// but lands as figurative vocabulary in Korean: a failure has no volume.
|
|
85
|
+
// 0 hits across 400 Korean files in yaml-sns-agent, so the confirm-request is
|
|
86
|
+
// not noise.
|
|
87
|
+
{
|
|
88
|
+
re: /(?:์๋๋ฝ๊ฒ|์๋๋ฌ์ด|์กฐ์ฉํ|์กฐ์ฉํ)\s*[๊ฐ-ํฃ]*(?:์คํจ|ํ๋ฆฌ|๋๋ฌ๋|์ด๊ธ)|(?:์คํจ|์ค๋ฅ|๋ฒ๊ทธ)[๊ฐ์๋์ด]?\s*(?:์๋๋ฝ|์๋๋ฌ์ด|์กฐ์ฉํ|์กฐ์ฉํ)/,
|
|
89
|
+
fix: "๋ฌด์์ผ๋ก ์คํจ๋ฅผ ํ์ธํ๋์ง ์ ์ต๋๋ค. '์ข
๋ฃ ์ฝ๋๋ก ๋ฐ๋ก ๋๋ฌ๋๋๊ฐ'ยท'๊ฒ์ฆ ์์ด ์ง๋๊ฐ๋๊ฐ'",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
re: /์์ ๋ง/,
|
|
93
|
+
fix: "'๊ฒ์ฆ ์๋จ'ยท'ํ์ธ ์ฅ์น'์ฒ๋ผ ๋ฌด์์ด ์คํจ๋ฅผ ์ก์๋ด๋์ง ๊ทธ๋๋ก ์ ์ต๋๋ค",
|
|
94
|
+
},
|
|
74
95
|
];
|
|
75
96
|
|
|
76
97
|
// Guidance 3.2/3.3: literal renderings of English noun phrases.
|
|
@@ -111,9 +132,13 @@ const NOMINAL_ENDING = /(?:์|ํจ|๋จ|์|์ |๋ก |์|์ฑ|ํ)\.$/;
|
|
|
111
132
|
function isLintTarget(filePath, scope = 'all') {
|
|
112
133
|
if (!filePath) return false;
|
|
113
134
|
if (SKIP_PATH.test(filePath)) return false;
|
|
135
|
+
// The rule table has to spell every banned form out, so this file always
|
|
136
|
+
// matches its own lexicon. Checking it reports the rules, not a breach.
|
|
137
|
+
if (SELF_PATH.test(filePath)) return false;
|
|
114
138
|
const dot = filePath.lastIndexOf('.');
|
|
115
139
|
const ext = dot === -1 ? '' : filePath.slice(dot).toLowerCase();
|
|
116
140
|
if (PROSE_EXTENSIONS.has(ext)) return true;
|
|
141
|
+
if (HTML_EXTENSIONS.has(ext)) return true;
|
|
117
142
|
if (scope !== 'all') return false;
|
|
118
143
|
if (!ext) return false;
|
|
119
144
|
return !BINARY_EXTENSIONS.has(ext);
|
|
@@ -124,6 +149,26 @@ function isProseFile(filePath) {
|
|
|
124
149
|
return isLintTarget(filePath, 'prose');
|
|
125
150
|
}
|
|
126
151
|
|
|
152
|
+
function isHtmlFile(filePath) {
|
|
153
|
+
const dot = String(filePath).lastIndexOf('.');
|
|
154
|
+
const ext = dot === -1 ? '' : String(filePath).slice(dot).toLowerCase();
|
|
155
|
+
return HTML_EXTENSIONS.has(ext);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Blank out the parts of an HTML document no reader sees โ comments, CSS, and
|
|
160
|
+
* the tags themselves โ while keeping every newline, so finding line numbers
|
|
161
|
+
* still point into the original file. Script bodies stay: that is where the
|
|
162
|
+
* page's Korean copy lives on a static site.
|
|
163
|
+
*/
|
|
164
|
+
function stripHtml(text) {
|
|
165
|
+
const blank = (m) => m.replace(/[^\n]/g, ' ');
|
|
166
|
+
return String(text)
|
|
167
|
+
.replace(/<!--[\s\S]*?-->/g, blank)
|
|
168
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, blank)
|
|
169
|
+
.replace(/<[^>]+>/g, blank);
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
/** Drop fenced code blocks and inline code so snippets never trip the rules. */
|
|
128
173
|
function stripCode(text) {
|
|
129
174
|
const out = [];
|
|
@@ -226,9 +271,12 @@ function lintToolUse(context, { scope = 'all' } = {}) {
|
|
|
226
271
|
const filePath = toolInput && typeof toolInput.file_path === 'string' ? toolInput.file_path : '';
|
|
227
272
|
if (!isLintTarget(filePath, scope)) return null;
|
|
228
273
|
|
|
229
|
-
|
|
274
|
+
let text = writtenTextOf(toolName, toolInput);
|
|
230
275
|
if (!text || !hasKorean(text)) return null;
|
|
231
276
|
|
|
277
|
+
// Edits arrive as fragments, so tag-stripping only applies when the payload
|
|
278
|
+
// is a whole document; a fragment is still linted, just without stripping.
|
|
279
|
+
if (isHtmlFile(filePath) && toolName === 'Write') text = stripHtml(text);
|
|
232
280
|
const findings = lintKoreanText(text, { code: !isProseFile(filePath) });
|
|
233
281
|
if (findings.length === 0) return null;
|
|
234
282
|
return { filePath, findings };
|
|
@@ -246,6 +294,8 @@ module.exports = {
|
|
|
246
294
|
METAPHOR_LEXICON,
|
|
247
295
|
isLintTarget,
|
|
248
296
|
isProseFile,
|
|
297
|
+
isHtmlFile,
|
|
298
|
+
stripHtml,
|
|
249
299
|
lintKoreanText,
|
|
250
300
|
writtenTextOf,
|
|
251
301
|
lintToolUse,
|