ccc-notifier 0.2.0 → 0.4.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.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ccc-notifier
2
2
 
3
- **ccc = Claude Code Cost.** Claude Code で **プロンプトを実行するたび**、そのターンにかかったコストを **$(USD)と¥(JPY)の両方** で自動通知するツールです。**5分で導入できます。**
3
+ **ccc = Claude Code Cost**(現在は [Codex CLI](docs/codex.md) のコストも同じ仕組みで扱えます)。Claude Code や Codex CLI で **プロンプトを実行するたび**、そのターンにかかったコストを **$(USD)と¥(JPY)の両方** で自動通知するツールです。**5分で導入できます。**
4
4
 
5
5
  ```
6
6
  💰 API換算 $0.267(¥40)| Fable 5
@@ -18,18 +18,20 @@ in 1.2k(cache 40%)/ out 480 · 📁 my-app · 今日: $1.85
18
18
 
19
19
  ## 特徴 / Features
20
20
 
21
- - **ターン毎に自動通知** — Claude Code の応答が完了するたび(Stop hook)に、そのターンのコストを自動でプッシュ通知します。自分から `/cost` を見に行く必要はありません
21
+ - **ターン毎に自動通知** — Claude Code / Codex CLI の応答が完了するたび(Stop hook)に、そのターンのコストを自動でプッシュ通知します。自分から `/cost` を見に行く必要はありません
22
22
  - **$ と ¥ を併記** — USD と JPY の両方を毎回表示します(為替レートは自動取得 + キャッシュ + 固定フォールバックの三段構え)
23
23
  - **プロンプト全文をローカルに履歴保存** — `~/.ccc-notifier/history.jsonl` にそのターンのプロンプト全文を保存します(外部には送信されません)
24
- - **HTMLダッシュボード** — `dashboard` コマンドで、サマリー・コスト推移(**日 / 週 / 月**で切替、横スクロールで過去まで)・モデル別/プロジェクト別内訳・検索できるターン履歴を1枚の HTML(完全自己完結・ライト/ダーク対応)に書き出してブラウザで開きます。棒をクリックするとその期間が選択され、内訳・履歴が連動(「通算」で全期間)
25
- - **月予算(monthly budget)** — 月に使える金額(USD)を設定すると、ダッシュボードに**当月の使用額 / 予算・使用率(%)** をプログレスバーで表示します(`init` の対話、または `ccc-notifier budget <金額>` で設定)
24
+ - **HTMLダッシュボード** — `dashboard` コマンドで、サマリー・コスト推移(**日 / 週 / 月**で切替、横スクロールで過去まで)・モデル別/プロジェクト別内訳・検索できるターン履歴を1枚の HTML(完全自己完結・ライト/ダーク対応)に書き出してブラウザで開きます。棒をクリックするとその期間が選択され、内訳・履歴が連動(「通算」で全期間)。Codex CLI を併用していれば **Claude / Codex** のソースフィルタも使えます
25
+ - **月予算(monthly budget)** — 月に使える金額(USD)を設定すると、ダッシュボードに**当月の使用額 / 予算・使用率(%)** をプログレスバーで表示します(`init` の対話、または `ccc-notifier budget <金額>` で設定。Claude Code と Codex CLI の合算です)
26
+ - **Codex CLI もそのまま検出** — `init` を実行すると `~/.codex` の有無を見て、あれば Codex にも導入するか聞かれます。通知・履歴・ダッシュボード・`sweep` は Claude Code と全く同じ仕組みで扱います([詳細](docs/codex.md))
26
27
  - **OS 標準の通知機構のみ使用・追加依存ゼロ** — 通知は macOS では `osascript`、Windows では PowerShell 標準のトースト通知機能のみで送信します(node-notifier 等の外部通知ライブラリには一切依存しません)
27
- - **全処理ローカル・フェイルセーフ設計** — 通知や集計の処理が失敗しても、Claude Code 本体の応答は絶対にブロックしません
28
+ - **全処理ローカル・フェイルセーフ設計** — 通知や集計の処理が失敗しても、Claude Code / Codex CLI 本体の応答は絶対にブロックしません
28
29
 
29
30
  ## 必要環境 / Requirements
30
31
 
31
32
  - Node.js 20 以上(未導入の場合は [Node.js の用意](docs/installing-node.md) を参照してください)
32
- - Claude Code(インストール・利用中であること)
33
+ - Claude Code(インストール・利用中であること。`init` は Claude Code の Stop hook を必ず設定します)
34
+ - (任意)[Codex CLI](docs/codex.md) を併用している場合は、`init` が自動検出してそのコストも同じ仕組みで通知できます
33
35
 
34
36
  Windows / WSL2 で使う場合は、環境ごとの手順を [Windows / WSL2 での導入](docs/windows-wsl2.md) にまとめています。
35
37
 
@@ -70,16 +72,17 @@ node dist/cli.js init
70
72
 
71
73
  2. **質問に答える**
72
74
 
73
- 対話形式で次の4点を聞かれます。
75
+ 対話形式で次の4点(Codex CLI を検出した場合はもう1点)を聞かれます。
74
76
 
75
77
  - 通知チャネル(OS通知のみ / Slackのみ / OS通知+Slack / 通知なし(記録・ダッシュボードのみ))
76
78
  - コスト表示ラベル(API換算 / 実額)
77
79
  - USD/JPY のフォールバック為替レート(既定 150円)
78
80
  - 月の予算(USD、既定 $400。`0` で無効。ダッシュボードに当月の使用率を表示。詳細は [月予算](docs/monthly-budget.md))
81
+ - (Codex CLI 検出時)Codex にもコスト通知を入れるか(既定 Yes。詳細は [Codex CLI 対応](docs/codex.md))
79
82
 
80
83
  完了すると Claude Code の `~/.claude/settings.json` に Stop hook が自動で追記されます。**既存の設定内容(他の hook や設定)は一切変更されず**、書き込み前に必ず `settings.json.bak-<タイムスタンプ>` としてバックアップが作成されます。settings.json が壊れている(JSONとして解析できない)場合は自動編集を諦め、手動で追記する内容を画面に表示するだけで、ファイルには一切書き込みません。
81
84
 
82
- 3. **Claude Code で何か実行してみる**
85
+ 3. **Claude Code(や Codex CLI)で何か実行してみる**
83
86
 
84
87
  ひとこと実行して応答が完了すると、通知が届きます。
85
88
 
@@ -99,6 +102,8 @@ CI などから非対話で `init` したい場合は次のフラグが使えま
99
102
  | `--label <api_equivalent\|actual>` | コスト表示ラベルを指定 |
100
103
  | `--rate <number>` | USD/JPY フォールバックレートを指定 |
101
104
  | `--budget <USD>` | 月予算(USD)を指定(0 で無効)。未指定なら既定 **$400**(既存設定があれば維持) |
105
+ | `--codex` | Codex CLI にも Stop hook を導入する(`~/.codex` 未検出でも強制導入。詳細は [Codex CLI 対応](docs/codex.md)) |
106
+ | `--no-codex` | Codex hook を導入しない(検出しても触らない)。`--codex` とは併用不可 |
102
107
 
103
108
  ## コマンド一覧 / Commands
104
109
 
@@ -107,7 +112,7 @@ CI などから非対話で `init` したい場合は次のフラグが使えま
107
112
  | `init` | Stop hook を対話形式でセットアップ(前述のフラグで非対話実行も可) |
108
113
  | `doctor` | hook登録・設定・単価表・為替・通知・直近セッション合計を診断 |
109
114
  | `report [--days N] [--json]` | 蓄積した履歴を集計してターミナルに表示(`--days` の既定は30、不正な値も30扱い)。`--json` で機械可読な出力 |
110
- | `dashboard [--days N] [--no-open] [--out <path>] [--refresh <sec>\|--no-refresh]` | 履歴を可視化した HTML ダッシュボードを生成してブラウザで開く(既定で全履歴を埋め込み、日/週/月・通算をブラウザ側で切替。詳細は [ダッシュボード](docs/dashboard.md)) |
115
+ | `dashboard [--all\|--days N] [--no-open] [--out <path>] [--refresh <sec>\|--no-refresh]` | 履歴を可視化した HTML ダッシュボードを生成してブラウザで開く(引数なしは設定期間の直近版 `report.html`、`--all` は全履歴版 `report-all.html`。詳細は [ダッシュボード](docs/dashboard.md)) |
111
116
  | `sweep [--dry-run] [--days N] [--include-active]` | 過去の未計上分(hook 導入前や後から完了したサブエージェント分)を一括で履歴に取り込む。ローカル走査のみで **Claude API を呼ばず料金ゼロ**・二重計上なし(詳細は [過去分の取り込み](docs/sweep.md)) |
112
117
  | `history <clear\|redact> [--days N] [--yes]` | 履歴(`history.jsonl`)を削除。`clear` はレコードごと、`redact` はプロンプト全文だけ消去。`--days N` で「N 日より前」だけ対象(詳細は [履歴の削除](docs/dashboard.md#履歴の削除--deleting-history)) |
113
118
  | `budget [<USD>]` | 月予算(USD)の表示/設定。金額省略で現在の予算と当月の使用率を表示、`budget 400` で設定、`budget 0` で解除(詳細は [月予算](docs/monthly-budget.md)) |
@@ -133,13 +138,15 @@ CI などから非対話で `init` したい場合は次のフラグが使えま
133
138
 
134
139
  ## ダッシュボード / Dashboard
135
140
 
136
- `dashboard` コマンドで、サマリー(今日 / 今週 / 今月 / 通算)、コスト推移(日/週/月で切替・横スクロールで過去まで)、モデル別/プロジェクト別内訳、検索・行展開できるターン履歴を1枚の HTML に書き出してブラウザで開きます。生成物は **CSS/JS/SVG をすべてインライン化した完全自己完結・オフライン動作・外部通信ゼロ**のファイルで、ライト/ダーク両対応です。
141
+ `dashboard` コマンドで、サマリー(今日 / 今週 / 今月 / 通算)、コスト推移(日/週/月で切替・横スクロールで過去まで)、モデル別/プロジェクト別内訳、検索・行展開できるターン履歴を HTML に書き出してブラウザで開きます。自動生成物は、毎ターン更新する直近版 `~/.ccc-notifier/report.html`(既定30日)と、ローカル日の最初の正常なターンだけ更新する全履歴版 `~/.ccc-notifier/report-all.html` に分かれます。片方が未生成でも生成方法を示すplaceholderを置くため、ページ内リンクは切れません。履歴・カーソル・生成snapshotは `cache/data.lock/` で直列化されます。履歴の読み込みと解析は正確な当月予算を保つため全履歴が対象です。生成物は **CSS/JS/SVG をすべてインライン化した完全自己完結・オフライン動作・外部通信ゼロ**のファイルで、ライト/ダーク両対応です。
137
142
 
138
143
  ```bash
