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
59 changes: 58 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,56 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}

- name: Ad-hoc sign
run: |
APP=$(find src-tauri/target/universal-apple-darwin/release/bundle/macos -name "*.app" -maxdepth 1 | head -1)
echo "Signing: $APP"
codesign --deep --force --sign - "$APP"

- name: Collect updater artifacts
run: |
BUNDLE=src-tauri/target/universal-apple-darwin/release/bundle/macos
TARGZ=$(find "$BUNDLE" -name "*.app.tar.gz" -maxdepth 1 | head -1)
SIG=$(find "$BUNDLE" -name "*.app.tar.gz.sig" -maxdepth 1 | head -1)
if [ -z "$TARGZ" ] || [ -z "$SIG" ]; then
echo "No signed updater artifacts — TAURI_SIGNING_PRIVATE_KEY may not be set."
echo "HAS_UPDATER=false" >> $GITHUB_ENV
else
TARGZ_NAME=$(basename "$TARGZ")
cp "$TARGZ" "$TARGZ_NAME"
cp "$SIG" "${TARGZ_NAME}.sig"
echo "TARGZ_NAME=$TARGZ_NAME" >> $GITHUB_ENV
echo "HAS_UPDATER=true" >> $GITHUB_ENV
fi

- name: Generate latest.json
if: env.HAS_UPDATER == 'true'
run: |
VERSION="${{ steps.ref.outputs.tag }}"
VERSION_NUM="${VERSION#v}"
TARGZ_NAME="${{ env.TARGZ_NAME }}"
DOWNLOAD_URL="https://github.com/varkart/contextbar/releases/download/${VERSION}/${TARGZ_NAME}"
python3 - "$VERSION_NUM" "$DOWNLOAD_URL" "${TARGZ_NAME}.sig" <<'PYEOF'
import sys, json
from datetime import datetime, timezone
version, url, sig_path = sys.argv[1], sys.argv[2], sys.argv[3]
signature = open(sig_path).read()
manifest = {
"version": version,
"notes": f"See https://github.com/varkart/contextbar/releases/tag/v{version}",
"pub_date": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"platforms": {
"darwin-aarch64": {"signature": signature, "url": url},
"darwin-x86_64": {"signature": signature, "url": url},
}
}
with open("latest.json", "w") as f:
json.dump(manifest, f, indent=2)
print(json.dumps(manifest, indent=2))
PYEOF

- name: Package DMG
run: |
VERSION="${{ steps.ref.outputs.tag }}"
Expand Down Expand Up @@ -112,6 +155,19 @@ jobs:
SHA256=$(shasum -a 256 "${{ env.DMG_NAME }}" | awk '{print $1}')
echo "DMG_SHA256=$SHA256" >> $GITHUB_ENV

- name: Build release file list
run: |
{
echo "RELEASE_FILES<<RFEOF"
echo "${{ env.DMG_NAME }}"
if [ "$HAS_UPDATER" = "true" ]; then
echo "${{ env.TARGZ_NAME }}"
echo "${{ env.TARGZ_NAME }}.sig"
echo "latest.json"
fi
echo "RFEOF"
} >> $GITHUB_ENV

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
Expand All @@ -123,8 +179,9 @@ jobs:
---
**Install (DMG):** Download the `.dmg` below.
**Install (Homebrew):** `brew tap varkart/tap && brew install --cask contextbar`
**Auto-update:** Existing installs update automatically via the Settings panel.
**macOS security prompt:** run `xattr -d com.apple.quarantine /Applications/Context\ Bar.app`
files: ${{ env.DMG_NAME }}
files: ${{ env.RELEASE_FILES }}
draft: false
prerelease: false

Expand Down
18 changes: 18 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,23 @@ async fn check_for_update(app: tauri::AppHandle) -> Result<Option<serde_json::Va
}
}

#[tauri::command]
async fn install_update(app: tauri::AppHandle) -> Result<(), String> {
use tauri_plugin_updater::UpdaterExt;
let update = app
.updater()
.map_err(|e| e.to_string())?
.check()
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| "No update available".to_string())?;
update
.download_and_install(|_, _| {}, || {})
.await
.map_err(|e| e.to_string())?;
app.restart();
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1939,6 +1956,7 @@ pub fn run() {
install_skill_from_github,
read_text_file,
check_for_update,
install_update,
query_mcp_tools,
remove_skill,
set_skill_active,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"plugins": {
"updater": {
"pubkey": "",
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDg4ODlBMUMxQThDNDM5RUYKUldUdk9jU293YUdKaUxpNHhPWjZPNktUWG9kMTBCU1VBdTQzYnNEN2RwaGdsb3l0OXZRMWNaM2QK",
"endpoints": ["https://github.com/varkart/contextbar/releases/latest/download/latest.json"]
}
},
Expand Down
27 changes: 23 additions & 4 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export default function Settings({ updateInfo, theme, onThemeChange, onOpenLogs
const [vibrancyLoading, setVibrancyLoading] = useState(true)
const [version, setVersion] = useState('')
const [accessibilityGranted, setAccessibilityGranted] = useState<boolean | null>(null)
const [installing, setInstalling] = useState(false)
const [installError, setInstallError] = useState<string | null>(null)

useEffect(() => {
Promise.all([
Expand Down Expand Up @@ -309,10 +311,27 @@ export default function Settings({ updateInfo, theme, onThemeChange, onOpenLogs
</SettingRow>
{updateInfo && (
<SettingRow label="Update">
<a href={updateInfo.releaseUrl} target="_blank" rel="noopener noreferrer"
className="text-[13px] text-indigo-500 hover:text-indigo-400 transition-colors flex items-center gap-1">
{updateInfo.latestVersion} available <ExternalLinkIcon />
</a>
<div className="flex flex-col items-end gap-1">
<button
onClick={async () => {
setInstalling(true)
setInstallError(null)
try {
await invoke('install_update')
} catch (e) {
setInstallError(String(e))
setInstalling(false)
}
}}
disabled={installing}
className="text-[13px] px-2 py-0.5 rounded-md bg-indigo-500 text-white hover:bg-indigo-400 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
>
{installing ? 'Installing…' : `Install ${updateInfo.latestVersion}`}
</button>
{installError && (
<span className="text-[11px] text-red-400 max-w-[160px] text-right">{installError}</span>
)}
</div>
</SettingRow>
)}
<SettingRow label="Source">
Expand Down
2 changes: 1 addition & 1 deletion src/components/__tests__/Settings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('Settings', () => {

it('shows update info when provided', async () => {
render(<Settings {...defaultProps} updateInfo={{ latestVersion: '1.0.0', releaseUrl: 'https://example.com' }} />)
await waitFor(() => expect(screen.getByText(/1\.0\.0 available/)).toBeInTheDocument())
await waitFor(() => expect(screen.getByRole('button', { name: /Install 1\.0\.0/ })).toBeInTheDocument())
})

it('does not show update row when updateInfo is null', async () => {
Expand Down
Loading