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 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.38.0",
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
+ }
@@ -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
- const s = r.statusline;
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
- return { path: file, action: 'skipped', reason: `existing statusLine command (${cur.command}) — re-run with --force to overwrite` };
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',