Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ body:
id: environment
attributes:
label: Environment
description: macOS, Apple Silicon/Intel, Xcode, Node and Codex versions
description: macOS, Apple Silicon/Intel, app version, download or source install, Codex version (Xcode/Node versions only for source installs)
validations:
required: true
- type: textarea
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ jobs:
- run: node scripts/check-release.mjs
native:
runs-on: macos-15
timeout-minutes: 15
timeout-minutes: 30
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- run: bash scripts/test-native.sh
- run: bash scripts/build.sh
- run: bash scripts/package-release.sh
- name: Test the shipped Node runtime
run: '"build/Codex Pulse.app/Contents/Resources/runtime/node" --test tests/*.test.mjs'
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release preview

on:
push:
tags: ['v*']
workflow_dispatch:

permissions:
contents: read

jobs:
release:
runs-on: macos-15
timeout-minutes: 30
permissions:
contents: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- run: npm test
- run: bash scripts/test-native.sh
- run: node scripts/check-release.mjs
- name: Validate release tag
run: |
[ "$GITHUB_REF_TYPE" = tag ] || { echo 'Run this workflow from a tag.' >&2; exit 1; }
TAG="$GITHUB_REF_NAME"
VERSION="$(node -p "require('./package.json').version")"
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Invalid release tag: $TAG" >&2; exit 1; }
[ "$TAG" = "v$VERSION" ] || { echo "Tag $TAG does not match package version $VERSION" >&2; exit 1; }
[ "$TAG" = "$(git describe --exact-match --tags HEAD)" ] || { echo 'Workflow ref does not point at the checked-out tag.' >&2; exit 1; }
- run: bash scripts/package-release.sh
- name: Test the shipped Node runtime
run: '"build/Codex Pulse.app/Contents/Resources/runtime/node" --test tests/*.test.mjs'
- name: Create draft prerelease
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="$GITHUB_REF_NAME"
VERSION="$(node -p "require('./package.json').version")"
gh release create "$TAG" dist/Codex-Pulse-"$VERSION"-universal-unsigned.dmg dist/Codex-Pulse-"$VERSION"-universal-unsigned.zip dist/SHA256SUMS.txt --draft --prerelease --title "Codex Pulse $VERSION" --notes-file "docs/releases/v$VERSION.md"
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.1.1 — downloadable unnotarized prerelease

- Universal macOS 14+ DMG and ZIP distribution with bundled Node.js and local bridge.
- GUI actions: **Enable/repair bridge**, **Remove local bridge**, and explicit reset radar enable/disable controls (radar off by default).
- Requires Codex CLI or desktop app already signed in with a ChatGPT account.
- Ad-hoc signed and unnotarized; prerelease only. Intel execution and clean-machine installation remain unverified.

## 0.1.0 — source preview

- Native small and medium macOS widgets, with English and Simplified Chinese.
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ Before opening a pull request:
Please preserve the read-only boundary, explicit missing/stale states, optional social feed, and small dependency surface. No credential scraping, automatic quota resets, account switching, or fake engagement features.

Good first contributions: clean-machine setup feedback, Intel build/install verification, accessible text sizing, and additional languages. Contributions are licensed under the repository's MIT license.

## Release changes

For packaging, signing and draft prereleases, follow [RELEASING.md](docs/RELEASING.md). CI tests the bundled runtime and verifies ZIP/DMG contents as well as the native build. Keep unsigned preview limitations explicit; do not claim clean-Mac or Intel execution results without recording evidence.
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

A native macOS desktop widget for **5-hour and weekly usage**, with separate reset countdowns — days included.

