Skip to content
Open
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
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main]
branches: [main, dev]

permissions:
contents: read
Expand All @@ -16,14 +18,14 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 actions/checkout@v6 references a non-existent major version

All workflow files were changed from actions/checkout@v4 to actions/checkout@v6. As of February 2026, the latest major version of actions/checkout is v4. There is no v6 tag.

Root Cause and Impact

The actions/checkout action uses semantic versioning with major version tags (v1, v2, v3, v4). The PR bumped all references to @v6 which does not exist. This affects:

  • .github/workflows/ci.yml:21
  • .github/workflows/publish.yml:33
  • .github/workflows/release-dev.yml:24
  • .github/workflows/release-dev.yml:86

Impact: Every workflow job will fail at the checkout step because GitHub Actions cannot resolve the v6 tag. No CI checks, no publishing, and no dev releases will work.

Prompt for agents
Change all occurrences of actions/checkout@v6 back to actions/checkout@v4 across all workflow files:

1. .github/workflows/ci.yml line 21: change to actions/checkout@v4
2. .github/workflows/publish.yml line 33: change to actions/checkout@v4
3. .github/workflows/release-dev.yml line 24: change to actions/checkout@v4
4. .github/workflows/release-dev.yml line 86: change to actions/checkout@v4

As of February 2026, actions/checkout v4 is the latest major version. v6 does not exist.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install Rust toolchain
uses: dtolnay/rust-action@stable
uses: dtolnay/rust-toolchain@v1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 dtolnay/rust-toolchain@v1 is an invalid action ref — no v1 branch/tag exists

All three workflow files (ci.yml, publish.yml, release-dev.yml) changed the Rust toolchain action reference from @stable to @v1. The dtolnay/rust-toolchain action is unique: it uses the git ref (the part after @) as the Rust toolchain channel to install. @stable installs the stable toolchain, @nightly installs nightly, etc. There is no v1 branch or tag in the dtolnay/rust-toolchain repository.

Root Cause and Impact

The dtolnay/rust-toolchain action reads github.action_ref to determine which Rust toolchain to install. When you write uses: dtolnay/rust-toolchain@stable, GitHub resolves the stable branch, and the action installs the stable Rust toolchain. Writing @v1 causes GitHub Actions to attempt to resolve a ref called v1, which does not exist in that repository.

Affected files:

  • ci.yml:28uses: dtolnay/rust-toolchain@v1 (no toolchain input, no rust-toolchain.toml fallback)
  • publish.yml:37uses: dtolnay/rust-toolchain@v1
  • release-dev.yml:94uses: dtolnay/rust-toolchain@v1

Impact: Every workflow that needs Rust will fail at the "Install Rust toolchain" step because the action ref cannot be resolved. This breaks CI, stable publishing, and dev prerelease publishing.

Prompt for agents
Change all occurrences of dtolnay/rust-toolchain@v1 back to dtolnay/rust-toolchain@stable across all three workflow files:

1. .github/workflows/ci.yml line 28: change `uses: dtolnay/rust-toolchain@v1` to `uses: dtolnay/rust-toolchain@stable`
2. .github/workflows/publish.yml line 37: change `uses: dtolnay/rust-toolchain@v1` to `uses: dtolnay/rust-toolchain@stable`
3. .github/workflows/release-dev.yml line 94: change `uses: dtolnay/rust-toolchain@v1` to `uses: dtolnay/rust-toolchain@stable`

The dtolnay/rust-toolchain action uses the git ref as the toolchain specifier. @stable means install stable Rust. There is no v1 tag/branch in that repository.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Install dependencies (Ubuntu only)
if: matrix.os == 'ubuntu-latest'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ jobs:
env:
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@v1
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || 'x86_64-pc-windows-msvc' }}
- uses: swatinem/rust-cache@v2
Expand Down
126 changes: 126 additions & 0 deletions .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Release Dev

on:
push:
branches:
- dev
workflow_dispatch:

permissions:
contents: write

concurrency:
group: release-dev-${{ github.ref }}
cancel-in-progress: false

jobs:
prepare:
name: Prepare dev release
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.meta.outputs.release_tag }}
release_name: ${{ steps.meta.outputs.release_name }}
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- run: bun install --frozen-lockfile

