Skip to content
Closed
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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,37 @@ jobs:

- name: Run tests
run: bun run test

rust:
name: Rust build & test (Linux)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
- uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
Comment on lines +33 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unpinned actions and checkout credential persistence in this workflow.
rg -n '^\s*-\s*uses:\s*.+@(?!(?:[a-f0-9]{40})$).*' .github/workflows/ci.yml
rg -n -C2 'actions/checkout@' .github/workflows/ci.yml
rg -n 'persist-credentials:\s*false' .github/workflows/ci.yml

Repository: robinebers/openusage

Length of output: 604


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/ci.yml"
echo "== File =="
wc -l "$FILE"

echo
echo "== actions/checkout uses and surrounding lines =="
rg -n -C3 'uses:\s*actions/checkout@' "$FILE" || true

echo
echo "== persist-credentials settings near checkout =="
rg -n -C5 'persist-credentials\s*:\s*false' "$FILE" || true
rg -n -C5 'persist-credentials\s*:' "$FILE" || true

echo
echo "== Other unpinned third-party actions (non-SHA refs) =="
# Print any "uses:" lines with a ref like `@v1/`@v2/@stable/etc (i.e., not a 40-hex commit SHA)
rg -n --pcre2 '^\s*-\s*uses:\s*[^@\s]+@[0-9a-f]{40}$' "$FILE" | cat > /tmp/pinned_shas.txt || true
# Now find uses lines whose ref is NOT a 40-hex SHA
rg -n --pcre2 '^\s*-\s*uses:\s*([^@\s]+)@(?!(?:[0-9a-f]{40})$)[^,\s]+$' "$FILE" || true

echo
echo "== Current checkout blocks (for manual review) =="
python3 - <<'PY'
import re, itertools
path=".github/workflows/ci.yml"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
# Find checkout steps and print from "uses: actions/checkout@" line up to next "- uses:" or next "with:" end heuristically
for i,l in enumerate(lines):
    if "uses: actions/checkout@" in l:
        start=max(0,i-1)
        end=i+1
        # print until next step marker at same indentation level "- " (best-effort)
        indent=len(l)-len(l.lstrip())
        for j in range(i+1,len(lines)):
            if lines[j].lstrip().startswith("- uses:") and (len(lines[j])-len(lines[j].lstrip()))==indent:
                end=j
                break
        print("----")
        for k in range(start,end):
            print(f"{k+1:4d}:{lines[k].rstrip()}")
PY

Repository: robinebers/openusage

Length of output: 1077


🏁 Script executed:

#!/bin/bash
set -euo pipefail
nl -ba .github/workflows/ci.yml | sed -n '1,120p'

Repository: robinebers/openusage

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/ci.yml"

# Show full file with line numbers (small file: 61 lines)
cat -n "$FILE" | sed -n '1,120p'

Repository: robinebers/openusage

Length of output: 1782


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/ci.yml"

python3 - <<'PY'
import re
path = ".github/workflows/ci.yml"
text = open(path, "r", encoding="utf-8").read().splitlines()

uses = []
for i, line in enumerate(text, 1):
    m = re.match(r'^\s*-\s*uses:\s*([^@\s]+)@([^,\s]+)\s*$', line)
    if m:
        uses.append((i, m.group(1), m.group(2)))

unPinned = []
for i, action, ref in uses:
    if not re.fullmatch(r"[0-9a-f]{40}", ref):
        unPinned.append((i, action, ref))

print("Unpinned action refs (expected 40-hex commit SHA):")
for i, action, ref in unPinned:
    print(f"- line {i}: {action}@{ref}")

# Check persist-credentials anywhere
if not any(re.search(r"persist-credentials\s*:\s*false", l) for l in text):
    print("\nMissing: `persist-credentials: false`")

