diff --git a/AGENTS.md b/AGENTS.md index df07a85..0febde9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,16 @@ # AGENTS.md Guide for agentic coding assistants working in `openusage-mono`. +## 0) Contributor Operating Contract +- Operate as a core contributor to this project, not a code generator. +- Behave like a high-signal senior engineer: understand context first, then change code once. +- Aim to get it right the first time: read nearby code, follow local patterns, and run relevant checks before finishing. +- Protect reliability: avoid speculative refactors, avoid hidden behavior changes, and call out release impact clearly. +- Prefer explicit tradeoffs in PR notes when behavior or release flow changes. +- Default execution behavior: choose the best reasonable path and execute it; do not offer distracting false-choice options. +- Only present alternatives when they are materially different and useful; put the recommended option first with a short rationale. +- Do not suggest reverting active user-requested feature work unless the user explicitly asks to discard it. + ## 1) Repo Overview - Package manager: `bun` - Monorepo runner: `turbo` @@ -73,14 +83,42 @@ Guide for agentic coding assistants working in `openusage-mono`. When Vercel Production Branch is switched to `dev`, step 6 is no longer required for web deploys (but keep `dev -> main` for stable release promotion/tagging discipline). -## 6) Release Version Sync +## 6) Branch Isolation and Feature Gating +- Use branch purpose prefixes and keep one concern per branch: + - `feature/...` new behavior + - `fix/...` bug fixes + - `chore/...` tooling/docs/refactors +- Canonical release gating model: + - Merge feature PRs into `dev` normally. + - For selective production release, create a branch from `main` and `cherry-pick` only approved commit(s) from `dev`. + - Open PR from that release branch into `main`. + - Treat this cherry-pick step as the feature gate for production. +- To make cherry-picking reliable: + - Keep one shippable concern per PR/commit. + - Avoid mixing unrelated changes in the same commit. + - Prefer commit structure that can be promoted independently. +- After selective promotion: + - If `main` got commits not yet present in `dev`, forward-port them to `dev` to keep branches aligned. +- For urgent production fixes while `dev` has risky work: + - Branch from `main` (`fix/hotfix-...`), merge to `main`, then forward-port to `dev`. +- Prefer git-based isolation over runtime/build flags for rollout control: + - Keep one shippable concern per branch and avoid stacking unrelated work. + - Validate experiments on feature branches via preview deploys; do not merge until ready. + - Merge/cherry-pick those same commits back to `dev` to keep history aligned. + - If a merged change is not ready for production, revert it on `main` (do not rewrite history). +- Release-channel guidance: + - Stable should map to stable GitHub releases. + - Beta/experimental should map to prereleases or explicitly gated UI paths. + - If both should coexist, render both options (stable + beta) instead of replacing one with the other. + +## 7) Release Version Sync - Before stable tagging run: `bun run release:version 0.6.1` - This updates: - `packages/tauri-src/package.json` - `packages/tauri-src/src-tauri/tauri.conf.json` - `packages/tauri-src/src-tauri/Cargo.toml` -## 7) Code Style Expectations +## 8) Code Style Expectations ### General - Keep changes minimal and focused. - Prefer readable, explicit code over clever abstractions. @@ -123,17 +161,17 @@ When Vercel Production Branch is switched to `dev`, step 6 is no longer required - Prefer behavior-focused tests over implementation details. - Add regression tests for bug fixes when practical. -## 8) Important Gotchas +## 9) Important Gotchas - Tauri updater pubkey must be base64-encoded minisign public key payload in `packages/tauri-src/src-tauri/tauri.conf.json` at `plugins.updater.pubkey`. - Using raw `RWS...` key text fails release builds (`failed to decode pubkey`). - Keep `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` in GitHub secrets. - Landing download CTA logic is in `apps/web/src/routes/index.tsx`; prefer direct installer links over generic releases pages. -## 9) Cursor/Copilot Rules +## 10) Cursor/Copilot Rules - No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` exist currently. - If these files are added later, treat them as authoritative and update this file. -## 10) Agent Completion Checklist +## 11) Agent Completion Checklist - Run relevant build/typecheck/test commands for touched areas. - Prefer single-test runs first, then broader suites as needed. - Confirm branch/release assumptions (`dev` vs `main`) before workflow edits. diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 0cb2e35..d8c3944 100644 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -13,11 +13,13 @@ const PRODUCTION_BODY_CLASS = const PRODUCTION_STYLESHEET_URL = "https://www.openusage.ai/_next/static/chunks/18141af1dfe18c48.css?dpl=dpl_FEcNUMfudsUjMFbSH2z2Gkhx1iG7"; const DEFAULT_RELEASE_REPOSITORY = "Noisemaker111/openusage-mono"; +const LEGACY_RELEASE_REPOSITORY = "robinebers/openusage"; const RELEASE_REPOSITORY = import.meta.env.VITE_RELEASE_REPOSITORY && import.meta.env.VITE_RELEASE_REPOSITORY.trim().length > 0 ? import.meta.env.VITE_RELEASE_REPOSITORY.trim() : DEFAULT_RELEASE_REPOSITORY; const DEFAULT_GITHUB_REPOSITORY_URL = `https://github.com/${DEFAULT_RELEASE_REPOSITORY}`; +const LEGACY_GITHUB_REPOSITORY_URL = `https://github.com/${LEGACY_RELEASE_REPOSITORY}`; const RAW_RELEASE_CHANNEL = import.meta.env.VITE_RELEASE_CHANNEL; const RELEASE_CHANNEL = RAW_RELEASE_CHANNEL === "dev" @@ -37,6 +39,7 @@ const productionBodyHtml = openUsageBodyHtmlRaw.replace(/]*>[\s\S]*?<\ type Platform = "macos" | "windows" | "linux" | "other"; type Architecture = "arm64" | "x64"; type ReleaseChannel = "stable" | "dev"; +type ReleaseTrack = "stable" | "beta"; interface ReleaseAsset { name: string; @@ -55,7 +58,7 @@ interface DownloadOption { subtitle: string; href: string; available: boolean; - releaseTrack: "stable" | "beta"; + releaseTrack: ReleaseTrack; comingSoon: boolean; } @@ -333,68 +336,60 @@ function extractUpdaterFallbackUrls(payload: unknown): DownloadFallbackUrls | nu }; } -function extractReleaseFallbackUrls(releases: ReadonlyArray): DownloadFallbackUrls | null { - const macArmUrl = findAssetUrlAcrossReleases( - releases, - (name) => name.endsWith("aarch64.dmg") || name.endsWith("arm64.dmg"), - ); - const macIntelUrl = findAssetUrlAcrossReleases( - releases, - (name) => name.endsWith("x64.dmg") || name.endsWith("intel.dmg"), - ); - const windowsUrl = - findAssetUrlAcrossReleases( - releases, - (name) => - (name.endsWith(".exe") || name.endsWith(".msi")) && - (name.includes("x64") || name.includes("amd64") || name.includes("windows")), - ) ?? findAssetUrlAcrossReleases(releases, (name) => name.endsWith(".exe") || name.endsWith(".msi") || name.includes("windows")); - const linuxUrl = findAssetUrlAcrossReleases( - releases, - (name) => - name.endsWith(".appimage") || - name.endsWith(".deb") || - name.endsWith(".rpm") || - (name.includes("linux") && (name.endsWith(".tar.gz") || name.endsWith(".zip"))), - ); - - if (macArmUrl === null && macIntelUrl === null && windowsUrl === null && linuxUrl === null) { - return null; - } - - return { - macArmUrl, - macIntelUrl, - windowsUrl, - linuxUrl, - }; +function findTrackAssetUrl( + releases: ReadonlyArray, + releaseTrack: ReleaseTrack, + predicate: (normalizedAssetName: string) => boolean, +): string | null { + const trackReleases = + releaseTrack === "stable" ? releases.filter((release) => !release.prerelease) : releases.filter((release) => release.prerelease); + return findAssetUrlAcrossReleases(trackReleases, predicate); } function buildDownloadOptions( - assets: ReadonlyArray, + releases: ReadonlyArray, fallbackUrls: DownloadFallbackUrls | null = null, ): ReadonlyArray { - const macArmUrl = - findAssetUrl(assets, (name) => name.endsWith("aarch64.dmg") || name.endsWith("arm64.dmg")) ?? + const macArmStableUrl = + findTrackAssetUrl(releases, "stable", (name) => name.endsWith("aarch64.dmg") || name.endsWith("arm64.dmg")) ?? fallbackUrls?.macArmUrl ?? null; - const macIntelUrl = - findAssetUrl(assets, (name) => name.endsWith("x64.dmg") || name.endsWith("intel.dmg")) ?? + const macArmBetaUrl = findTrackAssetUrl( + releases, + "beta", + (name) => name.endsWith("aarch64.dmg") || name.endsWith("arm64.dmg"), + ); + + const macIntelStableUrl = + findTrackAssetUrl(releases, "stable", (name) => name.endsWith("x64.dmg") || name.endsWith("intel.dmg")) ?? fallbackUrls?.macIntelUrl ?? null; + const macIntelBetaUrl = findTrackAssetUrl( + releases, + "beta", + (name) => name.endsWith("x64.dmg") || name.endsWith("intel.dmg"), + ); - const windowsUrl = - findAssetUrl( - assets, + const windowsStableUrl = + findTrackAssetUrl( + releases, + "stable", (name) => (name.endsWith(".exe") || name.endsWith(".msi")) && (name.includes("x64") || name.includes("amd64") || name.includes("windows")), ) ?? - findAssetUrl(assets, (name) => name.endsWith(".exe") || name.endsWith(".msi") || name.includes("windows")) ?? + findTrackAssetUrl(releases, "stable", (name) => name.endsWith(".exe") || name.endsWith(".msi") || name.includes("windows")) ?? fallbackUrls?.windowsUrl ?? null; + const windowsBetaUrl = + findTrackAssetUrl( + releases, + "beta", + (name) => (name.endsWith(".exe") || name.endsWith(".msi")) && (name.includes("x64") || name.includes("amd64") || name.includes("windows")), + ) ?? findTrackAssetUrl(releases, "beta", (name) => name.endsWith(".exe") || name.endsWith(".msi") || name.includes("windows")); - const linuxUrl = - findAssetUrl( - assets, + const linuxStableUrl = + findTrackAssetUrl( + releases, + "stable", (name) => name.endsWith(".appimage") || name.endsWith(".deb") || @@ -403,45 +398,90 @@ function buildDownloadOptions( ) ?? fallbackUrls?.linuxUrl ?? null; + const linuxBetaUrl = findTrackAssetUrl( + releases, + "beta", + (name) => + name.endsWith(".appimage") || + name.endsWith(".deb") || + name.endsWith(".rpm") || + (name.includes("linux") && (name.endsWith(".tar.gz") || name.endsWith(".zip"))), + ); const fallbackSectionHref = `#${MORE_DOWNLOADS_SECTION_ID}`; return [ { - id: "macos-apple-silicon", + id: "macos-apple-silicon-stable", title: "macOS (Apple Silicon)", subtitle: "arm64 dmg", - href: macArmUrl ?? fallbackSectionHref, - available: macArmUrl !== null, + href: macArmStableUrl ?? fallbackSectionHref, + available: macArmStableUrl !== null, releaseTrack: "stable", comingSoon: false, }, { - id: "macos-intel", + id: "macos-apple-silicon-beta", + title: "macOS (Apple Silicon)", + subtitle: "arm64 dmg", + href: macArmBetaUrl ?? fallbackSectionHref, + available: macArmBetaUrl !== null, + releaseTrack: "beta", + comingSoon: macArmBetaUrl === null, + }, + { + id: "macos-intel-stable", title: "macOS (Intel)", subtitle: "x64 dmg", - href: macIntelUrl ?? fallbackSectionHref, - available: macIntelUrl !== null, + href: macIntelStableUrl ?? fallbackSectionHref, + available: macIntelStableUrl !== null, releaseTrack: "stable", comingSoon: false, }, { - id: "windows-x64", + id: "macos-intel-beta", + title: "macOS (Intel)", + subtitle: "x64 dmg", + href: macIntelBetaUrl ?? fallbackSectionHref, + available: macIntelBetaUrl !== null, + releaseTrack: "beta", + comingSoon: macIntelBetaUrl === null, + }, + { + id: "windows-x64-stable", title: "Windows", subtitle: "x64 installer", - href: windowsUrl ?? fallbackSectionHref, - available: windowsUrl !== null, + href: windowsStableUrl ?? fallbackSectionHref, + available: windowsStableUrl !== null, + releaseTrack: "stable", + comingSoon: false, + }, + { + id: "windows-x64-beta", + title: "Windows", + subtitle: "x64 installer", + href: windowsBetaUrl ?? fallbackSectionHref, + available: windowsBetaUrl !== null, releaseTrack: "beta", + comingSoon: windowsBetaUrl === null, + }, + { + id: "linux-x64-stable", + title: "Linux", + subtitle: "AppImage / DEB / RPM", + href: linuxStableUrl ?? fallbackSectionHref, + available: linuxStableUrl !== null, + releaseTrack: "stable", comingSoon: false, }, { - id: "linux-x64", + id: "linux-x64-beta", title: "Linux", subtitle: "AppImage / DEB / RPM", - href: linuxUrl ?? fallbackSectionHref, - available: linuxUrl !== null, + href: linuxBetaUrl ?? fallbackSectionHref, + available: linuxBetaUrl !== null, releaseTrack: "beta", - comingSoon: linuxUrl === null, + comingSoon: linuxBetaUrl === null, }, ]; } @@ -462,23 +502,38 @@ function getPrimaryDownloadOption( architecture: Architecture, ): DownloadOption { if (platform === "macos") { - const macOptionId = architecture === "arm64" ? "macos-apple-silicon" : "macos-intel"; - const option = getOptionById(options, macOptionId); - if (option !== null && option.available) { - return option; + const preferredIds = + architecture === "arm64" + ? ["macos-apple-silicon-stable", "macos-apple-silicon-beta"] + : ["macos-intel-stable", "macos-intel-beta"]; + for (const id of preferredIds) { + const option = getOptionById(options, id); + if (option !== null && option.available) { + return option; + } } } if (platform === "windows") { - const option = getOptionById(options, "windows-x64"); - if (option !== null && option.available) { - return option; + for (const id of ["windows-x64-stable", "windows-x64-beta"]) { + const option = getOptionById(options, id); + if (option !== null && option.available) { + return option; + } } } if (platform === "linux") { - const option = getOptionById(options, "linux-x64"); - if (option !== null && option.available) { + for (const id of ["linux-x64-stable", "linux-x64-beta"]) { + const option = getOptionById(options, id); + if (option !== null && option.available) { + return option; + } + } + } + + for (const option of options) { + if (option.available && option.releaseTrack === "stable") { return option; } } @@ -489,7 +544,7 @@ function getPrimaryDownloadOption( } } - const fallback = getOptionById(options, "macos-apple-silicon"); + const fallback = getOptionById(options, "macos-apple-silicon-stable"); if (fallback !== null) { return fallback; } @@ -505,16 +560,17 @@ function getPrimaryDownloadOption( }; } -function getDownloadTrackLabel(option: DownloadOption): string | null { - if (option.releaseTrack === "stable") { - return null; - } +function getReleaseTrackLabel(track: ReleaseTrack): string { + return track === "stable" ? "Stable" : "Beta"; +} - if (option.comingSoon) { - return "Beta soon"; +function getDownloadTrackLabel(option: DownloadOption): string { + const trackLabel = getReleaseTrackLabel(option.releaseTrack); + if (!option.comingSoon) { + return trackLabel; } - return "Beta"; + return `${trackLabel} soon`; } function getPlatformLabel(platform: Platform): string { @@ -533,6 +589,51 @@ function getPlatformLabel(platform: Platform): string { return "your platform"; } +function rewriteLegacyRepositoryLinks(repositoryUrl: string): void { + for (const anchor of Array.from(document.querySelectorAll("a[href]"))) { + const href = anchor.getAttribute("href"); + if (href === null || !href.startsWith(LEGACY_GITHUB_REPOSITORY_URL)) { + continue; + } + + const suffix = href.slice(LEGACY_GITHUB_REPOSITORY_URL.length); + anchor.setAttribute("href", `${repositoryUrl}${suffix}`); + } +} + +function getPlatformTrackSummary( + options: ReadonlyArray, + prefix: string, + platformName: string, +): string { + const platformOptions = options.filter((option) => option.id.startsWith(prefix)); + if (platformOptions.length === 0) { + return `${platformName} unavailable`; + } + + const labels: Array = []; + const stableAvailable = platformOptions.some((option) => option.releaseTrack === "stable" && option.available); + if (stableAvailable) { + labels.push("Stable"); + } + + const betaAvailable = platformOptions.some((option) => option.releaseTrack === "beta" && option.available); + if (betaAvailable) { + labels.push("Beta"); + } else { + const betaSoon = platformOptions.some((option) => option.releaseTrack === "beta" && option.comingSoon); + if (betaSoon) { + labels.push("Beta soon"); + } + } + + if (labels.length === 0) { + return `${platformName} unavailable`; + } + + return `${platformName} ${labels.join(" + ")}`; +} + function ensureMoreDownloadsAnchor(): void { const ctaSection = Array.from(document.querySelectorAll("section")).find((section) => { const heading = section.querySelector("h2"); @@ -660,15 +761,18 @@ function relocateContributeToHeroMetaRow(): void { badgeContainer.insertBefore(primaryContributeAnchor, badge); } -function updatePrimaryDownloadCtas(primaryOption: DownloadOption, platformLabel: string): void { +function updatePrimaryDownloadCtas( + primaryOption: DownloadOption, + platformLabel: string, + options: ReadonlyArray, +): void { const anchors = Array.from(document.querySelectorAll("a")).filter((anchor) => { const text = anchor.textContent?.trim() ?? ""; return text.startsWith("Download for "); }); const primaryTrackLabel = getDownloadTrackLabel(primaryOption); - const primaryLabel = - primaryTrackLabel === null ? `Download for ${platformLabel}` : `Download for ${platformLabel} (${primaryTrackLabel})`; + const primaryLabel = `Download for ${platformLabel} (${primaryTrackLabel})`; for (const anchor of anchors.slice(0, 2)) { anchor.textContent = primaryLabel; anchor.setAttribute("href", primaryOption.href); @@ -700,10 +804,7 @@ function updatePrimaryDownloadCtas(primaryOption: DownloadOption, platformLabel: }); if (ctaParagraph !== undefined) { - ctaParagraph.textContent = - primaryTrackLabel === null - ? `Download OpenUsage for ${platformLabel}. It is free, and you will never have to guess your limits again.` - : `Download OpenUsage for ${platformLabel} (${primaryTrackLabel}). It is free, and you will never have to guess your limits again.`; + ctaParagraph.textContent = `Download OpenUsage for ${platformLabel} (${primaryTrackLabel}). It is free, and you will never have to guess your limits again.`; } const ctaFootnote = Array.from(document.querySelectorAll("p")).find((paragraph) => { @@ -712,7 +813,10 @@ function updatePrimaryDownloadCtas(primaryOption: DownloadOption, platformLabel: }); if (ctaFootnote !== undefined) { - ctaFootnote.textContent = "macOS - Windows Beta - Linux Beta soon - MIT License"; + const macSummary = getPlatformTrackSummary(options, "macos-", "macOS"); + const windowsSummary = getPlatformTrackSummary(options, "windows-x64", "Windows"); + const linuxSummary = getPlatformTrackSummary(options, "linux-x64", "Linux"); + ctaFootnote.textContent = `${macSummary} - ${windowsSummary} - ${linuxSummary} - MIT License`; } ensureHeroMoreDownloadsAnchor(); @@ -754,7 +858,10 @@ function renderMoreDownloadsSection(options: ReadonlyArray, rele description.className = "text-sm lg:text-base mt-2"; description.style.color = "var(--page-fg-muted)"; const releaseSummary = releaseTag === null ? "Latest release" : `Latest release ${releaseTag}`; - description.textContent = `${releaseSummary}. macOS is available, Windows is Beta, Linux is Beta soon.`; + const macSummary = getPlatformTrackSummary(options, "macos-", "macOS"); + const windowsSummary = getPlatformTrackSummary(options, "windows-x64", "Windows"); + const linuxSummary = getPlatformTrackSummary(options, "linux-x64", "Linux"); + description.textContent = `${releaseSummary}. ${macSummary}, ${windowsSummary}, ${linuxSummary}.`; header.append(title, description); @@ -795,12 +902,8 @@ function renderMoreDownloadsSection(options: ReadonlyArray, rele stageBadge.style.backgroundColor = "rgba(255,255,255,0.08)"; stageBadge.style.color = "var(--page-fg-subtle)"; const trackLabel = getDownloadTrackLabel(option); - if (trackLabel !== null) { - stageBadge.textContent = trackLabel; - cardHeader.append(cardTitle, stageBadge); - } else { - cardHeader.append(cardTitle); - } + stageBadge.textContent = trackLabel; + cardHeader.append(cardTitle, stageBadge); const cardSubtitle = document.createElement("p"); cardSubtitle.className = "text-xs mt-1"; @@ -811,7 +914,7 @@ function renderMoreDownloadsSection(options: ReadonlyArray, rele cardAction.className = "text-xs mt-3"; cardAction.style.color = option.available ? "var(--page-accent)" : "var(--page-fg-muted)"; if (option.available) { - cardAction.textContent = trackLabel === null ? "Download" : `Download ${trackLabel.toLowerCase()}`; + cardAction.textContent = `Download ${trackLabel.toLowerCase()}`; } else { cardAction.textContent = option.comingSoon ? "Coming soon" : "Unavailable"; } @@ -969,7 +1072,7 @@ function applyDownloadUi( const primaryOption = getPrimaryDownloadOption(options, platform, architecture); const platformLabel = getPlatformLabel(platform); - updatePrimaryDownloadCtas(primaryOption, platformLabel); + updatePrimaryDownloadCtas(primaryOption, platformLabel, options); renderMoreDownloadsSection(options, releaseTag); } @@ -1079,6 +1182,7 @@ function HomeComponent() { updateMenuBarClock(); const clockInterval = window.setInterval(updateMenuBarClock, 30_000); const repositoryUrl = GITHUB_REPOSITORY?.url ?? DEFAULT_GITHUB_REPOSITORY_URL; + rewriteLegacyRepositoryLinks(repositoryUrl); updateMenuBarGithubStars(null, repositoryUrl); applyDownloadUi(buildDownloadOptions([]), platform, architecture, null); @@ -1140,8 +1244,7 @@ function HomeComponent() { return; } - const releaseFallbackUrls = extractReleaseFallbackUrls(releases); - applyDownloadUi(buildDownloadOptions(release.assets, releaseFallbackUrls), platform, architecture, release.tag); + applyDownloadUi(buildDownloadOptions(releases), platform, architecture, release.tag); } catch { await applyUpdaterManifestFallback(); } diff --git a/apps/web/src/routes/openusage-body.html b/apps/web/src/routes/openusage-body.html index 37c1c21..dd9eb9e 100644 --- a/apps/web/src/routes/openusage-body.html +++ b/apps/web/src/routes/openusage-body.html @@ -1099,8 +1099,8 @@