- name: Type-check and build
run: bun run build

- name: Run tests
run: bun run test
Comment on lines +32 to +36

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Dev prerelease workflow missing bundle:plugins step in prepare job causes validation without bundled plugins

The release-dev.yml prepare job runs bun run build and bun run test but does not run bun run bundle:plugins before the build step. The publish job does run bundle:plugins, but the prepare job's build/test may depend on bundled plugins being present.

Detailed Explanation

Looking at src-tauri/tauri.conf.json:7-9, the beforeBuildCommand is bun run bundle:plugins && bun run build. However, the prepare job at release-dev.yml:32-33 runs bun run build directly (which is the Vite/TypeScript build, not the Tauri build), so this may not be an issue for the frontend build alone. The prepare job is primarily for validation (type-check, test, version alignment) and metadata computation, while the actual Tauri build happens in the publish job which does include bundle:plugins. This is a minor inconsistency rather than a blocking bug — the tests may pass without bundled plugins if they don't depend on them at runtime.

Impact: Low — the prepare job validates frontend code and version alignment, not the full Tauri build. But if any test depends on bundled plugin files existing, it would fail.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Validate version alignment
shell: bash
run: |
TAURI_CONF_VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version')
CARGO_VERSION=$(awk -F'"' '/^version =/ { print $2; exit }' ./src-tauri/Cargo.toml)
PKG_VERSION=$(node -p 'require("./package.json").version')

if [[ "$TAURI_CONF_VERSION" != "$CARGO_VERSION" ]]; then
echo "src-tauri/tauri.conf.json version ($TAURI_CONF_VERSION) != Cargo.toml version ($CARGO_VERSION)"
exit 1
fi

if [[ "$TAURI_CONF_VERSION" != "$PKG_VERSION" ]]; then
echo "src-tauri/tauri.conf.json version ($TAURI_CONF_VERSION) != package.json version ($PKG_VERSION)"
exit 1
fi

- name: Compute dev release metadata
id: meta
shell: bash
run: |
APP_VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version')
SHORT_SHA=$(echo "$GITHUB_SHA" | cut -c1-7)
DATE_UTC=$(date -u +%Y%m%d)
RELEASE_TAG="dev-v${APP_VERSION}-${DATE_UTC}-${SHORT_SHA}"
RELEASE_NAME="Dev ${APP_VERSION} (${SHORT_SHA})"

echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "release_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT"

publish:
name: Publish dev binaries (${{ matrix.target }})
needs: prepare
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
target: aarch64-apple-darwin
args: --target aarch64-apple-darwin
- platform: macos-latest
target: x86_64-apple-darwin
args: --target x86_64-apple-darwin
- platform: windows-latest
target: x86_64-pc-windows-msvc
args: --target x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- run: bun install --frozen-lockfile

- uses: dtolnay/rust-toolchain@v1
with:
targets: ${{ matrix.target }}

- uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"

- name: Bundle plugins
run: bun run bundle:plugins

- name: Validate updater signing key
shell: bash
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [[ -z "$TAURI_SIGNING_PRIVATE_KEY" ]]; then
echo "Missing TAURI_SIGNING_PRIVATE_KEY secret."
exit 1
fi

- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ needs.prepare.outputs.release_tag }}
releaseName: ${{ needs.prepare.outputs.release_name }}
releaseDraft: false
prerelease: true
includeUpdaterJson: true
args: ${{ matrix.args }}
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Amp, Cursor, Claude, Codex, and more coming. See your usage at a glance from you

## Download