# Check checkout blocks for persist-credentials
checkout_lines = [i for i, action, ref in uses if action == "actions/checkout" ]
for cl in checkout_lines:
    indent = len(text[cl-1]) - len(text[cl-1].lstrip())
    has_persist = False
    for j in range(cl, min(len(text), cl+20)):  # heuristic window
        line = text[j-1]
        if (len(line) - len(line.lstrip())) == indent and line.lstrip().startswith("- uses:") and j != cl:
            break
        if "persist-credentials" in line:
            if re.search(r"persist-credentials\s*:\s*false", line):
                has_persist = True
    print(f"Checkout at line {cl}: persist-credentials: false {'present' if has_persist else 'NOT present'}")
PY

Repository: robinebers/openusage

Length of output: 475


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/ci.yml"
python3 - <<'PY'
import re
text=open(".github/workflows/ci.yml","r",encoding="utf-8").read().splitlines()
uses=[]
for i,l in enumerate(text,1):
    m=re.match(r'^\s*-\s*uses:\s*([^@\s]+)@([^,\s]+)\s*$', l)
    if m: uses.append((i,m.group(1),m.group(2)))
print("Unpinned action refs (expected 40-hex commit SHA):")
for i,a,r in uses:
    if not re.fullmatch(r"[0-9a-f]{40}", r):
        print(f"- line {i}: {a}@{r}")
if not any(re.search(r"persist-credentials\s*:\s*false", l) for l in text):
    print("\nMissing: `persist-credentials: false`")
PY

Repository: robinebers/openusage

Length of output: 355


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/ci.yml"

echo "Unpinned actions (non-40-hex refs):"
python3 - <<'PY'
import re
text=open(".github/workflows/ci.yml","r",encoding="utf-8").read().splitlines()
for i,l in enumerate(text,1):
    m=re.match(r'^\s*-\s*uses:\s*([^@\s]+)@([^,\s]+)\s*$', l)
    if m and not re.fullmatch(r"[0-9a-f]{40}", m.group(2)):
        print(f"line {i}: {m.group(1)}@{m.group(2)}")
PY

echo
echo "Missing persist-credentials:"
rg -n 'persist-credentials\s*:\s*false' "$FILE" || true

Repository: robinebers/openusage

Length of output: 319


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/ci.yml"

python3 - <<'PY'
import re
text=open(".github/workflows/ci.yml","r",encoding="utf-8").read().splitlines()
for i,l in enumerate(text,1):
    m=re.match(r'^\s*-\s*uses:\s*([^@\s]+)@([^,\s]+)\s*$', l)
    if m and not re.fullmatch(r"[0-9a-f]{40}", m.group(2)):
        print(f"Unpinned: line {i}: {m.group(1)}@{m.group(2)}")

if not any(re.search(r"persist-credentials\s*:\s*false", l) for l in text):
    print("Missing: `persist-credentials: false`")
PY

Repository: robinebers/openusage

Length of output: 351


Pin CI workflow actions to commit SHAs and disable checkout credential persistence

  • .github/workflows/ci.yml uses mutable action refs (not commit-SHA pinned): actions/checkout@v4 (lines 15, 33), oven-sh/setup-bun@v2 (lines 17, 52), dtolnay/rust-toolchain@stable (line 35), swatinem/rust-cache@v2 (line 36).
  • Both actions/checkout@v4 steps (lines 15 and 33) omit with: persist-credentials: false.
Suggested hardening patch
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full-commit-sha>
+        with:
+          persist-credentials: false

-      - uses: oven-sh/setup-bun@v2
+      - uses: oven-sh/setup-bun@<full-commit-sha>
         with:
           bun-version: latest
...
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full-commit-sha>
+        with:
+          persist-credentials: false

-      - uses: dtolnay/rust-toolchain@stable
-      - uses: swatinem/rust-cache@v2
+      - uses: dtolnay/rust-toolchain@<full-commit-sha>
+      - uses: swatinem/rust-cache@<full-commit-sha>
         with:
           workspaces: "./src-tauri -> target"