[English](README.md) · [简体中文](README.zh-CN.md) · [Install](#quick-start) · [Security](SECURITY.md) · [Contribute](CONTRIBUTING.md)
[Download v0.1.1](https://github.com/18637168668a-cpu/codex-pulse/releases/tag/v0.1.1) · [简体中文](README.zh-CN.md) · [Install](#quick-start) · [Security](SECURITY.md) · [Contribute](CONTRIBUTING.md)

![Codex Pulse native widget showing two usage percentages and separate reset countdowns](docs/images/hero-en.png)

Expand All @@ -21,6 +21,7 @@ I wanted to glance at my desktop and know two things: **how much have I used, an
Codex Pulse keeps the answer small, readable, and on the desktop.

- **Used, not remaining:** 5-hour and weekly percentages side by side.
- **CP menu meter:** C represents 5-hour usage and P represents weekly usage; each shifts from green at 0% through yellow to red at 100%. Missing or cached values are gray.
- **Two reset clocks:** each quota has its own countdown, including days.
- **Native SwiftUI + WidgetKit:** small and medium widgets; English and Simplified Chinese follow your system language.
- **Read-only local bridge:** Node.js built-ins only. No Electron, web development server, telemetry, or extra API key.
Expand All @@ -31,14 +32,23 @@ If this saves you a few trips to the usage page, a ⭐ helps other Codex users f

## Quick start

**v0.1 is a source-build preview, not a signed/notarized app download.**
**Download, open, enable the bridge, add your widget. No Xcode or Node installation needed.**

You need:
[Download v0.1.1 — DMG or ZIP](https://github.com/18637168668a-cpu/codex-pulse/releases/tag/v0.1.1)

- macOS 14 or later. Apple Silicon has been locally tested; Intel installation is not yet verified.
- Full **Xcode 15+**, selected as the active developer directory. Command Line Tools alone are insufficient. The release build was tested with Xcode 26.4.
- **Node.js 22+** on your PATH.
- **Codex CLI or desktop app**, already signed in with a ChatGPT account whose quota windows are available. API-key-only login is not the intended mode.
> **Unnotarized prerelease:** the `unsigned` downloads have an ad-hoc signature, not an Apple Developer ID signature. macOS may block first launch. Only approve this specific app if you trust the download; see [first-launch guidance](docs/INSTALL.md). Intel and clean-Mac installation still need validation.

1. On **macOS 14+**, install Codex and sign in with your **ChatGPT account**. API-key-only login is not supported for quota display.
2. Download the DMG/ZIP and move **Codex Pulse.app** into **Applications** (or your user Applications folder).
3. Open Codex Pulse and click **Enable/repair bridge**. It registers a local service that starts at login; no sudo or API key is needed.
4. **Right-click desktop → Edit Widgets → Codex Pulse → add small or medium.** You can close the companion app afterwards.

The app bundles the local bridge and Node runtime. Setup only runs when you click the button. The companion app runs outside App Sandbox for service setup; the widget remains sandboxed. [Security details](SECURITY.md).

<details>
<summary>Build from source instead</summary>

Requires full **Xcode 15+** (Command Line Tools alone are insufficient) and **Node.js 22+**. The local build uses an ad-hoc signature.

```bash
git clone https://github.com/18637168668a-cpu/codex-pulse.git
Expand All @@ -47,20 +57,16 @@ bash scripts/install.sh --dry-run
bash scripts/install.sh
```

The installer builds locally, uses an ad-hoc signature, installs into `~/Applications`, and registers a user LaunchAgent for the bridge. No sudo or Apple signing certificate is requested. It refuses to overwrite an unrelated app.
Source installs go to `~/Applications`. For a custom CLI location, set `CODEX_BIN=/absolute/path/to/codex`. Pull and rerun the installer to update a source installation.

Then **right-click the desktop → Edit Widgets → search “Codex Pulse” → add a small or medium widget**. macOS requires this final placement step. You can close the companion app afterwards.
</details>

If Codex is installed elsewhere, pass its executable path:

```bash
CODEX_BIN="/path/to/codex" bash scripts/install.sh
```

To update, pull the repository and rerun the installer. Remove/re-add the widget if macOS keeps an old rendered view. See [installation and troubleshooting](docs/INSTALL.md) for paths, manual operation, and removal.
For downloads, close the app, replace it in the **same Applications folder**, reopen it, and click **Enable/repair bridge**. Use **Remove local bridge** before moving the app to Trash. [Installation, upgrades and troubleshooting](docs/INSTALL.md).

## Optional reset radar

In the app, choose **Enable reset radar** and confirm the third-party request notice. Choose **Disable reset radar** to turn it off. Source installs also support:

```bash
bash scripts/social.sh on
# Later:
Expand Down Expand Up @@ -105,7 +111,7 @@ bash scripts/build.sh # local native app build + signature verification

## Status & roadmap

This is an early, deliberately small release. The local bridge and native build are tested; installation still needs feedback across clean Macs and Codex versions. There is no App Store distribution or notarized binary yet.
This is an early, deliberately small prerelease. The native build architecture is prepared for universal distribution; Intel installation and clean-machine support remain pending validation. There is no App Store distribution or notarized binary.

Next priorities: clean-machine/Intel testing, better installation diagnostics, accessible larger-text layouts, and additional languages. Feature requests and reproducible reports are welcome; please remove account data from screenshots and logs.

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

原生 macOS 桌面小组件:并排展示 **5 小时已用量、本周已用量**,分别显示重置倒计时,超过一天明确显示天数。

[English](README.md) · [安装与排查](docs/INSTALL.md) · [隐私与安全](SECURITY.md)
[下载 v0.1.1](https://github.com/18637168668a-cpu/codex-pulse/releases/tag/v0.1.1) · [English](README.md) · [安装与排查](docs/INSTALL.md) · [隐私与安全](SECURITY.md)

![中文原生组件,两个已用百分比及带天数的重置倒计时](docs/images/medium-zh.png)

Expand All @@ -15,6 +15,7 @@
想知道额度用了多少、什么时候恢复,不应该每次打开一个网页。这个项目来自一个很具体的日常需求:在桌面留一小块地方,把两组额度和两个重置时间说清楚。

- 百分比统一表示**已用量**,不是剩余量。
- 菜单栏的 **C/P** 分别代表 5 小时和本周已用量:0% 为绿色,中间过渡为黄色,100% 为红色;无数据或缓存数据显示灰色。
- 原生 WidgetKit,小号和中号,跟随系统显示中英文。
- 本机只读服务,Node 内置模块即可,不需要 Electron、网页开发服务器或额外 API Key。
- 无数据显示 `—`,旧数据显示“缓存”,不使用演示数字冒充实时数据。
Expand All @@ -24,9 +25,23 @@

## 安装

**v0.1 是源码构建预览版,不是签名公证好的即装即用 App。**
**下载 → 打开 → 启用本机服务 → 添加组件。无需另装 Xcode 或 Node。**

需要 macOS 14+、完整 Xcode 15+、Node.js 22+,以及已通过 ChatGPT 账号登录的 Codex CLI 或桌面 App。只安装 Command Line Tools 不够。已在 Apple Silicon / Xcode 26.4 本机验证构建;Intel 和其他干净机器仍需要测试反馈。
[下载 v0.1.1(DMG / ZIP)](https://github.com/18637168668a-cpu/codex-pulse/releases/tag/v0.1.1)

> **这是未公证的预发布版。** 文件名中的 `unsigned` 表示没有 Apple Developer ID 签名,仅有临时签名。macOS 可能阻止首次打开;仅在信任下载来源时按[安装指南](docs/INSTALL.md)批准此应用。Intel 实机和全新 Mac 安装仍待验证。

1. 使用 **macOS 14+**,先在 Codex 中以 **ChatGPT 账号**登录;仅 API Key 登录无法用于本组件的额度显示。
2. 下载 DMG/ZIP,将 **Codex Pulse.app** 放入“应用程序”(`/Applications` 或 `~/Applications`)。
3. 打开 App,点击 **启用/修复本机服务**。服务会在登录时启动,无需 sudo 或额外 API Key。
4. **右键桌面 → 编辑小组件 → Codex Pulse → 添加小号或中号**。之后可关闭主应用。

内置 Node 运行时和只读服务。仅在点击按钮后配置服务;主应用因需配置登录服务不使用沙盒,WidgetKit 扩展仍保留沙盒。[隐私与安全](SECURITY.md)。

<details>
<summary>从源码安装</summary>

需要完整 **Xcode 15+** 和 **Node.js 22+**;只有 Command Line Tools 不够。

```bash
git clone https://github.com/18637168668a-cpu/codex-pulse.git
Expand All @@ -35,20 +50,24 @@ bash scripts/install.sh --dry-run
bash scripts/install.sh
```

脚本本机构建并临时签名,安装到 `~/Applications`,注册登录时启动的本机后台服务。无需 sudo,不要求你的 Apple 开发证书,也不会覆盖不同标识的同名 App。
源码安装到 `~/Applications`,使用临时签名,不要求 Apple 开发证书。更新时拉取代码并重新运行安装脚本。

</details>

最后:**右键桌面 → 编辑小组件 → 搜索 Codex Pulse → 添加小号或中号**。位置需要通过系统界面添加,之后可以关闭主应用
下载版更新:先关闭 App,在**原应用目录**替换后重新打开并点击 **启用/修复本机服务**。卸载时先点击 **移除本机服务**,再将 App 移到废纸篓,最后手动移除桌面组件

### 可选雷达

在 App 中点击 **开启重置雷达**并确认第三方请求提示,可随时点击 **关闭重置雷达**。源码版也支持:

```bash
bash scripts/social.sh on
bash scripts/social.sh off
```

开启后访问第三方公开源 [codex-reset.com](https://codex-reset.com),它会收到你的 IP 等普通 HTTPS 请求信息,但不会收到用量、登录凭证或聊天。只分析最近七天、链接指向 @thsottiaux 的动态。规则可能误判、漏报,第三方源也可能延迟或停止;所有判断均不应视为官方确认。

### 卸载
### 源码版卸载

```bash
bash scripts/uninstall.sh
Expand Down
4 changes: 3 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ The bridge binds to IPv4 loopback and checks Host, Origin and cross-site fetch m

Optional social radar is off by default. When enabled, it contacts `https://codex-reset.com/api/feed`; that provider receives the usual network metadata. No account data is sent. Feed content is untrusted plain text, not executable instructions. Only recent HTTPS links to the intended X account are accepted. Classification is heuristic and may be incorrect. Clicking a source link opens X in your browser.

Local builds use ad-hoc signatures, not Apple notarization. The installer does not disable Gatekeeper, change global security settings, install system-wide services, or request sudo. Inspect scripts before running them.
The companion app runs outside App Sandbox so an explicit setup button can register a user LaunchAgent and launch the local bridge. The WidgetKit extension remains sandboxed with network-client access only. Setup does not run automatically on first launch. The bundled Node runtime is downloaded from nodejs.org at build time, checked against pinned SHA-256 digests, and shipped with its license; no runtime is downloaded during installation.

Local builds and unsigned preview downloads use ad-hoc signatures, not Apple notarization. Downloaded artifacts include SHA-256 checksums for integrity, not proof of publisher identity. The installer does not disable Gatekeeper, change global security settings, install system-wide services, or request sudo. Inspect scripts before running them.

## Reporting

Expand Down
5 changes: 4 additions & 1 deletion bridge/codex.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';
import { existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';

export function codexBinary() {
if (process.env.CODEX_BIN) return process.env.CODEX_BIN;
return [
'/Applications/Codex.app/Contents/Resources/codex',
'/Applications/ChatGPT.app/Contents/Resources/codex',
join(homedir(), 'Applications/Codex.app/Contents/Resources/codex'),
].find(existsSync) || 'codex';
}

Expand Down Expand Up @@ -48,7 +51,7 @@ export class CodexBridge {
child.once('error', failed);
child.once('exit', failed);
this.ready = this.send('initialize', {
clientInfo: { name: 'codex-pulse', title: 'Codex Pulse', version: '0.1.0' },
clientInfo: { name: 'codex-pulse', title: 'Codex Pulse', version: '0.1.1' },
}).then(() => {
child.stdin.write(JSON.stringify({ method: 'initialized' }) + '\n');
}).catch(error => { this.stop(); throw error; });
Expand Down
2 changes: 1 addition & 1 deletion bridge/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function createPulseServer({ bridge = new CodexBridge(), social = false,
if (request.method !== 'GET') return json(405, { error: 'Read-only service.' });
let path;
try { path = new URL(request.url, 'http://localhost').pathname; } catch { return json(400, { error: 'Invalid request.' }); }
if (path === '/' || path === '/health') return json(200, { app: 'codex-pulse', version: '0.1.0', readOnly: true, socialEnabled: social });
if (path === '/' || path === '/health') return json(200, { app: 'codex-pulse', version: '0.1.1', readOnly: true, socialEnabled: social });
try {
if (path === '/api/local/usage') return json(200, await cached('usage', 30000, async () => normalizeUsage(await bridge.rateLimits())));
if (path === '/api/local/tibo') return json(200, social
Expand Down
2 changes: 1 addition & 1 deletion bridge/social.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function filterSignals(data, now = Date.now()) {

export async function fetchSocial() {
const response = await fetch(FEED_URL, {
headers: { Accept: 'application/json', 'User-Agent': 'Codex-Pulse/0.1.0' },
headers: { Accept: 'application/json', 'User-Agent': 'Codex-Pulse/0.1.1' },
signal: AbortSignal.timeout(10000), redirect: 'error',
});
if (!response.ok) throw new Error('Public feed unavailable.');
Expand Down
Loading
Loading