[**Download the latest release**](https://github.com/robinebers/openusage/releases/latest) (macOS, Apple Silicon & Intel)
[**Download the latest release**](https://github.com/Noisemaker111/openusage-opencode/releases/latest) (macOS, Apple Silicon & Intel, Windows x64)

The app auto-updates. Install once and you're set.

Expand Down Expand Up @@ -48,7 +48,13 @@ I maintain the project as a guide and quality gatekeeper, but this is your app a

Plugins are currently bundled as we build our the API, but soon will be made flexible so you can build and load their own.

**Windows/Linux:** high-priority and on the todo, but I need testers with some time, willing to help out.
**Windows:** supported in this fork.

**Linux:** planned; contributions and testing help are welcome.

## Development and Release Channels

See [`docs/release-flow.md`](docs/release-flow.md) for the `dev` -> `main` promotion workflow and prerelease/stable publishing flow.

### How to Contribute

Expand Down Expand Up @@ -89,4 +95,4 @@ Inspired by [CodexBar](https://github.com/steipete/CodexBar) by [@steipete](http

### Stack

...
...
49 changes: 49 additions & 0 deletions docs/release-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Release Flow

Two-channel delivery flow:

- `dev` branch: prerelease builds for testing and staging
- `main` branch + `v*` tags: stable production releases

## Branch Strategy

1. Feature PRs merge into `dev`.
2. `dev` triggers CI and prerelease desktop binaries (`Release Dev` workflow).
3. Once validated, merge `dev` into `main`.
4. Create and push a semantic tag (`vX.Y.Z`) from `main` to publish stable binaries.

## Workflows

- `ci.yml`: runs checks on pushes and PRs for `main` and `dev`
- `release-dev.yml`: publishes prerelease artifacts from `dev`
- `publish.yml`: publishes stable artifacts from tags

## Version Sync

Keep versions aligned before stable tags:

```bash
bun run release:version 0.6.3
```

This updates:

- `package.json`
- `src-tauri/tauri.conf.json`
- `src-tauri/Cargo.toml`

Then commit, tag, and push:

```bash
git add .
git commit -m "release: prepare v0.6.3"
git tag v0.6.3
git push origin main --follow-tags
```

## Required Secrets

- `TAURI_SIGNING_PRIVATE_KEY`
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`

Optional platform-signing secrets can be added as needed for platform notarization/certificate requirements.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc && vite build",
"build:release": "./scripts/build-release.sh",
"release:version": "node scripts/sync-version.mjs",
"bundle:plugins": "bun copy-bundled.cjs",
"preview": "vite preview",
"test": "vitest",
Expand Down
60 changes: 60 additions & 0 deletions scripts/sync-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";

const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;

function fail(message) {
console.error(message);
process.exit(1);
}

function absolutePath(relativePath) {
return path.resolve(process.cwd(), relativePath);
}

async function updateJsonVersion(filePath, version) {
const source = await readFile(absolutePath(filePath), "utf8");
const parsed = JSON.parse(source);

if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
fail(`Missing version field in ${filePath}`);
}

parsed.version = version;
await writeFile(absolutePath(filePath), `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
}

async function updateCargoVersion(filePath, version) {
const source = await readFile(absolutePath(filePath), "utf8");
const updated = source.replace(/^version\s*=\s*"[^"]+"/m, `version = "${version}"`);

if (updated === source) {
fail(`Could not locate package version in ${filePath}`);
}

await writeFile(absolutePath(filePath), updated, "utf8");
}

async function main() {
const version = process.argv[2]?.trim();

if (!version) {
fail("Usage: bun run release:version <version>");
}

if (!VERSION_PATTERN.test(version)) {
fail(`Invalid version '${version}'. Expected semantic version like 1.2.3 or 1.2.3-beta.1`);
}

await updateJsonVersion("package.json", version);
await updateJsonVersion("src-tauri/tauri.conf.json", version);
await updateCargoVersion("src-tauri/Cargo.toml", version);

console.log(`Synchronized release version to ${version}`);
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
});
4 changes: 2 additions & 2 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDU2Njk4QTU0MDMzMEY5MTkKUldRWitUQURWSXBwVnFTY1FBWitxNlpaQnB5S3RVWW1qWHJ3NlRuZ2p3c2hPVzNTa3BUR0V0SXkK",
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDI1NTcyNzBEOTVBNzY2ODMKUldTRFpxZVZEU2RYSlppL3h1Y2lNblpQWEpHeHNmQ3BESjlxSk0zcmNhdnBKV1NCY25uZC9lRmUK",
"endpoints": [
"https://github.com/robinebers/openusage/releases/latest/download/latest.json"
"https://github.com/Noisemaker111/openusage-opencode/releases/latest/download/latest.json"
]
}
}
Expand Down