...
-      - uses: oven-sh/setup-bun@v2
+      - uses: oven-sh/setup-bun@<full-commit-sha>
         with:
           bun-version: latest
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 33-33: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 33 - 38, Replace mutable action refs
with their corresponding commit SHAs and disable checkout credential
persistence: update uses entries for actions/checkout@v4, oven-sh/setup-bun@v2,
dtolnay/rust-toolchain@stable, and swatinem/rust-cache@v2 to point to specific
commit SHA pins instead of version tags, and add with: persist-credentials:
false to both actions/checkout steps so the checkout step does not leave
credentials in the runner; locate the steps that use the symbols
actions/checkout@v4, oven-sh/setup-bun@v2, dtolnay/rust-toolchain@stable, and
swatinem/rust-cache@v2 in the CI workflow and replace the tags with their SHA
and add the persist-credentials setting to the checkout steps.


- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libsecret-1-dev \
build-essential

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install
- name: Bundle plugins
run: bun run bundle:plugins

- name: Cargo test
working-directory: src-tauri
run: cargo test --locked
51 changes: 41 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,46 @@ jobs:
include:
- platform: macos-latest
args: "--target aarch64-apple-darwin"
rust_targets: "aarch64-apple-darwin"
- platform: macos-latest
args: "--target x86_64-apple-darwin"
rust_targets: "x86_64-apple-darwin"
- platform: ubuntu-22.04
args: ""
rust_targets: "x86_64-unknown-linux-gnu"
runs-on: ${{ matrix.platform }}
env:
RELEASE_TAG: ${{ github.ref_name }}
# GitHub runners lack FUSE; make linuxdeploy/AppImage tooling extract-and-run.
APPIMAGE_EXTRACT_AND_RUN: 1
# Whether Apple code-signing secrets are configured (forks usually have none).
# When false the macOS job builds an unsigned app instead of failing.
HAS_APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE != '' }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_TAG }}

- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin,x86_64-apple-darwin
targets: ${{ matrix.rust_targets }}
- uses: swatinem/rust-cache@v2
Comment on lines 36 to 43
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unpinned actions and checkout credential persistence in publish workflow.
rg -n '^\s*-\s*uses:\s*.+@(?!(?:[a-f0-9]{40})$).*' .github/workflows/publish.yml
rg -n -C2 'actions/checkout@' .github/workflows/publish.yml
rg -n 'persist-credentials:\s*false' .github/workflows/publish.yml

Repository: robinebers/openusage

Length of output: 520


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/publish.yml"

echo "== publish.yml summary (first 220 lines) =="
nl -ba "$FILE" | sed -n '1,220p'

echo "== All uses: lines with line numbers =="
rg -n '^\s*-\s*uses:\s*' "$FILE"

echo "== uses refs that are tags (not 40-hex SHA) (PCRE2) =="
rg --pcre2 -n '^\s*-\s*uses:\s*([^@]+)@(?!(?:[A-Fa-f0-9]{40})$).*$' "$FILE" || true

echo "== Checkout steps blocks (actions/checkout) with surrounding context =="
rg -n -C3 'actions/checkout@' "$FILE" || true

echo "== persist-credentials occurrences =="
rg -n 'persist-credentials:' "$FILE" || true

Repository: robinebers/openusage

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/publish.yml"

nl -ba "$FILE" | sed -n '1,220p'

echo "--- uses lines ---"
rg -n '^\s*-\s*uses:\s*' "$FILE"

echo "--- unpinned uses (not exactly 40 hex) ---"
rg --pcre2 -n '^\s*-\s*uses:\s*[^@]+@(?!(?:[A-Fa-f0-9]{40})$).*$' "$FILE" || true

echo "--- checkout blocks ---"
rg -n -C3 'actions/checkout@' "$FILE" || true

echo "--- persist-credentials occurrences ---"
rg -n 'persist-credentials:' "$FILE" || true

Repository: robinebers/openusage

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/publish.yml"

echo "== publish.yml (with line numbers) =="
awk '{printf "%6d: %s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo "== All uses: lines with line numbers =="
awk '{if ($0 ~ /^[[:space:]]*-[[:space:]]uses:/) printf "%6d: %s\n", NR, $0}' "$FILE"

