claude-token-saver 3.38.0 → 3.39.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 +15 -0
- package/README.md +15 -0
- package/bin/cli.js +11 -1
- package/package.json +1 -1
- package/src/commands/feedback.js +132 -0
- package/src/commands/install.js +29 -1
- package/src/installer.js +10 -1
package/README.ko.md
CHANGED
|
@@ -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/claude-token-saver/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
|
@@ -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/claude-token-saver/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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.39.0",
|
|
4
4
|
"description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet — post-hoc session analysis, no realtime router, no extra LLM calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -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/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',
|