+ 8 more - +

Never Get Cut Off by Surprise.

Download OpenUsage for macOS. It's free, and you'll never have to guess your limits again.

Requires macOS 14+ · v0.6.0 · MIT License

@@ -2786,7 +2786,7 @@

-GitHub +GitHub YouTube Twitter Newsletter @@ -2823,4 +2823,4 @@

- \ No newline at end of file + diff --git a/packages/tauri-src/CONTRIBUTING.md b/packages/tauri-src/CONTRIBUTING.md index 78e69c4..b8be6af 100644 --- a/packages/tauri-src/CONTRIBUTING.md +++ b/packages/tauri-src/CONTRIBUTING.md @@ -41,7 +41,7 @@ Each provider is a plugin. See the [Plugin API docs](docs/plugins/api.md) for th 4. Test it locally with `bun tauri dev` 5. Open a PR with screenshots showing it working -You can also [open an issue](https://github.com/robinebers/openusage/issues/new?template=new_provider.yml) to request a provider without building it yourself. +You can also [open an issue](https://github.com/Noisemaker111/openusage-mono/issues/new?template=new_provider.yml) to request a provider without building it yourself. ### Fix a bug @@ -52,7 +52,7 @@ You can also [open an issue](https://github.com/robinebers/openusage/issues/new? ### Request a feature -Don't open a PR for large features without discussing first. [Open an issue](https://github.com/robinebers/openusage/issues/new?template=feature_request.yml) and make your case. +Don't open a PR for large features without discussing first. [Open an issue](https://github.com/Noisemaker111/openusage-mono/issues/new?template=feature_request.yml) and make your case. ## What Gets Accepted @@ -79,4 +79,4 @@ Don't open a PR for large features without discussing first. [Open an issue](htt ## Questions? -Open a [bug report](https://github.com/robinebers/openusage/issues/new?template=bug_report.yml) or [feature request](https://github.com/robinebers/openusage/issues/new?template=feature_request.yml) using the issue templates. +Open a [bug report](https://github.com/Noisemaker111/openusage-mono/issues/new?template=bug_report.yml) or [feature request](https://github.com/Noisemaker111/openusage-mono/issues/new?template=feature_request.yml) using the issue templates. diff --git a/packages/tauri-src/README.md b/packages/tauri-src/README.md index 6b5644a..33df159 100644 --- a/packages/tauri-src/README.md +++ b/packages/tauri-src/README.md @@ -33,12 +33,12 @@ OpenUsage lives in your menu bar and shows you how much of your AI coding subscr ### Maybe Soon -- [Factory / Droid](https://github.com/robinebers/openusage/issues/16) -- [Gemini](https://github.com/robinebers/openusage/issues/13) -- [Vercel AI Gateway](https://github.com/robinebers/openusage/issues/18) +- [Factory / Droid](https://github.com/Noisemaker111/openusage-mono/issues/16) +- [Gemini](https://github.com/Noisemaker111/openusage-mono/issues/13) +- [Vercel AI Gateway](https://github.com/Noisemaker111/openusage-mono/issues/18) Community contributions welcome. -Want a provider that's not listed? [Open an issue.](https://github.com/robinebers/openusage/issues/new) +Want a provider that's not listed? [Open an issue.](https://github.com/Noisemaker111/openusage-mono/issues/new) ## Open Source, Community Driven @@ -60,7 +60,7 @@ See [`docs/release-flow.md`](docs/release-flow.md) for the `dev` -> `main` promo - **Add a provider.** Each one is just a plugin. See the [Plugin API](docs/plugins/api.md). - **Fix a bug.** PRs welcome. Provide before/after screenshots. -- **Request a feature.** [Open an issue](https://github.com/robinebers/openusage/issues/new) and make your case. +- **Request a feature.** [Open an issue](https://github.com/Noisemaker111/openusage-mono/issues/new) and make your case. Keep it simple. No feature creep, no AI-generated commit messages, test your changes. diff --git a/packages/tauri-src/SECURITY.md b/packages/tauri-src/SECURITY.md index 0961ef2..09e7f93 100644 --- a/packages/tauri-src/SECURITY.md +++ b/packages/tauri-src/SECURITY.md @@ -6,7 +6,7 @@ If you find a security vulnerability in OpenUsage, please report it responsibly. ### Preferred: GitHub Security Advisories -1. Go to the [Security Advisories page](https://github.com/robinebers/openusage/security/advisories/new) +1. Go to the [Security Advisories page](https://github.com/Noisemaker111/openusage-mono/security/advisories/new) 2. Click "Report a vulnerability" 3. Fill in the details diff --git a/packages/tauri-src/src/components/about-dialog.tsx b/packages/tauri-src/src/components/about-dialog.tsx index b52f61e..49d663a 100644 --- a/packages/tauri-src/src/components/about-dialog.tsx +++ b/packages/tauri-src/src/components/about-dialog.tsx @@ -84,7 +84,7 @@ export function AboutDialog({ version, onClose }: AboutDialogProps) {

Open source on{" "} - + GitHub

diff --git a/packages/tauri-src/src/components/side-nav.tsx b/packages/tauri-src/src/components/side-nav.tsx index b3b0a02..a2155d2 100644 --- a/packages/tauri-src/src/components/side-nav.tsx +++ b/packages/tauri-src/src/components/side-nav.tsx @@ -110,7 +110,7 @@ export function SideNav({ activeView, onViewChange, plugins }: SideNavProps) { { - openUrl("https://github.com/robinebers/openusage/issues").catch(console.error) + openUrl("https://github.com/Noisemaker111/openusage-mono/issues").catch(console.error) invoke("hide_panel").catch(console.error) }} aria-label="Help" diff --git a/verifaction-todo.md b/verifaction-todo.md new file mode 100644 index 0000000..634bf2f --- /dev/null +++ b/verifaction-todo.md @@ -0,0 +1,99 @@ +# App Verification TODO + +## Why users see warnings now + +- **macOS warning** (`Apple could not verify ... free of malware`) means the app is not fully trusted by Gatekeeper yet (missing notarization and/or stapled ticket). +- **Windows warning** (`Unknown publisher`) means installers are not Authenticode-signed with a trusted code-signing certificate, or the cert has no SmartScreen reputation yet. + +--- + +## 1) macOS trust (Gatekeeper) - Required + +### A. Apple account and certs + +- [ ] Enroll in Apple Developer Program (paid account). +- [ ] Create **Developer ID Application** certificate. +- [ ] Export certificate as `.p12` + password from Keychain. + +### B. Notarization credentials + +- [ ] Create app-specific password for Apple ID. +- [ ] Confirm values for: + - `APPLE_ID` + - `APPLE_PASSWORD` + - `APPLE_TEAM_ID` + - `APPLE_SIGNING_IDENTITY` (example in `packages/tauri-src/.env.example`) + +### C. GitHub Actions secrets + +- [ ] Add signing/notarization secrets in repo settings: + - `APPLE_ID` + - `APPLE_PASSWORD` + - `APPLE_TEAM_ID` + - `APPLE_SIGNING_IDENTITY` + - (if needed by your setup) certificate secrets for CI keychain import (`APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD`). + +### D. Workflow wiring + +- [ ] Ensure `.github/workflows/release-dev.yml` passes Apple signing/notarization envs to `tauri-apps/tauri-action`. +- [ ] Ensure `.github/workflows/release-stable.yml` passes same envs. + +### E. Verify on produced artifact + +- [ ] Confirm app is signed: + - `codesign --verify --deep --strict --verbose=2 OpenUsage.app` +- [ ] Confirm notarization ticket is stapled: + - `xcrun stapler validate OpenUsage.app` +- [ ] Confirm Gatekeeper acceptance: + - `spctl --assess --type execute -vv OpenUsage.app` + +--- + +## 2) Windows trust (SmartScreen + publisher) - Required + +### A. Certificate + +- [ ] Buy code-signing cert (recommended: **EV cert** for fastest SmartScreen trust). +- [ ] Export cert as `.pfx` and keep password. + +### B. GitHub Actions secrets + +- [ ] Add: + - `WINDOWS_CERTIFICATE` (base64-encoded `.pfx`) + - `WINDOWS_CERTIFICATE_PASSWORD` + +### C. Workflow wiring + +- [ ] Update `.github/workflows/release-dev.yml` Windows publish job to include Windows signing envs. +- [ ] Update `.github/workflows/release-stable.yml` Windows publish job similarly. +- [ ] Keep failing the release if Windows signing secrets are missing (do not ship unsigned Windows installers). + +### D. Verify on produced artifact + +- [ ] Verify signature in PowerShell: + - `Get-AuthenticodeSignature .\OpenUsage_...exe | Format-List` +- [ ] Verify MSI/exe chain with signtool: + - `signtool verify /pa /v OpenUsage_...exe` + +### E. SmartScreen reality check + +- [ ] Expect some warning period with standard cert until reputation builds. +- [ ] EV cert minimizes/avoids the "unknown publisher" experience much faster. + +--- + +## 3) Repo-specific checks to keep + +- [ ] Keep updater signing secrets in place: + - `TAURI_SIGNING_PRIVATE_KEY` + - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` +- [ ] Keep updater pubkey correctly encoded in `packages/tauri-src/src-tauri/tauri.conf.json`. +- [ ] After changing release signing, run a full dev release and stable release dry run to confirm all three targets (mac arm64, mac x64, windows x64) publish successfully. + +--- + +## Done criteria + +- [ ] macOS downloads open without the malware verification block for signed/notarized builds. +- [ ] Windows installer shows your publisher name (not "Unknown Publisher"). +- [ ] `release-dev` and `release-stable` both enforce signing and fail loudly if secrets are missing.