echo "== uses refs that are tags (not exactly 40-hex SHA) (PCRE2) =="
rg --pcre2 -n '^\s*-\s*uses:\s*([^@]+)@(?!(?:[A-Fa-f0-9]{40})$).*$' "$FILE" || true

echo "== checkout steps blocks (actions/checkout) with surrounding context (3 lines) =="
rg -n -C3 'uses:\s*actions/checkout@' "$FILE" || true

echo "== persist-credentials occurrences =="
rg -n 'persist-credentials:' "$FILE" || true

Repository: robinebers/openusage

Length of output: 8601


Pin publish workflow actions to commit SHAs and set persist-credentials: false for checkout.

This release workflow uses mutable action tags and checkout does not disable credential persistence, leaving avoidable supply-chain and token exposure risk.

  • .github/workflows/publish.yml: actions/checkout@v4 (36-38) — tag-based @v4 and missing persist-credentials: false.
  • Also tag-based uses: refs: dtolnay/rust-toolchain@stable, swatinem/rust-cache@v2 (40-43), oven-sh/setup-bun@v2 (60-63), tauri-apps/tauri-action@v0 (135).
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 36-38: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 40-40: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 43-43: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 43-43: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml around lines 36 - 43, The workflow uses
mutable action tags and leaves checkout credentials persisted; update the uses
references to immutable commit SHAs for actions currently referenced by tags
(actions/checkout, dtolnay/rust-toolchain, swatinem/rust-cache,
oven-sh/setup-bun, tauri-apps/tauri-action) and set persist-credentials: false
on the actions/checkout step to prevent token leakage; replace each tag-based
ref (e.g. actions/checkout@v4, dtolnay/rust-toolchain@stable,
swatinem/rust-cache@v2, oven-sh/setup-bun@v2, tauri-apps/tauri-action@v0) with
the corresponding pinned git commit SHA and add persist-credentials: false under
the checkout step.

with:
workspaces: "./src-tauri -> target"

- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libsecret-1-dev \
build-essential

- uses: oven-sh/setup-bun@v2
with:
bun-version: "latest"
Expand Down Expand Up @@ -80,11 +103,16 @@ jobs:
exit 1
fi

- name: Import Apple Developer Certificate
- name: Import Apple Developer Certificate & enable signing
if: runner.os == 'macOS' && env.HAS_APPLE_CERT == 'true'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
Expand All @@ -94,21 +122,24 @@ jobs:
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm certificate.p12
# Only expose the Apple signing vars to tauri-action when a cert exists.
# If they are set but empty, Tauri tries to codesign with identity "" and fails;
# leaving them unset makes Tauri ad-hoc sign, which works for unsigned releases.
{
echo "APPLE_SIGNING_IDENTITY=$APPLE_SIGNING_IDENTITY"
echo "APPLE_ID=$APPLE_ID"
echo "APPLE_PASSWORD=$APPLE_PASSWORD"
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID"
} >> "$GITHUB_ENV"

- 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 }}

APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# Apple signing vars are injected via $GITHUB_ENV by the step above,
# and only when a certificate is configured. Do not set them here.
with:
tagName: ${{ env.RELEASE_TAG }}
releaseName: ${{ env.RELEASE_TAG }}
Expand Down
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,53 @@ See your usage at a glance from your menu bar. No digging through dashboards.

## Download

