Kiến trúc & các bẫy đã gặp

Luồng dữ liệu, các thành phần, quy tắc cache và những bẫy kỹ thuật đã gặp.

AIUsageCập nhật 23/10/2026
kiến trúc

Trang này dành cho người muốn hiểu hoặc sửa mã: luồng dữ liệu, các thành phần và những bẫy đã gặp.

Luồng dữ liệu

claude.ai · Anthropic Console · chatgpt.com · agy · Google Cloud · ~/.claude · ~/.codex · máy từ xa (SSH)
                              |
             fetch_one.sh <nguồn>   (mỗi nguồn chạy độc lập, song song)
                              |  ghi nguyên tử, có khoá file
                       ~/.ai_usage_cache.json
                        |                     |
              AIUsage.app (SwiftUI)    AIUsageWidget (WidgetKit)

Ứng dụng và widget chỉ đọc cache. Nút làm mới chỉ ghi một file kích hoạt, LaunchAgent lo phần còn lại.

Thành phần

  • fetch_one.sh nhận một nguồn (claude, codex, codexhist, agy, gemini, insights) và gộp kết quả vào cache. Lỗi parser được ghi vào một file log thay vì bị nuốt.
  • ClaudeHelper là ứng dụng nhỏ dùng WKWebView, giữ phiên đăng nhập và in nội dung trang cho script phân tích. Các chế độ: login, fetch, login-aistudio, fetch-aistudio, login-console, fetch-console.
  • remote_insights.py chạy trên máy từ xa, đọc nhật ký phiên theo kiểu tăng dần và trả về JSON nén.
  • Shared.swift được biên dịch vào cả ứng dụng và widget: parser logo, mô hình dữ liệu và logic dự báo dùng chung.

Quy tắc dữ liệu

  • Mỗi nguồn ghi vào khoá riêng của cache, kèm fetched_at. Cờ _fresh báo một lần lấy thật sự thành công; chỉ khi đó đồng hồ chung mới nhích.
  • Lấy dữ liệu lỗi tạm thời thì giữ nguyên số cũ, không ghi đè bằng rỗng.
  • Phiên hết hạn thì giữ số cũ và đặt needs_login để thẻ hiện nút đăng nhập.

Các bẫy đã gặp

  • Cửa sổ nền của helper phải nằm trên màn hình. Cửa sổ ngoài màn hình làm WebKit báo visibilityState=hidden, requestAnimationFrame bị đình, và các trang Next.js như Anthropic Console không render. Helper dùng cửa sổ trong suốt 99% nằm trong màn hình, không nhận chuột.
  • Cookie gắn với bundle id của helper. Chạy tệp thực thi rời ngoài gói .app sẽ không thấy phiên đăng nhập.
  • Trang usage của claude.ai có nút chứa ký tự icon ở cuối chữ, nên biểu thức chính quy không được neo $; văn bản của Console cũng chứa ký tự icon trên dòng riêng giữa nhãn và số.
  • Tiến trình mở rộng của widget sống dai qua nhiều lần deploy. Sau mỗi lần deploy có sửa widget, kết thúc tiến trình AIUsageWidgetExtension để hệ thống chạy bản mới.
  • Build hỏng bị chép nhầm. Không đưa kết quả xcodebuild qua grep mà bỏ qua việc kiểm BUILD SUCCEEDED.
  • Ký ad-hoc trong thư mục đồng bộ bị lỗi "detritus not allowed"; ký trên bản sao ở thư mục tạm.
  • Widget không có NSHomeDirectory() thật vì chạy trong sandbox; đường dẫn tới cache được dựng từ tên người dùng.

Ảnh minh hoạ

Ứng dụng có hai biến môi trường dành cho việc chụp ảnh giới thiệu: AIUSAGE_HOME trỏ cache và script sang một thư mục chứa số liệu mẫu, và AIUSAGE_APPEARANCE (light hoặc dark) ghim giao diện. Nhờ vậy ảnh công khai không bao giờ chứa số liệu thật.