Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.build
.git
.swiftpm
38 changes: 38 additions & 0 deletions Dockerfile.linux
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1

FROM swift:6.0-jammy AS builder

ARG UBUNTU_PORTS_MIRROR=http://ports.ubuntu.com/ubuntu-ports
RUN sed -i "s@http://ports.ubuntu.com/ubuntu-ports@${UBUNTU_PORTS_MIRROR}@g" /etc/apt/sources.list \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
libgtk-3-dev \
libsqlite3-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY . .
RUN swift build -c release -Xswiftc -static-stdlib

FROM builder AS tester
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl \
xvfb \
&& rm -rf /var/lib/apt/lists/*

FROM ubuntu:22.04

ARG UBUNTU_PORTS_MIRROR=http://ports.ubuntu.com/ubuntu-ports
RUN runtime_mirror="$(printf '%s' "${UBUNTU_PORTS_MIRROR}" | sed 's@^https:@http:@')" \
&& sed -i "s@http://ports.ubuntu.com/ubuntu-ports@${runtime_mirror}@g" /etc/apt/sources.list \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \
libcurl4 \
libgtk-3-0 \
libsqlite3-0 \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /src/.build/release/TokenClock /usr/local/bin/tokenclock-gui
ENTRYPOINT ["/usr/local/bin/tokenclock-gui"]
74 changes: 74 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,89 @@
// swift-tools-version: 6.0
import PackageDescription

#if os(Linux)
let linuxSources = [
"Config/AppConfig.swift",
"Config/SettingsKeys.swift",
"L10n.swift",
"Models/TokenUsage.swift",
"Services/AiderUsageService.swift",
"Services/AntigravityUsageService.swift",
"Services/AppPaths.swift",
"Services/ClaudeCodeUsageService.swift",
"Services/ClineUsageService.swift",
"Services/CodexUsageService.swift",
"Services/ContinueUsageService.swift",
"Services/CopilotUsageService.swift",
"Services/CursorAgentUsageService.swift",
"Services/GeminiUsageService.swift",
"Services/GrokUsageService.swift",
"Services/HermesUsageService.swift",
"Services/HistoryStore.swift",
"Services/MockUsageService.swift",
"Services/ModelEmoji.swift",
"Services/ModelNormalizer.swift",
"Services/OpenClawUsageService.swift",
"Services/OpenCodeUsageService.swift",
"Services/PathConfig.swift",
"Services/PathDetector.swift",
"Services/QwenCodeUsageService.swift",
"Services/UsageAggregator.swift",
"Services/UsageServiceProtocol.swift",
"Linux/LinuxAPIServer.swift",
"Linux/LinuxApp.swift",
"Linux/LinuxMain.swift",
"Linux/LinuxUsageModel.swift",
]

let package = Package(
name: "TokenClock",
targets: [
.systemLibrary(
name: "CGtk",
pkgConfig: "gtk+-3.0",
providers: [.apt(["libgtk-3-dev"])]
),
.systemLibrary(
name: "CSQLite",
pkgConfig: "sqlite3",
providers: [.apt(["libsqlite3-dev"])]
),
.executableTarget(
name: "TokenClock",
dependencies: ["CGtk", "CSQLite"],
path: "Sources/TokenClock",
exclude: [
"AppDelegate.swift",
"FloatingPanel.swift",
"Models/ClockFaceTheme.swift",
"Models/ClockSize.swift",
"Models/CustomThemeConfig.swift",
"Resources",
"Services/LaunchAgentHelper.swift",
"Services/UsageAPIServer.swift",
"Services/WeatherService.swift",
"ViewModel.swift",
"Views",
"main.swift",
],
sources: linuxSources,
swiftSettings: [.unsafeFlags(["-parse-as-library"])]
),
]
)
#else
let package = Package(
name: "TokenClock",
platforms: [.macOS(.v12)], // Monterey+ —— normal 变体最低支持 macOS 12(glass 仍只在 main/.v26)
targets: [
.executableTarget(
name: "TokenClock",
path: "Sources/TokenClock",
exclude: ["Linux"],
resources: [.copy("Resources/glass_disc.png")],
swiftSettings: [.unsafeFlags(["-parse-as-library"])]
)
]
)
#endif
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[![macOS 12+](https://img.shields.io/badge/macOS-12%2B-000000?style=for-the-badge&logo=apple&logoColor=white)](https://www.apple.com/macos/)
[![macOS 26+](https://img.shields.io/badge/macOS%2026-Liquid%20Glass-00B0F0?style=for-the-badge)](https://developer.apple.com/macos/)
[![Linux normal](https://img.shields.io/badge/Linux-normal-FCC624?style=for-the-badge&logo=linux&logoColor=black)](#linux-normal-build)

[![Swift 6](https://img.shields.io/static/v1?label=Swift&message=6&color=F05138&logo=swift&logoColor=white)](https://www.swift.org/)
[![SwiftUI](https://img.shields.io/static/v1?label=UI&message=SwiftUI%20%2B%20AppKit&color=blueviolet)](https://developer.apple.com/xcode/swiftui/)
Expand Down Expand Up @@ -97,7 +98,7 @@ It's built with the macOS 26 **Liquid Glass** material for a crystal clock disc,
- Ships in **two variants** — `main` (macOS 26) and `normal` (macOS 12) — and the `tokenclock` CLI picks the right one for your OS automatically.

### 📦 One-line install & CLI
- The normal build runs on **macOS 12+**, the Liquid Glass build on **macOS 26+**. The one-liner picks the right variant for your OS automatically, and the lightweight `tokenclock` CLI handles start / stop / switch / diagnose. See the [install guide](https://github.com/Neo-Isshin/TokenClock).
- The normal build runs on **macOS 12+ and Linux**, while the Liquid Glass build remains macOS 26+ only. The lightweight `tokenclock` CLI handles start / stop / diagnose on both platforms.

### 🎨 Multiple faces & thoughtful design
- 6 built-in faces (Classic / Midnight / Luxe / Gu Feng / Railgun / Sky), each with its own personality.
Expand Down Expand Up @@ -203,13 +204,34 @@ curl -fsSL https://raw.githubusercontent.com/Neo-Isshin/TokenClock/main/cli/inst
./cli/install.sh
```

### Linux normal build

Linux intentionally ships the **normal classic dial only**. It uses GTK3, keeps the existing 14 local usage parsers, stores history under XDG data directories, registers XDG autostart, and exposes the same loopback API on `127.0.0.1:9988`. Liquid Glass, the macOS theme editor, and weather/location UI are not part of the Linux build.

Install [Swift 6](https://www.swift.org/install/linux/) first, then:

```bash
# Ubuntu / Debian build dependencies
sudo apt install git pkg-config libcurl4 libgtk-3-dev libsqlite3-dev

# Linux installer (builds the normal branch from source)
curl -fsSL https://raw.githubusercontent.com/Neo-Isshin/TokenClock/normal/cli/install.sh | bash
```

Or use the reproducible container build:

```bash
docker build -f Dockerfile.linux -t tokenclock-linux .
```

Options: `--normal` / `--glass` (pick the variant) / `--no-start` (don't auto-launch) / `--build-from-source` (force a local build) / `--check` (check only, don't install) / `--debug` (debug build).

> Or build manually from source below.

### Prerequisites
- **macOS 12+** (normal build); **macOS 26+** (Liquid Glass build)
- The precompiled install needs **no toolchain at all**; Swift 6 (Xcode 16+ / Command Line Tools) is only required for `--build-from-source` local builds
- **Linux normal:** Swift 6, GTK3 development headers, SQLite3 development headers, `libcurl4`, and `pkg-config`

### Build & run from source

Expand All @@ -228,6 +250,8 @@ swift build -c release # release

> The `main` branch's `Package.swift` declares `.macOS(.v26)`, so `swift run` produces the Liquid Glass build; the classic (opaque) macOS 12-compatible build lives on the `normal` branch.

> On Linux, clone or select the `normal` branch before running `swift build`; SwiftPM automatically selects the GTK3 target.

> Gotcha: on **macOS 27 with only Command Line Tools** installed (no full Xcode), a bare `swift build` on the `main` branch fails because the 27 SDK macro-expands `@State`; pass `SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk swift build` to fix it (in the 26 SDK `@State` is still a plain property wrapper). The `normal` branch is unaffected.

### Use the `tokenclock` CLI
Expand All @@ -240,7 +264,7 @@ sudo install -m755 cli/tokenclock /usr/local/bin/tokenclock

| Command | Description |
|---------|-------------|
| `tokenclock start [--glass\|--normal] [--force]` | Start the clock; auto-picks by OS (26+ → glass, 12+ → normal); `--force` opens another instance |
| `tokenclock start [--glass\|--normal] [--force]` | Start the clock; auto-picks by OS (macOS 26+ → glass, macOS 12–25/Linux → normal); `--force` opens another instance |
| `tokenclock stop` | Stop all running TokenClock instances |
| `tokenclock restart [--glass\|--normal]` | Restart |
| `tokenclock doctor` | Diagnose: OS version, installed variant paths, running processes, env vars |
Expand Down Expand Up @@ -320,9 +344,9 @@ It returns JSON usage data, handy for external scripts / dashboards. It listens
| | |
|---|---|
| **Language** | Swift 6 (`-parse-as-library`) |
| **UI** | SwiftUI + AppKit (lock-free dual-`NSPanel` architecture) |
| **UI** | macOS: SwiftUI + AppKit; Linux normal: GTK3 + Cairo |
| **Build** | Swift Package Manager (no `.xcodeproj`) |
| **Platforms** | macOS 26 SDK (`main` branch) / macOS 12 SDK (`normal` branch) |
| **Platforms** | macOS 26 SDK (`main`) / macOS 12 and Linux GTK3 (`normal`) |
| **i18n** | Custom `L10n` engine (zh-Hans / zh-Hant / en, no `.xcstrings`) |
| **Size** | ~12,400 lines of Swift |

Expand Down
32 changes: 28 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[![macOS 12+](https://img.shields.io/badge/macOS-12%2B-000000?style=for-the-badge&logo=apple&logoColor=white)](https://www.apple.com/macos/)
[![macOS 26+](https://img.shields.io/badge/macOS%2026-Liquid%20Glass-00B0F0?style=for-the-badge)](https://developer.apple.com/macos/)
[![Linux normal](https://img.shields.io/badge/Linux-normal-FCC624?style=for-the-badge&logo=linux&logoColor=black)](#linux-normal-版)

[![Swift 6](https://img.shields.io/static/v1?label=Swift&message=6&color=F05138&logo=swift&logoColor=white)](https://www.swift.org/)
[![SwiftUI](https://img.shields.io/static/v1?label=UI&message=SwiftUI%20%2B%20AppKit&color=blueviolet)](https://developer.apple.com/xcode/swiftui/)
Expand Down Expand Up @@ -96,7 +97,7 @@ TokenClock 是一个常驻桌面的 **悬浮时钟**(置顶 · 可拖拽 ·
- 提供 `main`(macOS 26)与 `normal`(macOS 12)**双版本**,由 `tokenclock` CLI 按系统版本自动选用。

### 📦 一行安装 & CLI
- `normal` 适用于 **macOS 12+**,液态玻璃适用于 **macOS 26+**。一行安装脚本会根据系统自动选变体,简单的 `tokenclock` CLI 即可启动 / 停止 / 切换 / 诊断。详见 [安装指南](https://github.com/Neo-Isshin/TokenClock)
- `normal` 适用于 **macOS 12+ 与 Linux**,液态玻璃仅适用于 **macOS 26+**。`tokenclock` CLI 可在两个平台启动 / 停止 / 诊断

### 🎨 多表盘 + 美观设计 + 支持深度自定义
- 6 款内置表盘(经典 / 深夜 / 暗金 / 古风 / 超电磁炮 / 天空),风格各异。
Expand Down Expand Up @@ -202,13 +203,34 @@ curl -fsSL https://raw.githubusercontent.com/Neo-Isshin/TokenClock/main/cli/inst
./cli/install.sh
```

### Linux normal 版

Linux 仅适配 **normal 经典不透明表盘**:使用 GTK3,复用现有 14 个本地用量解析器,历史数据按 XDG 目录保存,支持 XDG 登录自启动,并在 `127.0.0.1:9988` 提供同构 API。Linux 版不包含 Liquid Glass、macOS 主题编辑器和天气/定位界面。

先安装 [Swift 6](https://www.swift.org/install/linux/),然后执行:

```bash
# Ubuntu / Debian 构建依赖
sudo apt install git pkg-config libcurl4 libgtk-3-dev libsqlite3-dev

# Linux 安装器(从 normal 分支源码构建)
curl -fsSL https://raw.githubusercontent.com/Neo-Isshin/TokenClock/normal/cli/install.sh | bash
```

也可以使用可复现的容器构建:

```bash
docker build -f Dockerfile.linux -t tokenclock-linux .
```

可选参数:`--normal` / `--glass`(指定变体)/ `--no-start`(装完不自动启动)/ `--build-from-source`(强制本地编译)/ `--check`(仅检查不安装)/ `--debug`(debug 构建)。

> 也可手动从源码构建,见下方。

### 前置要求
- **macOS 12+**(普通版);**macOS 26+**(Liquid Glass 版)
- 预编译安装**无需任何工具链**;仅 `--build-from-source` 本地编译时需要 Swift 6(Xcode 16+ / Command Line Tools)
- **Linux normal**:Swift 6、GTK3/SQLite3 开发头文件、`libcurl4` 与 `pkg-config`

### 从源码构建运行

Expand All @@ -227,6 +249,8 @@ swift build -c release # 发布

> `main` 分支的 `Package.swift` 声明为 `.macOS(.v26)`,`swift run` 直接产出 Liquid Glass 版;兼容 macOS 12 的经典(不透明)版本位于 `normal` 分支。

> Linux 上请克隆或切换到 `normal` 分支再运行 `swift build`;SwiftPM 会自动选择 GTK3 目标。

> 小坑:在 **macOS 27 且只装了 Command Line Tools**(无完整 Xcode)的机器上,`main` 分支裸 `swift build` 会因 27 SDK 把 `@State` 宏化而失败;指定 `SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk swift build` 即可(26 SDK 里 `@State` 仍是普通属性包装器)。`normal` 分支不受影响。

### 使用 `tokenclock` CLI
Expand All @@ -239,7 +263,7 @@ sudo install -m755 cli/tokenclock /usr/local/bin/tokenclock

| 命令 | 说明 |
|------|------|
| `tokenclock start [--glass\|--normal] [--force]` | 启动时钟;未指定版本时 **按系统版本自动选**(26+ → glass,12+ → normal);`--force` 强制另开一份 |
| `tokenclock start [--glass\|--normal] [--force]` | 启动时钟;自动选择(macOS 26+ → glass,macOS 12–25/Linux → normal);`--force` 强制另开一份 |
| `tokenclock stop` | 停止所有运行中的 TokenClock 实例 |
| `tokenclock restart [--glass\|--normal]` | 重启 |
| `tokenclock doctor` | 诊断环境:系统版本、已安装变体路径、运行中的进程、环境变量 |
Expand Down Expand Up @@ -319,9 +343,9 @@ GET http://127.0.0.1:9988/api/history?days=30 # 过去 N 天的日结快照(
| | |
|---|---|
| **语言** | Swift 6(`-parse-as-library`) |
| **UI** | SwiftUI + AppKit(无锁 NSPanel 双窗口架构) |
| **UI** | macOS:SwiftUI + AppKit;Linux normal:GTK3 + Cairo |
| **构建** | Swift Package Manager(无 `.xcodeproj`) |
| **平台** | macOS 26 SDK(`main` 分支)/ macOS 12 SDK(`normal` 分支) |
| **平台** | macOS 26 SDK(`main`)/ macOS 12 与 Linux GTK3(`normal`) |
| **定位** | 自研 `L10n` 引擎(zh-Hans / zh-Hant / en,无 `.xcstrings`) |
| **规模** | 约 12,400 行 Swift |

Expand Down
Loading