[**Download the latest release**](https://github.com/robinebers/openusage/releases/latest) (macOS, Apple Silicon & Intel)
[**Download the latest release**](https://github.com/robinebers/openusage/releases/latest) (macOS Apple Silicon & Intel, Linux AppImage/.deb/.rpm)

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

> **Linux notes:** OpenUsage runs in the system tray. On desktops without
> StatusNotifierItem/AppIndicator support (e.g. GNOME without the AppIndicator
> extension) a left-click on the tray icon may not open the panel — use the
> tray menu's **Show Stats** entry or the global shortcut instead. The panel
> appears under the tray icon where the compositor allows it (best-effort on
> Wayland). Reading credentials stored in the system keyring requires
> `secret-tool` (the `libsecret`/`libsecret-tools` package).

## Install on Linux

Grab the asset for your distro from the
[latest release](https://github.com/robinebers/openusage/releases/latest)
(replace the version in the examples below if a newer one is out).

**Fedora / RHEL (.rpm)** — `dnf` pulls in the dependencies automatically:

```sh
sudo dnf install https://github.com/robinebers/openusage/releases/download/v0.6.24/OpenUsage-0.6.24-1.x86_64.rpm
```

**Debian / Ubuntu (.deb):**

```sh
curl -LO https://github.com/robinebers/openusage/releases/download/v0.6.24/OpenUsage_0.6.24_amd64.deb
sudo apt install ./OpenUsage_0.6.24_amd64.deb
```

**Any distro (AppImage)** — portable, no install:

```sh
curl -L -o OpenUsage.AppImage https://github.com/robinebers/openusage/releases/download/v0.6.24/OpenUsage_0.6.24_amd64.AppImage
chmod +x OpenUsage.AppImage
./OpenUsage.AppImage
```

After installing via `.rpm`/`.deb`, launch **OpenUsage** from your app menu — it
starts in the system tray. If your desktop has no tray (e.g. GNOME), install an
AppIndicator/StatusNotifier extension, or open the panel with the global shortcut.

Runtime dependencies (handled automatically by `.rpm`/`.deb`): `webkit2gtk-4.1`,
`gtk3`, an AppIndicator library, and — for providers that read the system
keyring — `secret-tool` (`libsecret-tools` / `libsecret`).

## What It Does

OpenUsage lives in your menu bar and shows you how much of your AI coding subscriptions you've used. Progress bars, badges, and clear labels. No mental math required.
Expand Down Expand Up @@ -98,6 +141,20 @@ Inspired by [CodexBar](https://github.com/steipete/CodexBar) by [@steipete](http

> **Warning**: The `main` branch may not be stable. It is merged directly without staging, so users are advised to use tagged versions for stable builds. Tagged versions are fully tested while `main` may contain unreleased features.

### Linux build prerequisites

Install the system libraries Tauri needs before building:

```sh
# Debian/Ubuntu
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev \
libayatana-appindicator3-dev librsvg2-dev patchelf libsecret-1-dev build-essential

# Fedora
sudo dnf install -y webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel \
librsvg2-devel libsecret-devel patchelf
```

### Stack

...
11 changes: 9 additions & 2 deletions plugins/antigravity/plugin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
(function () {
var LS_SERVICE = "exa.language_server_pb.LanguageServerService"
var STATE_DB = "~/Library/Application Support/Antigravity/User/globalStorage/state.vscdb"
// Antigravity's app data location varies per OS (VS Code-style layout).
function stateDbPath(ctx) {
var platform = ctx.app && ctx.app.platform
var suffix = "/Antigravity/User/globalStorage/state.vscdb"
if (platform === "linux") return "~/.config" + suffix
if (platform === "windows") return "~/AppData/Roaming" + suffix
return "~/Library/Application Support" + suffix
}
var CLOUD_CODE_URLS = [
"https://daily-cloudcode-pa.googleapis.com",
"https://cloudcode-pa.googleapis.com",
Expand Down Expand Up @@ -95,7 +102,7 @@
function loadOAuthTokens(ctx) {
try {
var rows = ctx.host.sqlite.query(
STATE_DB,
stateDbPath(ctx),
"SELECT value FROM ItemTable WHERE key = '" + OAUTH_TOKEN_KEY + "' LIMIT 1"
)
var parsed = ctx.util.tryParseJson(rows)
Expand Down
17 changes: 13 additions & 4 deletions plugins/cursor/plugin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
(function () {
const STATE_DB =
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
// VS Code-style app data location varies per OS.
function stateDbPath(ctx) {
const platform = ctx.app && ctx.app.platform
if (platform === "linux") {
return "~/.config/Cursor/User/globalStorage/state.vscdb"
}
if (platform === "windows") {
return "~/AppData/Roaming/Cursor/User/globalStorage/state.vscdb"
}
return "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
}
const KEYCHAIN_ACCESS_TOKEN_SERVICE = "cursor-access-token"
const KEYCHAIN_REFRESH_TOKEN_SERVICE = "cursor-refresh-token"
const BASE_URL = "https://api2.cursor.sh"
Expand All @@ -18,7 +27,7 @@
try {
const sql =
"SELECT value FROM ItemTable WHERE key = '" + key + "' LIMIT 1;"
const json = ctx.host.sqlite.query(STATE_DB, sql)
const json = ctx.host.sqlite.query(stateDbPath(ctx), sql)
const rows = ctx.util.tryParseJson(json)
if (!Array.isArray(rows)) {
throw new Error("sqlite returned invalid json")
Expand All @@ -42,7 +51,7 @@
"', '" +
escaped +
"');"
ctx.host.sqlite.exec(STATE_DB, sql)
ctx.host.sqlite.exec(stateDbPath(ctx), sql)
return true
} catch (e) {
ctx.host.log.warn("sqlite write failed for " + key + ": " + String(e))
Expand Down
3 changes: 3 additions & 0 deletions plugins/gemini/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"~/.bun/install/global/node_modules",
"~/.npm-global/lib/node_modules",
"/usr/local/lib/node_modules",
"/usr/lib/node_modules",
"~/Library/pnpm/global/5/node_modules",
"~/.local/share/pnpm/global/5/node_modules",
]

const STATIC_NESTED_ONLY = [
Expand All @@ -20,6 +22,7 @@
const VERSION_MANAGER_ROOTS = [
{ root: "~/.nvm/versions/node", modulePath: "/lib/node_modules" },
{ root: "~/Library/Application Support/fnm/node-versions", modulePath: "/installation/lib/node_modules" },
{ root: "~/.local/share/fnm/node-versions", modulePath: "/installation/lib/node_modules" },
]

function listDirSafe(ctx, path) {
Expand Down
28 changes: 21 additions & 7 deletions plugins/kiro/plugin.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
(function () {
const STATE_DB = "~/Library/Application Support/Kiro/User/globalStorage/state.vscdb"
// Kiro's app data location varies per OS (VS Code-style layout).
function kiroBase(ctx) {
const platform = ctx.app && ctx.app.platform
if (platform === "linux") return "~/.config/Kiro"
if (platform === "windows") return "~/AppData/Roaming/Kiro"
return "~/Library/Application Support/Kiro"
}
function stateDbPath(ctx) {
return kiroBase(ctx) + "/User/globalStorage/state.vscdb"
}
function logsRoot(ctx) {
return kiroBase(ctx) + "/logs"
}
function profilePath(ctx) {
return kiroBase(ctx) + "/User/globalStorage/kiro.kiroagent/profile.json"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use consistent extension directory casing in profile path.

Line 16 uses kiro.kiroagent, but Line 18/Line 217 use kiro.kiroAgent. On Linux, this case mismatch can miss profile.json and break the ARN fallback.

Suggested fix
-    return kiroBase(ctx) + "/User/globalStorage/kiro.kiroagent/profile.json"
+    return kiroBase(ctx) + "/User/globalStorage/kiro.kiroAgent/profile.json"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return kiroBase(ctx) + "/User/globalStorage/kiro.kiroagent/profile.json"
return kiroBase(ctx) + "/User/globalStorage/kiro.kiroAgent/profile.json"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/kiro/plugin.js` at line 16, The profile path returned from
kiroBase(ctx) uses the wrong casing "kiro.kiroagent" which mismatches other
usages ("kiro.kiroAgent") and can break lookups on case-sensitive filesystems;
update the string returned by the function that constructs the profile path (the
line returning kiroBase(ctx) + "/User/globalStorage/.../profile.json") to use
the exact casing "kiro.kiroAgent" so it matches other references and preserves
the ARN fallback behavior.

}
const STATE_KEY = "kiro.kiroAgent"
const LOGS_ROOT = "~/Library/Application Support/Kiro/logs"
const LOG_FILE_NAME = "q-client.log"
const TOKEN_PATH = "~/.aws/sso/cache/kiro-auth-token.json"
const PROFILE_PATH = "~/Library/Application Support/Kiro/User/globalStorage/kiro.kiroagent/profile.json"
const REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
const LIVE_STALE_MS = 15 * 60 * 1000
const REFRESH_BUFFER_MS = 10 * 60 * 1000
Expand Down Expand Up @@ -77,7 +90,7 @@
function loadProfileArn(ctx, authState) {
const fromToken = authState && authState.token && authState.token.profileArn
if (typeof fromToken === "string" && fromToken) return fromToken
const parsed = readJsonFile(ctx, PROFILE_PATH, "profile")
const parsed = readJsonFile(ctx, profilePath(ctx), "profile")
return parsed && typeof parsed.arn === "string" && parsed.arn.trim() ? parsed.arn.trim() : null
}
function regionFromArn(profileArn) {
Expand All @@ -87,7 +100,7 @@
function readStateValue(ctx, key) {
try {
const sql = "SELECT value FROM ItemTable WHERE key = '" + String(key).replace(/'/g, "''") + "' LIMIT 1;"
const rows = ctx.util.tryParseJson(ctx.host.sqlite.query(STATE_DB, sql))
const rows = ctx.util.tryParseJson(ctx.host.sqlite.query(stateDbPath(ctx), sql))
return Array.isArray(rows) && rows.length && typeof rows[0].value === "string" ? rows[0].value : null
} catch (e) {
ctx.host.log.warn("Kiro sqlite read failed: " + String(e))
Expand Down Expand Up @@ -186,13 +199,14 @@
}
function loadLoggedState(ctx) {
let sessions = []
const logs = logsRoot(ctx)
try {
sessions = ctx.host.fs.listDir(LOGS_ROOT).slice().sort().reverse()
sessions = ctx.host.fs.listDir(logs).slice().sort().reverse()
} catch {
return null
}
for (let i = 0; i < sessions.length && i < 12; i += 1) {
const sessionRoot = LOGS_ROOT + "/" + sessions[i]
const sessionRoot = logs + "/" + sessions[i]
let windows = []
try {
windows = ctx.host.fs.listDir(sessionRoot).slice().sort().reverse()
Expand Down
19 changes: 16 additions & 3 deletions plugins/windsurf/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,28 @@
{
marker: "windsurf",
ideName: "windsurf",
stateDb: "~/Library/Application Support/Windsurf/User/globalStorage/state.vscdb",
appDir: "Windsurf",
},
{
marker: "windsurf-next",
ideName: "windsurf-next",
stateDb: "~/Library/Application Support/Windsurf - Next/User/globalStorage/state.vscdb",
appDir: "Windsurf - Next",
},
]

// VS Code-style app data location varies per OS.
function stateDbPath(ctx, appDir) {
var platform = ctx.app && ctx.app.platform
var suffix = "/" + appDir + "/User/globalStorage/state.vscdb"
if (platform === "linux") {
return "~/.config" + suffix
}
if (platform === "windows") {
return "~/AppData/Roaming" + suffix
}
return "~/Library/Application Support" + suffix
}

function readFiniteNumber(value) {
if (typeof value === "number") return Number.isFinite(value) ? value : null
if (typeof value !== "string") return null
Expand All @@ -39,7 +52,7 @@
function loadApiKey(ctx, variant) {
try {
var rows = ctx.host.sqlite.query(
variant.stateDb,
stateDbPath(ctx, variant.appDir),
"SELECT value FROM ItemTable WHERE key = 'windsurfAuthStatus' LIMIT 1"
)
var parsed = ctx.util.tryParseJson(rows)
Expand Down
Loading
Loading