claude-cache-keepalive 0.1.8 → 0.1.10
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 -0
- package/package.json +1 -1
- package/src/cli.mjs +3 -0
- package/src/host.mjs +17 -1
- package/src/keepalive.mjs +37 -0
- package/src/statusline/segment.mjs +3 -1
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ Environment variables (mostly for testing / advanced use):
|
|
|
76
76
|
| `CWARM_ESC_DELAY_MS` | gap between the `Esc` and the keepalive message (default `250`) |
|
|
77
77
|
| `CWARM_THRESHOLD_S` | override idle threshold (seconds) |
|
|
78
78
|
| `CWARM_TTL_S` | override cooldown (seconds) |
|
|
79
|
+
| `CWARM_BILLING` | force billing mode: `subscription` (keep warming) or `credits` (suspend); otherwise auto‑detected |
|
|
79
80
|
| `CWARM_CLAUDE` | path to the `claude` executable (otherwise auto‑detected via `which`/`where`) |
|
|
80
81
|
| `CLAUDE_CONFIG_DIR` | Claude config dir (default `~/.claude`) |
|
|
81
82
|
|
|
@@ -126,6 +127,7 @@ Environment variables (mostly for testing / advanced use):
|
|
|
126
127
|
| `CWARM_ESC_DELAY_MS` | `Esc` 與訊息之間的間隔(預設 `250`) |
|
|
127
128
|
| `CWARM_THRESHOLD_S` | 覆寫閒置門檻(秒) |
|
|
128
129
|
| `CWARM_TTL_S` | 覆寫冷卻(秒) |
|
|
130
|
+
| `CWARM_BILLING` | 強制指定計費模式:`subscription`(照常保溫)或 `credits`(暫停);否則自動偵測 |
|
|
129
131
|
| `CWARM_CLAUDE` | `claude` 執行檔路徑(否則用 `which`/`where` 自動偵測) |
|
|
130
132
|
| `CLAUDE_CONFIG_DIR` | Claude 設定目錄(預設 `~/.claude`) |
|
|
131
133
|
|
|
@@ -137,6 +139,14 @@ Environment variables (mostly for testing / advanced use):
|
|
|
137
139
|
|
|
138
140
|
## Changelog
|
|
139
141
|
|
|
142
|
+
### 0.1.10
|
|
143
|
+
- **Fix:** the optional statusline segment now reflects the 0.1.9 billing guard. On credits/API billing the keepalive is suspended, but the `♻️ cache …` countdown kept ticking as if warming were still active — misleading. The segment now shows `⏸️ cwarm off (API)` while `detectBillingMode` reports credits, and returns to the normal countdown once you're back on a subscription account.
|
|
144
|
+
- **修正:** 選配的 statusline 區段現在會反映 0.1.9 的計費防護。credits/API 計費時 keepalive 已暫停,但 `♻️ cache …` 倒數仍照跑,彷彿還在保溫——會造成誤導。現在 `detectBillingMode` 回報 credits 期間會改顯示 `⏸️ cwarm off (API)`,切回訂閱帳號後自動恢復正常倒數。
|
|
145
|
+
|
|
146
|
+
### 0.1.9
|
|
147
|
+
- **Fix:** keepalive now **auto-suspends on credits/API billing**. If you `/login` into an Anthropic Console account (credits usage) — or run purely on `ANTHROPIC_API_KEY` — every injected `hi` and every cache refresh costs real money, so warming the cache no longer makes sense (on a subscription it only spends rate-limit quota). cwarm now detects the billing mode each tick (from `~/.claude/.credentials.json`'s `claudeAiOauth.subscriptionType`, falling back to `~/.claude.json`'s `oauthAccount.billingType`, then the `ANTHROPIC_API_KEY` env var) and skips injection while on credits, logging `skip: credits/API billing detected` once; switching back to a subscription account mid-session resumes warming automatically. Override with `CWARM_BILLING=subscription|credits` if detection guesses wrong. Adds `billingModeFromSources` / `detectBillingMode`.
|
|
148
|
+
- **修正:** keepalive 現在會在 **credits/API 計費時自動暫停**。若你用 `/login` 切到 Anthropic Console 帳號(credits usage),或純靠 `ANTHROPIC_API_KEY` 執行,每次注入的 `hi` 與每次 cache 續寫都是實際花錢,保溫就失去意義(訂閱制下花的只是額度)。cwarm 現在每個 tick 偵測計費模式(先看 `~/.claude/.credentials.json` 的 `claudeAiOauth.subscriptionType`,再退回 `~/.claude.json` 的 `oauthAccount.billingType`,最後看 `ANTHROPIC_API_KEY` 環境變數),credits 期間跳過注入並記錄一次 `skip: credits/API billing detected`;session 中切回訂閱帳號會自動恢復保溫。偵測誤判可用 `CWARM_BILLING=subscription|credits` 強制指定。新增 `billingModeFromSources`/`detectBillingMode`。
|
|
149
|
+
|
|
140
150
|
### 0.1.8
|
|
141
151
|
- **Fix:** the keepalive's `Esc`‑prefix (added in 0.1.5) could dismiss Claude Code's **folder‑trust dialog** ("Do you trust the files in this folder?"). Since 0.1.7 dropped the implicit `--continue`, bare `cwarm` starts a *fresh* session, so an untrusted directory shows the trust dialog on launch; if you stepped away past the idle threshold, the keepalive's `Esc` cancelled it — which writes `hasTrustDialogAccepted: false` into `~/.claude.json` and makes that folder's `.claude/settings.local.json` permissions silently ignored (the "Ignoring N permissions.allow entries: this workspace has not been trusted" warning you only see after `/exit` restores the normal screen). The keepalive now detects the trust dialog on screen and **skips the whole tick** (no `Esc`, no message), leaving it for you to answer; all other mandatory prompts keep the 0.1.5 `Esc` behaviour. Adds `looksLikeTrustPrompt`.
|
|
142
152
|
- **修正:** 0.1.5 加入的 keepalive **`Esc` 先行**可能會把 Claude Code 的**資料夾信任對話框**(「Do you trust the files in this folder?」)給收掉。自 0.1.7 拿掉隱含的 `--continue` 後,單獨打 `cwarm` 會開*全新* session,所以進入未信任的資料夾時啟動就會跳信任框;若你人走開、閒置過門檻,keepalive 的 `Esc` 就把它取消掉——這會在 `~/.claude.json` 寫下 `hasTrustDialogAccepted: false`,使該資料夾的 `.claude/settings.local.json` 權限被靜默忽略(就是你 `/exit` 還原一般畫面後才看到的「Ignoring N permissions.allow entries: this workspace has not been trusted」警告)。keepalive 現在會偵測畫面上的信任框並**整輪跳過**(不送 `Esc`、不送訊息),交給你本人回答;其他必答提示維持 0.1.5 的 `Esc` 行為。新增 `looksLikeTrustPrompt`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-cache-keepalive",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Keep Claude Code's prompt cache warm while idle, by running claude inside a PTY host and injecting a tiny keepalive when you step away. Cross-platform, no tmux required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -32,6 +32,9 @@ Everything except the 'setup' subcommand is passed straight to claude
|
|
|
32
32
|
Keepalive only fires after you've been idle past the cache-TTL threshold.
|
|
33
33
|
The TTL is auto-detected from the transcript's cache_creation (1h cache ->
|
|
34
34
|
fire after ~58min idle; 5m cache -> ~4min), not guessed from your plan.
|
|
35
|
+
Keepalive auto-suspends when you're on credits/API billing (Console account
|
|
36
|
+
via /login, or ANTHROPIC_API_KEY) — injections would cost real money there.
|
|
37
|
+
Override with CWARM_BILLING=subscription|credits if detection is wrong.
|
|
35
38
|
Pause anytime: touch ~/.claude/cwarm.disabled
|
|
36
39
|
Log: ~/.claude/cwarm-keepalive.log
|
|
37
40
|
`);
|
package/src/host.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { defaultClaudeDir, regimeParams, detectTtlRegime, decideInject, transcriptIdleMs, looksLikeTrustPrompt } from './keepalive.mjs';
|
|
8
|
+
import { defaultClaudeDir, regimeParams, detectTtlRegime, decideInject, transcriptIdleMs, looksLikeTrustPrompt, detectBillingMode } from './keepalive.mjs';
|
|
9
9
|
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const isWin = process.platform === 'win32';
|
|
@@ -83,7 +83,23 @@ export function startHost(opts = {}) {
|
|
|
83
83
|
|
|
84
84
|
let lastFire = 0;
|
|
85
85
|
let trustGuardLogged = false;
|
|
86
|
+
let creditsGuardLogged = false;
|
|
86
87
|
const timer = setInterval(() => {
|
|
88
|
+
// credits / API 計費(/login 切到 Console 帳號、或只用 ANTHROPIC_API_KEY)時,每次注入
|
|
89
|
+
// 都是實際花錢,保溫沒有意義 → 自動暫停。每個 tick 重新偵測,session 中 /login 切回
|
|
90
|
+
// 訂閱帳號會自動恢復保溫。要強制指定:CWARM_BILLING=subscription|credits。
|
|
91
|
+
const billing = detectBillingMode(claudeDir);
|
|
92
|
+
if (billing === 'credits') {
|
|
93
|
+
if (!creditsGuardLogged) {
|
|
94
|
+
creditsGuardLogged = true;
|
|
95
|
+
try { fs.appendFileSync(LOG, `${new Date().toISOString()} skip: credits/API billing detected — keepalive suspended (CWARM_BILLING=subscription to override)\n`); } catch {}
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (creditsGuardLogged) {
|
|
100
|
+
creditsGuardLogged = false;
|
|
101
|
+
try { fs.appendFileSync(LOG, `${new Date().toISOString()} resume: subscription billing detected — keepalive re-enabled\n`); } catch {}
|
|
102
|
+
}
|
|
87
103
|
// 信任對話框在畫面上時,這輪完全不動作(連 Esc 都不送)——那是使用者本人該回答的框,被保溫
|
|
88
104
|
// Esc 掉會留下 hasTrustDialogAccepted:false,害該資料夾 settings.local.json 權限失效、之後不再跳框。
|
|
89
105
|
if (looksLikeTrustPrompt(screenBuf)) {
|
package/src/keepalive.mjs
CHANGED
|
@@ -126,6 +126,43 @@ export function detectTtlRegime(claudeDir, cwd, opts) {
|
|
|
126
126
|
return readTtlRegime(transcriptPath(claudeDir, cwd), opts);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// ---- 計費模式偵測(subscription vs credits/API)----
|
|
130
|
+
// 保溫只在「訂閱制」下划算:額度是 rate limit,cache 保溫省的是額度。若 /login 切到
|
|
131
|
+
// Console 帳號(credits / API 計費),每次注入的 "hi" 與 cache 續寫都是實際花錢,
|
|
132
|
+
// 保溫反而燒錢 → 偵測到 credits 就自動暫停注入。
|
|
133
|
+
// 判斷順序:CWARM_BILLING 強制指定 → .credentials.json 的 claudeAiOauth.subscriptionType
|
|
134
|
+
// (/login 當下就會改寫,最即時)→ .claude.json 的 oauthAccount.billingType →
|
|
135
|
+
// ANTHROPIC_API_KEY 環境變數 → null(無法判斷,維持現行行為照常保溫)。
|
|
136
|
+
export function billingModeFromSources({ env = {}, credentials = null, config = null } = {}) {
|
|
137
|
+
const forced = String(env.CWARM_BILLING || '').toLowerCase();
|
|
138
|
+
if (forced === 'subscription' || forced === 'credits') return forced;
|
|
139
|
+
const oauth = credentials && credentials.claudeAiOauth;
|
|
140
|
+
if (oauth && typeof oauth === 'object') {
|
|
141
|
+
const sub = oauth.subscriptionType;
|
|
142
|
+
if (typeof sub === 'string' && /^(pro|max|team|enterprise)/i.test(sub)) return 'subscription';
|
|
143
|
+
return 'credits'; // 有 OAuth 登入但沒有訂閱層級 → Console 帳號(credits 計費)
|
|
144
|
+
}
|
|
145
|
+
const billing = config && config.oauthAccount && config.oauthAccount.billingType;
|
|
146
|
+
if (billing === 'stripe_subscription') return 'subscription';
|
|
147
|
+
if (typeof billing === 'string' && billing) return 'credits';
|
|
148
|
+
if (env.ANTHROPIC_API_KEY) return 'credits'; // 沒有任何登入痕跡、只有 API key → 純 API 計費
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readJsonSafe(p) {
|
|
153
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// IO 版:讀 claudeDir 下的 .credentials.json 與 .claude.json(CLAUDE_CONFIG_DIR 佈局),
|
|
157
|
+
// 後者找不到再退回 homedir 的 ~/.claude.json(預設佈局)。macOS 憑證在 Keychain、
|
|
158
|
+
// 沒有 .credentials.json 檔,會自然落到 config 判斷。
|
|
159
|
+
export function detectBillingMode(claudeDir, { env = process.env, homedir = os.homedir() } = {}) {
|
|
160
|
+
const credentials = readJsonSafe(path.join(claudeDir, '.credentials.json'));
|
|
161
|
+
const config = readJsonSafe(path.join(claudeDir, '.claude.json'))
|
|
162
|
+
?? readJsonSafe(path.join(homedir, '.claude.json'));
|
|
163
|
+
return billingModeFromSources({ env, credentials, config });
|
|
164
|
+
}
|
|
165
|
+
|
|
129
166
|
// 純決策:現在該不該注入 keepalive?idleMs = 距上次訊息多久(由 transcriptIdleMs 算)。
|
|
130
167
|
// screenIdleMs = 距 claude 最近一次「畫面輸出」多久;quietMs = 需靜止多久才放行。
|
|
131
168
|
// 為什麼要這個畫面靜默門檻:transcript 在「等你回答必答提示(權限/選單/計畫批准)」與
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import { defaultClaudeDir, readTtlRegime, regimeParams } from '../keepalive.mjs';
|
|
7
|
+
import { defaultClaudeDir, readTtlRegime, regimeParams, detectBillingMode } from '../keepalive.mjs';
|
|
8
8
|
|
|
9
9
|
const claudeDir = defaultClaudeDir();
|
|
10
10
|
const ORIG = path.join(claudeDir, 'cwarm-statusline-orig.json');
|
|
@@ -15,6 +15,8 @@ function readStdin() {
|
|
|
15
15
|
|
|
16
16
|
// cache 倒數段:用 transcript mtime 當 idle、用 transcript 實測的 cache_creation 判 TTL(1h / 5m)。
|
|
17
17
|
function cacheSegment(payload) {
|
|
18
|
+
// credits/API 計費時 keepalive 已暫停,倒數沒有意義且會誤導 → 顯示暫停標記
|
|
19
|
+
if (detectBillingMode(claudeDir) === 'credits') return '⏸️ cwarm off (API)';
|
|
18
20
|
const tp = payload?.transcript_path;
|
|
19
21
|
if (!tp) return '';
|
|
20
22
|
let mtimeMs;
|