139
- npx ccc-notifier dashboard # 生成してブラウザで開く
144
+ npx ccc-notifier dashboard # 設定期間(既定30日)の直近版 report.html を生成して開く
145
+ npx ccc-notifier dashboard --all # 全履歴版 report-all.html を生成して開く
146
+ npx ccc-notifier dashboard --days 7 # 直近7日の report.html を生成して開く
140
147
  ```
141
148
 
142
- グラフの棒をクリックするとその期間に内訳・履歴が連動し、「通算」で全期間に戻せます。Claude Code の応答完了ごとに自動再生成され、開きっぱなしのタブは約30秒ごとに最新化されます。検索・行クリックでプロンプト全文を確認できます:
149
+ グラフの棒をクリックするとその期間に内訳・履歴が連動し、全履歴版では「通算」、期間限定版では「対象期間合計」で埋め込まれた全期間に戻せます。直近版は応答完了ごと、全履歴版は1日1回(または手動 `dashboard --all`)更新されます。Codex CLI のレコードがあれば **Claude / Codex** を絞り込むソースフィルタも表示されます(詳細は [Codex CLI 対応](docs/codex.md))。検索・行クリックでプロンプト全文を確認できます:
143
150
 
144
151
  ![ターン履歴(検索と全文展開)](docs/images/history-expand.png)
145
152
 
@@ -158,6 +165,7 @@ npx ccc-notifier dashboard # 生成してブラウザで開く
158
165
 
159
166
  - [Node.js の用意](docs/installing-node.md) — Node.js 20 が未導入の方へ(mise / 公式インストーラ)
160
167
  - [Windows / WSL2 での導入](docs/windows-wsl2.md) — ネイティブ Windows と WSL2 での手順
168
+ - [Codex CLI 対応](docs/codex.md) — 導入・hook の信頼承認・仕組み・制限
161
169
  - [ダッシュボード](docs/dashboard.md) — 期間の連動・自動更新・履歴の削除
162
170
  - [月予算 / Monthly budget](docs/monthly-budget.md) — 当月の使用率表示
163
171
  - [過去分の取り込み / sweep](docs/sweep.md) — hook 導入前・後から完了した分の回収
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  formatJPY,
4
4
  formatUSD
5
- } from "./chunk-TBFKGFZX.js";
5
+ } from "./chunk-J5QAYTFE.js";
6
6
  import {
7
7
  currentMonthTotals,
8
8
  paths,
9
9
  readConfig
10
- } from "./chunk-IIYMGLV4.js";
10
+ } from "./chunk-26CISNOE.js";
11
11
 
12
12
  // src/budget.ts
13
13
  import { writeFileSync } from "fs";
@@ -40,7 +40,12 @@ function paths() {
40
40
  cacheDir,
41
41
  errorLog: join(home, "error.log"),
42
42
  lastNotifyFile: join(home, "last-notify.json"),
43
- muteFile: join(home, "muted.json")
43
+ muteFile: join(home, "muted.json"),
44
+ recentDashboardFile: join(home, "report.html"),
45
+ fullDashboardFile: join(home, "report-all.html"),
46
+ dashboardFullStateFile: join(cacheDir, "dashboard-full-state.json"),
47
+ dataLockDir: join(cacheDir, "data.lock"),
48
+ dataReclaimDir: join(cacheDir, "data.lock.reclaim")
44
49
  };
45
50
  }
46
51
  function isPlainObject(v) {
@@ -88,7 +93,10 @@ function mergeConfig(partial) {
88
93
  result.dashboard.autoReloadSec = partial.dashboard.autoReloadSec;
89
94
  }
90
95
  if ("days" in partial.dashboard) {
91
- result.dashboard.days = partial.dashboard.days;
96
+ const days = partial.dashboard.days;
97
+ if (typeof days === "number" && Number.isFinite(days) && Number.isInteger(days) && days > 0) {
98
+ result.dashboard.days = days;
99
+ }
92
100
  }
93
101
  }
94
102
  return result;
@@ -170,7 +178,7 @@ function loadCursor(transcriptPath) {
170
178
  }
171
179
  function sanitizeCursor(raw) {
172
180
  if (!isPlainObject(raw)) return null;
173
- const { offset, lastUuid, lastTs, seenMessageKeys } = raw;
181
+ const { offset, lastUuid, lastTs, seenMessageKeys, codexTotals } = raw;
174
182
  if (typeof offset !== "number" || !Number.isFinite(offset)) return null;
175
183
  if (lastUuid !== null && typeof lastUuid !== "string") return null;
176
184
  if (lastTs !== null && typeof lastTs !== "string") return null;
@@ -180,7 +188,14 @@ function sanitizeCursor(raw) {
180
188
  if (typeof key !== "string") return null;
181
189
  keys.push(key);
182
190
  }
183
- return { offset, lastUuid, lastTs, seenMessageKeys: keys };
191
+ const cursor = { offset, lastUuid, lastTs, seenMessageKeys: keys };
192
+ if (isPlainObject(codexTotals)) {
193
+ const { input, cached, output } = codexTotals;
194
+ if (typeof input === "number" && Number.isFinite(input) && input >= 0 && typeof cached === "number" && Number.isFinite(cached) && cached >= 0 && typeof output === "number" && Number.isFinite(output) && output >= 0) {
195
+ cursor.codexTotals = { input, cached, output };
196
+ }
197
+ }
198
+ return cursor;
184
199
  }
185
200
  function saveCursor(transcriptPath, c) {
186
201
  const p = paths();