diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml index 5af787c283..b4bac20829 100644 --- a/.codex/environments/environment.toml +++ b/.codex/environments/environment.toml @@ -1,6 +1,6 @@ # THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY version = 1 -name = "char" +name = "anarlog" [setup] script = "" diff --git a/.github/actions/install_cli_deps/action.yaml b/.github/actions/install_cli_deps/action.yaml deleted file mode 100644 index f4e55a1dfe..0000000000 --- a/.github/actions/install_cli_deps/action.yaml +++ /dev/null @@ -1,14 +0,0 @@ -runs: - using: "composite" - steps: - - if: ${{ runner.os == 'Linux' }} - shell: bash - run: | - sudo apt-get update - sudo add-apt-repository -y ppa:pipewire-debian/pipewire-upstream - sudo apt-get update - sudo apt-get install -y \ - pkg-config \ - libasound2-dev \ - libpulse-dev \ - libpipewire-0.3-dev diff --git a/.github/actions/sentry_cli/action.yaml b/.github/actions/sentry_cli/action.yaml deleted file mode 100644 index b43d24b4e2..0000000000 --- a/.github/actions/sentry_cli/action.yaml +++ /dev/null @@ -1,11 +0,0 @@ -inputs: - version: - required: false - default: "2.39.1" -runs: - using: "composite" - steps: - - run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="${{ inputs.version }}" sh - shell: bash - - run: sentry-cli --version - shell: bash diff --git a/.github/actions/wait-for-netlify-preview/action.yml b/.github/actions/wait-for-netlify-preview/action.yml deleted file mode 100644 index 28e764f207..0000000000 --- a/.github/actions/wait-for-netlify-preview/action.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Wait for Netlify preview -description: Set BASE_URL to the Netlify deploy-preview URL and wait until it is ready -runs: - using: composite - steps: - - name: Set BASE_URL and wait for Netlify preview - shell: bash - run: | - if [[ "${{ github.event_name }}" != "pull_request" ]]; then - echo "Not a pull_request event; skipping Netlify preview wait." - exit 0 - fi - - url="https://deploy-preview-${{ github.event.pull_request.number }}--hyprnote.netlify.app" - echo "BASE_URL=$url" >> "$GITHUB_ENV" - - echo "Waiting for $url to be ready..." - for i in {1..60}; do - if curl -sSf "$url" > /dev/null 2>&1; then - echo "Preview is ready" - exit 0 - fi - echo "Not ready yet, sleeping... (attempt $i/60)" - sleep 10 - done - - echo "Timed out waiting for Netlify preview" - exit 1 diff --git a/.github/reports/legal-review/2026-04.md b/.github/reports/legal-review/2026-04.md deleted file mode 100644 index 917168f131..0000000000 --- a/.github/reports/legal-review/2026-04.md +++ /dev/null @@ -1,136 +0,0 @@ -# Legal Review — 2026-04 - -Scope: commits merged to `fastrepl/char` in the ~30 days leading up to 2026-04-19. -Legal docs inspected: `apps/web/content/legal/{privacy,terms,cookies,dpa}.mdx`. - -This report flags product changes that appear to introduce new data flows, sub-processors, permissions, or user surfaces that may not be reflected in the current legal documentation. All items are flagged for **human legal review** — none of the findings have been auto-remediated. When an item is ambiguous it is flagged rather than dismissed. - ---- - -## Changes Detected - -### 1. New third-party STT provider: AquaVoice -- Commit `8def7da1b` (2026-04-13) "add aquavoice" introduces a new cloud STT provider. -- Provider is user-selectable: `apps/desktop/src/settings/ai/stt/shared.tsx` exposes `aquavoice` with logo and `baseUrl: "https://api.aquavoice.com/api/v1"`. -- Wired through `crates/owhisper-client/src/providers.rs` (`Provider::AquaVoice`) and `crates/transcribe-proxy/src/env.rs` (`aquavoice_api_key`). -- Batch transcription only. - -### 2. New third-party STT provider scaffolding: SmallestAI -- Commits `7efb0c5e7` (2026-04-16) "scaffold smallestai adapter" and `7067cb4a4` "slight smallestai improvements". -- Adds full batch + live adapter at `crates/owhisper-client/src/adapter/smallestai/` (~1,000 LOC). -- Not yet wired into the settings UI as a user-selectable provider at the time of this review, but the adapter is complete and a short code path away from exposure. - -### 3. New third-party speaker-diarization proxy: Pyannote -- Commit `e4c65d02e` (2026-04-01) "add pyannote in api proxy" and `abb03da70` "update pyannote proxy route for safety". -- Adds `crates/api-pyannote/` and routes pyannote through `apps/api`. This represents raw-audio transmission to a third-party diarization service. - -### 4. New sub-processor: exe.dev via `apps/claw` + `crates/api-claw` + `crates/exedev` -- Commits `824590609`, `e2cc200f4`, `d82e644bb`, `8a39a63e8`, `ba492bfb0`, `ed515cbb3` (2026-04-17 / 2026-04-18). -- `crates/api-claw/AGENTS.md` describes a per-user VM lifecycle on top of `exe.dev`: - - One VM per user (`vm_name = "claw-" + sha256(user_id)[..12]`). - - Per-user SSH key stored in a keyring, scoped to the user's Supabase ID. - - Token minting includes `{"user_id": }` context. - - Billing hooks: "on `invoice.payment_failed` → suspend" (SSH key removal as suspension). -- `apps/claw` ships as a Dockerized HTTP app to run inside each user VM. -- This is a material new sub-processor with user-identity linkage, credential storage, and subscription-gated access control. - -### 5. New third-party agent/research integrations (scope unclear): Cursor, Exa, Devin -- `crates/cursor/` — `https://api.cursor.com`, launches agent sessions (`update crates/cursor` 0d89a0a5b, 2026-04-18). -- `crates/exa/` — `https://api.exa.ai`, web search + content retrieval (`update crates/exa` 04c5ba7b4, 2026-04-18). Used by `crates/api-research/` alongside `hypr-jina` and `hypr-mcp` and exposed via `apps/api`. -- `crates/devin/` — `https://api.devin.ai` Devin session management. -- These clients are compiled into the repo and, in the case of Exa, wired into `api-research`'s MCP server. Whether any of them are currently reachable from the desktop client in a user-facing capacity needs product confirmation; at minimum Exa is reachable via the server-side `api-research` router. - -### 6. Mobile app (Android + iOS) via Expo -- Commit `ed3ac0ad7` (2026-04-13) "expo for mobile" adds `apps/mobile` (Expo Router, bundle id `com.hyprmobile`). -- Commit `875fc585f` (2026-04-13) "bump cloudsync with mobile support" extends `crates/cloudsync` with Android (arm64-v8a, armeabi-v7a, x86_64) and iOS targets. -- The existing Privacy Policy, Terms, and DPA describe a "local-first application" on a "device" and list app/browser telemetry. They do not explicitly call out the mobile platforms or mobile-specific permissions/identifiers (e.g., iOS IDFA, Android Ad ID, push-notification tokens). - -### 7. Activity capture: storage abstraction + observation model + screenshots -- Commits `2446086f1` "activity capture experiment" (2026-04-10), `c25e92b8f` "various activity capture improvements" (2026-04-11), `d82249dbe` "various activity-capture update around the observation concept" (2026-04-13), `8f1354872` "add storage abstraction for activity-capture" (2026-04-15), plus continued work on `activity-capture-dev`. -- `crates/activity-capture/src/screenshot.rs` continues to be the capture path; `db-activity` migration `20260410000000_init.sql` persists observations. -- `crates/template-app/src/activity_capture.rs` feeds captured app-usage (app names, window titles, timestamps) into `daily-summary` LLM templates. -- A prior legal report (PR #4893, merged then removed in #4936) flagged this area. Activity capture has expanded since, and the Privacy Policy/DPA still do not explicitly describe on-device screenshotting, app/window-title logging, or daily-summary LLM processing of that data. - -### 8. New system permissions surfaced in settings -- Commit `2b51c4343` (2026-04-10) "add screen recording permission in settings" (macOS TCC screen-recording). -- Commit `5fca17a74` (2026-04-17) "update permissions for input monitoring" (macOS Input Monitoring via `plugins/permissions` + `plugins/shortcut`). -- Commit `7efc08371` (2026-04-17) "add dictation ui for macos" adds `crates/dictation-ui-macos`. -- These permissions expand the categories of data the app can read from the host OS (screen contents, keystrokes, system audio / dictation). The Privacy Policy mentions "Device Information" and "Usage Data" generically; it does not enumerate screen-recording, keystroke-level input monitoring, or dictation as data categories. - -### 9. Battery / power telemetry collection -- Commits `eb1c9a877` "add crates/power" (2026-04-11) and `f056375d6` "include percentage in crates/power". -- Collects AC/battery state, charging status, and remaining battery capacity on macOS (IOKit) and Windows. If this is piped into telemetry/analytics or `activity-capture`, it adds a new data category (power state / battery level) not explicitly enumerated in the Privacy Policy. - -### 10. File attachment support in the editor -- Commit `d73430985` (2026-04-17) "feat: file attachment support in editor (#5053)" adds arbitrary-file attachments to notes via `useFileUpload` / `plugins/file-handler`, with `attachmentId` persisted in the editor schema. -- Privacy Policy already references "Notes, documents, and other content you create or upload" as User Content, so this is likely covered, but storage location (local vs. cloud upload via Supabase/S3/R2) and any new file-size/type retention should be confirmed by legal. - -### 11. Supabase auth token refresh under feature flag -- Commit `52df539ea` (2026-04-18) "Add supabase auth token refresh under feature flag (#5085)". Introduces client-side refresh-token handling in `crates/supabase-auth` (new `refresh` feature). -- Supabase is already a disclosed sub-processor. Mostly informational; confirm that refresh-token storage location (keyring vs. disk) matches the security language in Annex I. - -### 12. Integration routes split: auth-only management endpoints -- Commit `cf1790b35` (2026-04-13) "allow disconnect integration without payment" splits `api-nango` into `session_router` (subscription-gated) and `management_router` (auth-gated). Users can now disconnect integrations without an active subscription. -- This is user-friendly; does not add new data flows. Worth noting because the DPA/Privacy language around OAuth disconnection lifecycles should remain accurate. - -### 13. Consent UX improvements (positive) -- `566c875e6` (2026-04-16) "add Microsoft Clarity script (#5066)" — Clarity added behind website tracking consent; legal docs (`privacy.mdx`, `cookies.mdx`, `dpa.mdx`) updated concurrently in the same PR. No gap. -- `fab9f4532` (2026-04-16) "adapt web consent banner by region (#5062)" — resolves consent region from request geo, switches flow for California/EEA. Aligns with CCPA/GDPR commitments in the DPA. -- `197c1714e` (2026-04-11) "hide privacy consent on auth pages" — scoped banner suppression. -- `9a2d9641e` (2026-04-13) "eliminate FOUC on cookie accept" — no legal impact. - -### 14. `Terms of Service` frontmatter date is stale -- `apps/web/content/legal/terms.mdx` is dated `2026-01-07` while `privacy.mdx`, `cookies.mdx`, and `dpa.mdx` are dated `2026-04-16`. Several disclosures that would touch the Terms (new sub-processors, mobile platform, Claw VM lifecycle, subscription suspension mechanics) were not accompanied by a Terms update. - ---- - -## Current Documentation Status - -| Area | Covered in | Status | -|------|-----------|--------| -| AquaVoice STT | DPA Annex II | **Missing.** Deepgram/AssemblyAI/Soniox are listed; AquaVoice is not. | -| SmallestAI STT | DPA Annex II | **Missing.** Needs listing before the provider is exposed in UI. | -| Pyannote speaker diarization | DPA Annex II / Privacy §6.2 | **Missing.** No current reference to pyannote. | -| exe.dev / Claw user VM | DPA Annex II, Privacy §6.2 / §5, Terms §9 | **Missing.** No disclosure of per-user VM provisioning, SSH key storage, subscription-gated suspension, or cross-border data residency of those VMs. | -| Exa / Cursor / Devin APIs | DPA Annex II | **Missing.** No mention. Needs scoping of which are user-facing. | -| Mobile app (iOS/Android) | Privacy §3.2, Terms §2, DPA §3 | **Partial.** Generic "device" language may cover it, but no explicit mobile identifiers, push-notification providers, or mobile telemetry disclosure. | -| Activity capture (screens, app/window titles, daily summaries) | Privacy §3.2, DPA §3 | **Partial / gap.** "Usage Data" language is generic. Screen capture, active-app/window-title logging, and LLM processing of that data for daily summaries are not specifically disclosed. | -| Screen-recording / input-monitoring / dictation permissions | Privacy §3.2 | **Partial.** Not enumerated as data categories; users see macOS permission prompts without a parallel privacy-policy explanation. | -| Battery / power state telemetry | Privacy §3.2 | **Missing.** Not enumerated. | -| File attachments in notes | Terms §6, Privacy §3.1 | **Likely covered** as User Content; confirm storage/retention. | -| Supabase auth token refresh | DPA Annex II, Privacy §6.2 | **Covered.** Supabase already disclosed. | -| Integration disconnect without payment | Privacy §7.5, DPA §9 | **Covered.** Improves alignment with existing deletion/disconnection rights. | -| Regional consent banner / Microsoft Clarity / GPC | Cookies §3.2 / §7, Privacy §7.4, DPA §7 | **Covered.** Updated concurrently. | -| Terms of Service date | n/a | **Stale** (`2026-01-07`) relative to other docs (`2026-04-16`). | - ---- - -## Recommended Updates (for human legal review, not auto-applied) - -1. **DPA Annex II — add / confirm sub-processors:** AquaVoice, SmallestAI (once user-reachable), Pyannote, exe.dev (Claw VMs), Exa, and any of Cursor / Devin that are user-facing. For each, include purpose, region, and safeguards consistent with existing entries. -2. **DPA §3 and Privacy §3.2 — document Claw VMs:** describe that an optional per-user compute environment is provisioned on exe.dev, with SSH credentials and a user identifier stored to manage its lifecycle, and that access is suspended on payment failure. Address data-at-rest location on that VM. -3. **Privacy §3.2 — enumerate activity-capture data:** explicitly list on-device screen captures, foreground-app names, and window titles (if retained) as a data category, describe whether any leaves the device, and disclose that this data is fed into LLM-generated daily summaries (local or cloud, whichever applies). -4. **Privacy §3.2 / Cookies — enumerate system permissions:** add explicit language for screen recording, input monitoring, microphone dictation, and accessibility permissions, mirroring the macOS TCC prompts. -5. **Privacy §3.2 — add power/battery state** to "Device Information" if that telemetry is transmitted or persisted server-side; otherwise confirm it stays local-only. -6. **Terms §2 / Privacy §3 — acknowledge mobile platforms:** update "device" language to cover iOS and Android clients, call out mobile-specific identifiers (push tokens, IDFA/Ad ID if used), and address mobile permissions. -7. **Terms §7 / §10 — payment-triggered suspension:** document that cloud-feature access and per-user compute (Claw VM) may be suspended on payment failure, and how restoration works. -8. **Terms frontmatter date:** refresh `terms.mdx`'s `date` when any of the above lands, so consumers see a consistent "last updated". -9. **DPA §5 — sub-processor notification cadence:** confirm the in-app / email notification for the new sub-processors has been (or will be) sent if they are already live for paying users. - ---- - -## Risk Assessment - -| # | Item | Severity | Rationale | -|---|------|----------|-----------| -| 4 | exe.dev / Claw user VM (new sub-processor, per-user SSH keys, identity linkage, subscription suspension) | **High** | Material new sub-processor processing user-linked credentials and potentially Customer Content. Not in Annex II. Cross-border transfer implications. | -| 7 | Activity capture (screenshots, window titles, daily-summary LLM feeds) | **High** | Expanding surface that was already flagged previously. Captures categories of data beyond what Privacy §3.2 spells out. Potential for incidental capture of third-party/sensitive content on screen. | -| 1, 2, 3 | AquaVoice, SmallestAI, Pyannote (raw-audio processors not in Annex II) | **Medium-High** | Raw audio is Personal Data; DPA requires sub-processors to be listed with safeguards. AquaVoice is already reachable from the UI. | -| 5 | Exa / Cursor / Devin client crates | **Medium** | Exa is wired into `api-research`; Cursor/Devin present but scope unclear. If user content or identifiers are sent to any of them, they need Annex II entries. | -| 6 | Mobile app + mobile cloudsync targets | **Medium** | New platforms introduce new identifiers and permissions; existing "device" language is thin. | -| 8 | Screen recording, input monitoring, dictation permissions | **Medium** | Each is a discrete user-consent surface; privacy text should map 1:1 to OS prompts. | -| 9 | Battery / power telemetry | **Low-Medium** | Only impactful if transmitted/persisted; needs confirmation. | -| 10, 11, 12 | File attachments, Supabase refresh, integration-disconnect split | **Low** | Likely covered by existing language; confirm storage/retention details. | -| 14 | Stale Terms `date` | **Low** | Cosmetic but undermines "last updated" trust; easy fix when related updates land. | - -When uncertain, items have been flagged rather than dismissed. No legal document was modified by this review; all changes above are recommendations for human counsel. diff --git a/.github/workflows/api_cd.yaml b/.github/workflows/api_cd.yaml deleted file mode 100644 index 73869b9c58..0000000000 --- a/.github/workflows/api_cd.yaml +++ /dev/null @@ -1,45 +0,0 @@ -on: - workflow_dispatch: - -jobs: - compute-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - run: git fetch --tags --force - - uses: ./.github/actions/doxxer_install - - id: version - run: | - VERSION=$(doxxer --config doxxer.api.toml next patch) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Computed version: $VERSION" - - deploy: - needs: compute-version - runs-on: ubuntu-latest - timeout-minutes: 60 - concurrency: api-fly-deploy - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --config apps/api/fly.toml --dockerfile apps/api/Dockerfile --remote-only --build-arg APP_VERSION=${{ needs.compute-version.outputs.version }} - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - - tag: - needs: [compute-version, deploy] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: mathieudutour/github-tag-action@v6.2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - custom_tag: api_v${{ needs.compute-version.outputs.version }} - tag_prefix: "" diff --git a/.github/workflows/api_ci.yaml b/.github/workflows/api_ci.yaml deleted file mode 100644 index 4b8b0dafeb..0000000000 --- a/.github/workflows/api_ci.yaml +++ /dev/null @@ -1,23 +0,0 @@ -on: - workflow_dispatch: - push: - branches: - - main - paths: - - apps/api/** - - crates/llm-proxy/** - - crates/transcribe-proxy/** - pull_request: - paths: - - apps/api/** - - crates/llm-proxy/** - - crates/transcribe-proxy/** -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - run: cargo check -p api diff --git a/.github/workflows/bot_cd.yaml b/.github/workflows/bot_cd.yaml deleted file mode 100644 index 2523f689ed..0000000000 --- a/.github/workflows/bot_cd.yaml +++ /dev/null @@ -1,19 +0,0 @@ -on: - workflow_dispatch: - push: - paths: - - "apps/bot/**" - branches: - - main - -jobs: - deploy: - runs-on: depot-ubuntu-24.04-8 - timeout-minutes: 60 - concurrency: bot-fly-deploy - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --config apps/bot/fly.toml --remote-only - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/bot_ci.yaml b/.github/workflows/bot_ci.yaml deleted file mode 100644 index 21e313d277..0000000000 --- a/.github/workflows/bot_ci.yaml +++ /dev/null @@ -1,18 +0,0 @@ -on: - workflow_dispatch: - pull_request: - paths: - - apps/bot/** -jobs: - ci: - runs-on: depot-ubuntu-24.04-8 - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - run: pnpm -F @hypr/bot typecheck - - run: pnpm -F @hypr/bot test diff --git a/.github/workflows/cactus.yaml b/.github/workflows/cactus.yaml deleted file mode 100644 index f180ea40d8..0000000000 --- a/.github/workflows/cactus.yaml +++ /dev/null @@ -1,44 +0,0 @@ -on: - workflow_dispatch: - push: - branches: [main] - paths: - - crates/cactus/** - - crates/cactus-sys/** - pull_request: - paths: - - crates/cactus/** - - crates/cactus-sys/** - -jobs: - cactus: - runs-on: depot-ubuntu-24.04-arm-8 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - run: git clone --depth 1 --branch v1.7 https://github.com/cactus-compute/cactus.git cactus - - uses: ./.github/actions/rust_install - with: - platform: linux - - run: | - sudo apt-get update - sudo apt-get install -y cmake build-essential libcurl4-openssl-dev libclang-dev - - run: | - pip3 install --break-system-packages huggingface-hub - pip3 install --break-system-packages -e cactus/python/ --no-deps - - uses: actions/cache@v4 - with: - path: cactus/weights/ - key: cactus-models-arm-v2 - - run: | - cactus download Cactus-Compute/LFM2-VL-450M - cactus download UsefulSensors/moonshine-base - - run: cargo test -p cactus --test vad - - run: cargo test -p cactus --test llm -- --ignored --nocapture - env: - CACTUS_LLM_MODEL: ${{ github.workspace }}/cactus/weights/LFM2-VL-450M - - run: cargo test -p cactus --test stt -- --ignored --nocapture - env: - CACTUS_STT_MODEL: ${{ github.workspace }}/cactus/weights/moonshine-base diff --git a/.github/workflows/chrome_cd.yaml b/.github/workflows/chrome_cd.yaml deleted file mode 100644 index 3bc79f606c..0000000000 --- a/.github/workflows/chrome_cd.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: chrome_cd - -on: - workflow_dispatch: - -jobs: - zip: - runs-on: depot-ubuntu-24.04-4 - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/pnpm_install - - run: pnpm -F @hypr/chrome zip - - uses: actions/upload-artifact@v4 - with: - name: chrome-extension-zip - path: apps/chrome/.output/*.zip - retention-days: 30 diff --git a/.github/workflows/chrome_ci.yaml b/.github/workflows/chrome_ci.yaml deleted file mode 100644 index 3276929ee2..0000000000 --- a/.github/workflows/chrome_ci.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: chrome_ci - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - apps/chrome/** - - pnpm-lock.yaml - - pnpm-workspace.yaml - - .github/actions/pnpm_install/** - - .github/workflows/chrome_ci.yaml - pull_request: - paths: - - apps/chrome/** - - pnpm-lock.yaml - - pnpm-workspace.yaml - - .github/actions/pnpm_install/** - - .github/workflows/chrome_ci.yaml - -jobs: - chrome_ci: - runs-on: depot-ubuntu-24.04-4 - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/pnpm_install - - run: pnpm -F @hypr/chrome typecheck - - run: pnpm -F @hypr/chrome build - - ci: - if: always() - needs: [chrome_ci] - runs-on: ubuntu-latest - steps: - - run: exit 1 - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 91f459da42..0000000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,7 +0,0 @@ -on: - pull_request: -jobs: - ci: - runs-on: ubuntu-latest - steps: - - run: echo "pass" diff --git a/.github/workflows/cli_desktop_ci.yml b/.github/workflows/cli_desktop_ci.yml deleted file mode 100644 index 79fc143136..0000000000 --- a/.github/workflows/cli_desktop_ci.yml +++ /dev/null @@ -1,29 +0,0 @@ -on: - pull_request: - branches: - - main - paths: - - "apps/cli/**" - - "crates/**" - - "Cargo.toml" - - "Cargo.lock" - - ".github/workflows/cli_desktop_ci.yml" - - ".github/actions/install_cli_deps/**" - -jobs: - check: - if: ${{ !startsWith(github.head_ref || '', 'blog/') }} - runs-on: macos-latest - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install_cli_deps - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - - run: cargo check -p cli --features desktop-macos - - run: cargo clippy -p cli --features desktop-macos - - run: cargo test -p cli --features desktop-macos diff --git a/.github/workflows/cli_standalone_cd.yaml b/.github/workflows/cli_standalone_cd.yaml deleted file mode 100644 index 6fba86d9bb..0000000000 --- a/.github/workflows/cli_standalone_cd.yaml +++ /dev/null @@ -1,211 +0,0 @@ -on: - workflow_dispatch: - inputs: - channel: - description: "Release channel" - required: true - type: choice - options: - - nightly - - stable - -concurrency: - group: ${{ github.workflow }}-${{ inputs.channel }} - cancel-in-progress: true - -jobs: - compute-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - run: git fetch --tags --force - - uses: ./.github/actions/doxxer_install - - id: version - run: | - if [[ "${{ inputs.channel }}" == "nightly" ]]; then - LATEST_TAG=$(git tag -l 'cli_v*' --sort=-creatordate | head -n1) - if [[ "$LATEST_TAG" == *"-nightly"* ]]; then - VERSION=$(doxxer --config doxxer.cli.toml next prerelease) - else - VERSION=$(doxxer --config doxxer.cli.toml next pre-patch) - fi - else - VERSION=$(doxxer --config doxxer.cli.toml next patch) - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Computed version: $VERSION" - - build-macos: - needs: compute-version - runs-on: depot-macos-15 - strategy: - matrix: - target: - - aarch64-apple-darwin - - x86_64-apple-darwin - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: macos - - id: apple-cert - uses: ./.github/actions/apple_cert - with: - apple-certificate: ${{ secrets.APPLE_CERTIFICATE }} - apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - - run: echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV - - run: cargo build --release -p cli --features standalone-macos --target ${{ matrix.target }} - env: - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} - APP_VERSION: ${{ needs.compute-version.outputs.version }} - - run: | - cd apps/cli-ui - swift build -c release --arch arm64 --arch x86_64 - cp .build/apple/Products/Release/char-cli-ui ../../target/${{ matrix.target }}/release/ - - run: | - BIN="target/${{ matrix.target }}/release/char" - UI_BIN="target/${{ matrix.target }}/release/char-cli-ui" - strip -x "$BIN" - codesign -s "${{ steps.apple-cert.outputs.cert-id }}" -f --identifier com.char.cli --options runtime "$BIN" - codesign -s "${{ steps.apple-cert.outputs.cert-id }}" -f --identifier com.char.cli-ui --options runtime "$UI_BIN" - codesign --verify --verbose=4 "$BIN" - codesign --verify --verbose=4 "$UI_BIN" - ARCHIVE="char-${{ needs.compute-version.outputs.version }}-${{ matrix.target }}.tar.xz" - tar -cJf "$ARCHIVE" -C "target/${{ matrix.target }}/release" char char-cli-ui - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: char-${{ matrix.target }} - path: ${{ env.ARCHIVE }} - retention-days: 3 - - build-linux: - needs: compute-version - runs-on: ${{ matrix.runner }} - strategy: - matrix: - include: - - target: x86_64-unknown-linux-gnu - runner: ubuntu-latest - - target: aarch64-unknown-linux-gnu - runner: ubuntu-24.04-arm - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - run: cargo build --release -p cli --features standalone-linux --target ${{ matrix.target }} - env: - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} - APP_VERSION: ${{ needs.compute-version.outputs.version }} - - run: | - BIN="target/${{ matrix.target }}/release/char" - strip "$BIN" - ARCHIVE="char-${{ needs.compute-version.outputs.version }}-${{ matrix.target }}.tar.xz" - tar -cJf "$ARCHIVE" -C "target/${{ matrix.target }}/release" char - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: char-${{ matrix.target }} - path: ${{ env.ARCHIVE }} - retention-days: 3 - - publish-npm: - needs: [compute-version, build-macos, build-linux] - runs-on: ubuntu-latest - permissions: - id-token: write - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: "22.14.0" - registry-url: "https://registry.npmjs.org" - - run: npm install -g npm@11.5.1 - - run: | - node -v - npm -v - - run: | - cd apps/cli/npm - VERSION="${{ needs.compute-version.outputs.version }}" - NPM_TAG="${{ inputs.channel == 'nightly' && 'nightly' || 'latest' }}" - node -e " - const fs = require('fs'); - const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); - pkg.version = '$VERSION'; - fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); - " - npm publish --tag "$NPM_TAG" - - create-tag: - needs: [compute-version, publish-npm] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: mathieudutour/github-tag-action@v6.2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - custom_tag: cli_v${{ needs.compute-version.outputs.version }} - tag_prefix: "" - - release: - needs: [compute-version, build-macos, build-linux, create-tag] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - run: git fetch --tags --force - - uses: actions/download-artifact@v4 - with: - path: artifacts - merge-multiple: true - - id: checksums - uses: ./.github/actions/generate_checksums - with: - files: | - artifacts/char-${{ needs.compute-version.outputs.version }}-aarch64-apple-darwin.tar.xz - artifacts/char-${{ needs.compute-version.outputs.version }}-x86_64-apple-darwin.tar.xz - artifacts/char-${{ needs.compute-version.outputs.version }}-x86_64-unknown-linux-gnu.tar.xz - artifacts/char-${{ needs.compute-version.outputs.version }}-aarch64-unknown-linux-gnu.tar.xz - - id: artifacts - run: | - LIST=$(ls artifacts/*.tar.xz artifacts/*.sha256 | paste -sd,) - echo "list=$LIST" >> $GITHUB_OUTPUT - - id: release-body - env: - VERSION: ${{ needs.compute-version.outputs.version }} - run: | - CURRENT_TAG="cli_v$VERSION" - PREV_TAG=$(git tag -l 'cli_v*' --sort=-v:refname | grep -v "^$CURRENT_TAG$" | head -n1) - - if [[ -n "$PREV_TAG" ]]; then - echo "value=https://github.com/${{ github.repository }}/compare/$PREV_TAG...$CURRENT_TAG" >> "$GITHUB_OUTPUT" - else - echo "value=https://github.com/${{ github.repository }}/commits/$CURRENT_TAG" >> "$GITHUB_OUTPUT" - fi - - uses: ncipollo/release-action@v1 - with: - tag: cli_v${{ needs.compute-version.outputs.version }} - name: cli_v${{ needs.compute-version.outputs.version }} - body: ${{ steps.release-body.outputs.value }} - prerelease: ${{ inputs.channel == 'nightly' }} - makeLatest: false - artifacts: ${{ steps.artifacts.outputs.list }} diff --git a/.github/workflows/cli_standalone_ci.yml b/.github/workflows/cli_standalone_ci.yml deleted file mode 100644 index 8f5b45361e..0000000000 --- a/.github/workflows/cli_standalone_ci.yml +++ /dev/null @@ -1,61 +0,0 @@ -on: - pull_request: - branches: - - main - paths: - - "apps/cli/**" - - "crates/**" - - "Cargo.toml" - - "Cargo.lock" - - ".github/workflows/cli_standalone_ci.yml" - - ".github/actions/install_cli_deps/**" - -jobs: - check: - if: ${{ !startsWith(github.head_ref || '', 'blog/') }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux - feature: standalone-linux - clippy: false - - os: macos-latest - platform: macos - feature: standalone-macos - clippy: true - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install_cli_deps - - if: matrix.clippy - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - if: ${{ !matrix.clippy }} - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo check -p cli --features ${{ matrix.feature }} - - if: matrix.clippy - run: cargo clippy -p cli --features ${{ matrix.feature }} - - if: runner.os == 'macOS' - run: | - rustup target add x86_64-apple-darwin - cargo check -p cli --features standalone-macos --target x86_64-apple-darwin - - e2e: - if: ${{ !startsWith(github.head_ref || '', 'blog/') }} - runs-on: macos-latest - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install_cli_deps - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo test -p cli --features standalone-macos,e2e -- --test-threads=1 diff --git a/.github/workflows/db_ci.yaml b/.github/workflows/db_ci.yaml index ae65edf0d5..f2f284b5df 100644 --- a/.github/workflows/db_ci.yaml +++ b/.github/workflows/db_ci.yaml @@ -6,9 +6,11 @@ on: - main paths: - supabase/** + - .github/workflows/db_ci.yaml pull_request: paths: - supabase/** + - .github/workflows/db_ci.yaml jobs: tests: diff --git a/.github/workflows/desktop_ci.yaml b/.github/workflows/desktop_ci.yaml index b4cc3315b3..ad3ccc49ed 100644 --- a/.github/workflows/desktop_ci.yaml +++ b/.github/workflows/desktop_ci.yaml @@ -6,17 +6,61 @@ on: - main paths: - apps/desktop/** + - packages/api-client/** + - packages/changelog/** + - packages/codemirror/** + - packages/db/** + - packages/db-react/** + - packages/db-runtime/** + - packages/db-tauri/** + - packages/editor/** + - packages/pricing/** + - packages/store/** + - packages/supabase/** + - packages/tinybase-utils/** + - packages/tiptap/** + - packages/ui/** + - packages/utils/** - plugins/** - crates/** - Cargo.toml - Cargo.lock + - package.json + - pnpm-workspace.yaml + - turbo.json + - .github/workflows/desktop_ci.yaml + - .github/actions/pnpm_install/** + - .github/actions/install_desktop_deps/** + - .github/actions/rust_install/** pull_request: paths: - apps/desktop/** + - packages/api-client/** + - packages/changelog/** + - packages/codemirror/** + - packages/db/** + - packages/db-react/** + - packages/db-runtime/** + - packages/db-tauri/** + - packages/editor/** + - packages/pricing/** + - packages/store/** + - packages/supabase/** + - packages/tinybase-utils/** + - packages/tiptap/** + - packages/ui/** + - packages/utils/** - plugins/** - crates/** - Cargo.toml - Cargo.lock + - package.json + - pnpm-workspace.yaml + - turbo.json + - .github/workflows/desktop_ci.yaml + - .github/actions/pnpm_install/** + - .github/actions/install_desktop_deps/** + - .github/actions/rust_install/** jobs: desktop_ci: if: ${{ !startsWith(github.head_ref || '', 'blog/') }} @@ -55,18 +99,13 @@ jobs: --exclude desktop \ --exclude control-tauri \ --exclude ai \ - --exclude email \ --exclude lago \ - --exclude mac \ --exclude notch \ --exclude notification-macos \ - --exclude notification-macos2 \ --exclude tcc \ - --exclude apple-note \ --exclude notification-linux \ --exclude am \ --exclude aec \ - --exclude agc \ --exclude whisper \ --exclude whisper-local \ --exclude whisper-local-model \ @@ -86,7 +125,6 @@ jobs: --exclude host \ --exclude intercept \ --exclude frontmatter \ - --exclude openstatus \ --exclude audio \ --exclude audio-device \ --exclude transcribe-whisper-local \ @@ -97,7 +135,6 @@ jobs: --exclude transcribe-cactus \ --exclude llm-cactus \ --exclude local-llm-core \ - --exclude local-stt-server \ --exclude tauri-plugin-analytics\ --exclude tauri-plugin-apple-calendar \ --exclude tauri-plugin-audio-priority \ @@ -136,7 +173,6 @@ jobs: --exclude tauri-plugin-tracing \ --exclude tauri-plugin-tray \ --exclude tauri-plugin-updater2 \ - --exclude tauri-plugin-webhook \ --exclude tauri-plugin-windows \ --exclude db3 \ --exclude db-core \ diff --git a/.github/workflows/desktop_publish.yaml b/.github/workflows/desktop_publish.yaml deleted file mode 100644 index cbffeceae9..0000000000 --- a/.github/workflows/desktop_publish.yaml +++ /dev/null @@ -1,162 +0,0 @@ -on: - workflow_dispatch: - inputs: - version: - description: "Version to publish (e.g., 1.0.2 or 1.0.2-nightly.15)" - required: true - type: string - -env: - CN_APPLICATION: "fastrepl/hyprnote2" - -jobs: - parse: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.parse.outputs.version }} - channel: ${{ steps.parse.outputs.channel }} - steps: - - id: parse - run: | - VERSION="${{ inputs.version }}" - if [[ "$VERSION" == *"-nightly"* ]]; then - CHANNEL="nightly" - else - CHANNEL="stable" - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "channel=$CHANNEL" >> $GITHUB_OUTPUT - - cn-publish: - needs: parse - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: ./scripts/version.sh "./apps/desktop/src-tauri/tauri.conf.json" "${{ needs.parse.outputs.version }}" - - id: existing-release - continue-on-error: true - uses: ./.github/actions/cn_release - with: - raw: >- - release show ${{ env.CN_APPLICATION }} ${{ needs.parse.outputs.version }} - --channel ${{ needs.parse.outputs.channel }} - key: ${{ secrets.CN_API_KEY }} - - id: publish-decision - env: - ASSETS: ${{ steps.existing-release.outputs.raw }} - run: | - if [[ "${{ steps.existing-release.outcome }}" != "success" ]]; then - echo "publish_needed=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - EXPECTED_PLATFORMS=( - "dmg-aarch64" - "dmg-x86_64" - ) - - MISSING_PLATFORMS=() - for platform in "${EXPECTED_PLATFORMS[@]}"; do - if ! echo "$ASSETS" | jq -e --arg platform "$platform" '.assets[] | select(.publicPlatform == $platform)' > /dev/null; then - MISSING_PLATFORMS+=("$platform") - fi - done - - if [[ "${#MISSING_PLATFORMS[@]}" -gt 0 ]]; then - echo "publish_needed=true" >> "$GITHUB_OUTPUT" - echo "missing_platforms=${MISSING_PLATFORMS[*]}" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "publish_needed=false" >> "$GITHUB_OUTPUT" - - uses: ./.github/actions/cn_release - if: ${{ steps.publish-decision.outputs.publish_needed == 'true' }} - with: - cmd: publish - app: ${{ env.CN_APPLICATION }} - key: ${{ secrets.CN_API_KEY }} - channel: ${{ needs.parse.outputs.channel }} - framework: tauri - working-directory: ./apps/desktop - - gh-release: - needs: [parse, cn-publish] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - run: git fetch --tags --force - - id: download-macos-aarch64 - uses: ./.github/actions/cn_download - with: - app: ${{ env.CN_APPLICATION }} - version: ${{ needs.parse.outputs.version }} - channel: ${{ needs.parse.outputs.channel }} - platform: dmg-aarch64 - output: char-macos-aarch64.dmg - key: ${{ secrets.CN_API_KEY }} - - id: download-macos-x86_64 - uses: ./.github/actions/cn_download - with: - app: ${{ env.CN_APPLICATION }} - version: ${{ needs.parse.outputs.version }} - channel: ${{ needs.parse.outputs.channel }} - platform: dmg-x86_64 - output: char-macos-x86_64.dmg - key: ${{ secrets.CN_API_KEY }} - - id: checksums - uses: ./.github/actions/generate_checksums - with: - files: | - char-macos-aarch64.dmg - char-macos-x86_64.dmg - - id: release-body - env: - CHANNEL: ${{ needs.parse.outputs.channel }} - VERSION: ${{ needs.parse.outputs.version }} - run: | - if [[ "$CHANNEL" != "nightly" ]]; then - echo "value=https://char.com/changelog/$VERSION" >> "$GITHUB_OUTPUT" - exit 0 - fi - - CURRENT_TAG="desktop_v$VERSION" - PREV_TAG="" - LAST_TAG="" - LAST_NIGHTLY_TAG="" - - while IFS= read -r tag; do - if [[ "$tag" == "$CURRENT_TAG" ]]; then - PREV_TAG="$LAST_NIGHTLY_TAG" - if [[ -z "$PREV_TAG" ]]; then - PREV_TAG="$LAST_TAG" - fi - break - fi - - if [[ "$tag" == *"-nightly."* ]]; then - LAST_NIGHTLY_TAG="$tag" - fi - - LAST_TAG="$tag" - done < <(git tag -l 'desktop_v*' --sort=v:refname) - - if [[ -n "$PREV_TAG" ]]; then - echo "value=https://github.com/${{ github.repository }}/compare/$PREV_TAG...$CURRENT_TAG" >> "$GITHUB_OUTPUT" - else - echo "value=https://github.com/${{ github.repository }}/commits/${{ github.sha }}" >> "$GITHUB_OUTPUT" - fi - - uses: ncipollo/release-action@v1 - with: - tag: desktop_v${{ needs.parse.outputs.version }} - name: desktop_v${{ needs.parse.outputs.version }} - body: ${{ steps.release-body.outputs.value }} - prerelease: ${{ needs.parse.outputs.channel == 'nightly' }} - artifacts: char-macos-aarch64.dmg,char-macos-x86_64.dmg,${{ steps.checksums.outputs.checksum_files }} - allowUpdates: true - makeLatest: ${{ needs.parse.outputs.channel == 'nightly' && 'false' || 'true' }} - replacesArtifacts: true diff --git a/.github/workflows/download_staging.yaml b/.github/workflows/download_staging.yaml deleted file mode 100644 index 7c29c33cd5..0000000000 --- a/.github/workflows/download_staging.yaml +++ /dev/null @@ -1,45 +0,0 @@ -on: - workflow_dispatch: - inputs: - platform: - description: "Platform to download" - required: true - type: choice - options: - - macos-aarch64 - -jobs: - download-staging: - runs-on: ubuntu-latest - steps: - - name: Download latest staging build from R2 - run: | - PLATFORM="${{ inputs.platform }}" - - LATEST_FILE=$(aws s3 ls "s3://hyprnote-build/desktop/staging/" \ - --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ - --region auto | grep "hyprnote-staging-.*-${PLATFORM}" | sort -r | head -1 | awk '{print $4}') - - if [[ -z "$LATEST_FILE" ]]; then - echo "No staging build found for platform: $PLATFORM" - exit 1 - fi - - echo "Latest staging build: $LATEST_FILE" - - aws s3 cp "s3://hyprnote-build/desktop/staging/$LATEST_FILE" \ - "./$LATEST_FILE" \ - --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ - --region auto - - echo "Downloaded: $LATEST_FILE" - ls -lh "$LATEST_FILE" - env: - AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} - - - uses: actions/upload-artifact@v4 - with: - name: hyprnote-staging-${{ inputs.platform }} - path: hyprnote-staging-* - retention-days: 7 diff --git a/.github/workflows/extensions_cd.yaml b/.github/workflows/extensions_cd.yaml deleted file mode 100644 index fe841a97b4..0000000000 --- a/.github/workflows/extensions_cd.yaml +++ /dev/null @@ -1,22 +0,0 @@ -on: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: depot-ubuntu-24.04-8 - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - working-directory: ./extensions - run: deno task build - - uses: actions/upload-artifact@v4 - with: - name: extensions - path: extensions/*/dist/ - retention-days: 7 diff --git a/.github/workflows/fmt.yaml b/.github/workflows/fmt.yaml index 80d041e3e6..b991481a89 100644 --- a/.github/workflows/fmt.yaml +++ b/.github/workflows/fmt.yaml @@ -4,8 +4,55 @@ on: push: branches: - main - - .github/workflows/fmt.yaml + paths: + - "**/*.css" + - "**/*.html" + - "**/*.jinja" + - "**/*.js" + - "**/*.json" + - "**/*.jsx" + - "**/*.md" + - "**/*.mdx" + - "**/*.mjs" + - "**/*.mts" + - "**/*.rs" + - "**/*.swift" + - "**/*.toml" + - "**/*.ts" + - "**/*.tsx" + - "**/*.wxl" + - "**/*.wxs" + - "**/*.yaml" + - "**/*.yml" + - "!**/*-lock.json" + - "!**/*-lock.yaml" + - dprint.json + - .oxfmtrc.json pull_request: + paths: + - "**/*.css" + - "**/*.html" + - "**/*.jinja" + - "**/*.js" + - "**/*.json" + - "**/*.jsx" + - "**/*.md" + - "**/*.mdx" + - "**/*.mjs" + - "**/*.mts" + - "**/*.rs" + - "**/*.swift" + - "**/*.toml" + - "**/*.ts" + - "**/*.tsx" + - "**/*.wxl" + - "**/*.wxs" + - "**/*.yaml" + - "**/*.yml" + - "!**/*-lock.json" + - "!**/*-lock.yaml" + - dprint.json + - .oxfmtrc.json jobs: fmt: runs-on: depot-ubuntu-24.04-4 diff --git a/.github/workflows/legacy_desktop_cd.yaml b/.github/workflows/legacy_desktop_cd.yaml deleted file mode 100644 index 10b440dade..0000000000 --- a/.github/workflows/legacy_desktop_cd.yaml +++ /dev/null @@ -1,187 +0,0 @@ -on: - workflow_dispatch: - inputs: - legacy_ref: - description: "Legacy tag or commit to build, for example desktop_v0.0.84" - required: true - type: string - channel: - description: "Release channel" - required: true - type: choice - options: - - stable - - nightly - publish: - description: "Publish to CrabNebula after upload" - required: true - type: boolean - default: true - experimental_airgapped: - description: "experimental_airgapped" - required: false - type: boolean - default: false - -run-name: Legacy Desktop CD (${{ inputs.channel }} / ${{ inputs.legacy_ref }}) - -concurrency: - group: legacy-desktop-cd-${{ inputs.legacy_ref }}-${{ inputs.channel }} - cancel-in-progress: true - -env: - CN_APPLICATION: "fastrepl/hyprnote" - RELEASE_CHANNEL: ${{ inputs.channel }} - TAURI_CONF_PATH: ./src-tauri/tauri.conf.${{ inputs.channel }}.json - NODE_OPTIONS: "--max-old-space-size=4096" - -jobs: - build: - permissions: - contents: write - runs-on: macos-14 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ inputs.legacy_ref }} - lfs: true - fetch-depth: 0 - fetch-tags: true - - - name: Validate ref against app version - run: | - VERSION=$(jq -r '.version' ./apps/desktop/src-tauri/tauri.conf.json) - REF="${{ inputs.legacy_ref }}" - echo "Building ref=$REF version=$VERSION channel=${{ inputs.channel }}" - if [[ ! "$REF" == *"$VERSION"* ]]; then - echo "::error::legacy_ref ($REF) does not include app version ($VERSION)" - exit 1 - fi - - - if: ${{ inputs.publish }} - uses: ./.github/actions/cn_release - with: - cmd: draft - app: ${{ env.CN_APPLICATION }} - key: ${{ secrets.CN_API_KEY }} - channel: ${{ env.RELEASE_CHANNEL }} - framework: tauri - working-directory: ./apps/desktop - - - uses: ./.github/actions/setup_protoc - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - uses: ./.github/actions/install_desktop_deps - with: - target: macos - - - uses: ./.github/actions/rust_install - with: - platform: macos - - - uses: ./.github/actions/pnpm_install - - - uses: ./.github/actions/poetry_install - - - run: poetry run python scripts/pre_build.py - - - run: pnpm -F desktop lingui:compile - - - run: pnpm -F ui build - - - uses: ./.github/actions/apple_cert - id: apple-cert - with: - apple-certificate: ${{ secrets.APPLE_CERTIFICATE }} - apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - - - if: ${{ inputs.experimental_airgapped }} - run: | - aws s3 cp s3://hyprnote-cache2/v0/resources/stt/nvidia_parakeet-v3_494MB.tar apps/desktop/src-tauri/resources/stt/nvidia_parakeet-v3_494MB.tar \ - --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ - --region auto - aws s3 cp s3://hyprnote-cache2/v0/resources/ttt/hypr-llm.gguf apps/desktop/src-tauri/resources/ttt/hypr-llm.gguf \ - --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ - --region auto - env: - AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} - - - run: | - aws s3 cp s3://argmax/stt apps/desktop/src-tauri/binaries/stt-aarch64-apple-darwin \ - --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ - --region auto - env: - AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} - - - run: | - chmod +x ./apps/desktop/src-tauri/binaries/stt-aarch64-apple-darwin - ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "binaries/stt" - - - run: | - pnpm -F desktop tauri build --target aarch64-apple-darwin --config ${{ env.TAURI_CONF_PATH }} --verbose - env: - CI: false - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AM_API_KEY: ${{ secrets.AM_API_KEY }} - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - KEYGEN_ACCOUNT_ID: ${{ secrets.KEYGEN_ACCOUNT_ID }} - KEYGEN_VERIFY_KEY: ${{ secrets.KEYGEN_VERIFY_KEY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - 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_IDENTITY: ${{ steps.apple-cert.outputs.cert-id }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - - - run: | - mkdir -p target/release/ - find target/aarch64-apple-darwin/release -type f -not -path "*/\.*" -exec cp {} target/release/ \; - working-directory: ./apps/desktop/src-tauri - - - uses: actions/upload-artifact@v4 - with: - name: legacy-desktop-${{ inputs.channel }}-${{ github.run_id }} - path: apps/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg - retention-days: 7 - - - if: ${{ inputs.publish }} - uses: ./.github/actions/cn_release - with: - cmd: upload - app: ${{ env.CN_APPLICATION }} - key: ${{ secrets.CN_API_KEY }} - channel: ${{ env.RELEASE_CHANNEL }} - framework: tauri - working-directory: ./apps/desktop - - publish: - if: ${{ inputs.publish && needs.build.result == 'success' }} - needs: build - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ inputs.legacy_ref }} - lfs: true - fetch-depth: 0 - fetch-tags: true - - - uses: ./.github/actions/cn_release - with: - cmd: publish - app: ${{ env.CN_APPLICATION }} - key: ${{ secrets.CN_API_KEY }} - channel: ${{ env.RELEASE_CHANNEL }} - framework: tauri - working-directory: ./apps/desktop diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6e852adcef..49f5690c99 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -4,7 +4,21 @@ on: push: branches: - main + paths: + - apps/desktop/src/** + - apps/desktop/tsconfig*.json + - .oxlintrc.json + - eslint.config.js + - eslint-plugin-hypr.mjs + - .github/workflows/lint.yaml pull_request: + paths: + - apps/desktop/src/** + - apps/desktop/tsconfig*.json + - .oxlintrc.json + - eslint.config.js + - eslint-plugin-hypr.mjs + - .github/workflows/lint.yaml jobs: lint: runs-on: depot-ubuntu-24.04-4 diff --git a/.github/workflows/llm_e2e.yaml b/.github/workflows/llm_e2e.yaml deleted file mode 100644 index d72a6d234d..0000000000 --- a/.github/workflows/llm_e2e.yaml +++ /dev/null @@ -1,37 +0,0 @@ -on: - workflow_dispatch: - push: - branches: - - main - paths: - - crates/llm-proxy/** - pull_request: - paths: - - crates/llm-proxy/** - -jobs: - proxy-non-streaming: - runs-on: depot-ubuntu-24.04-8 - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - uses: ./.github/actions/infisical_install - - run: infisical run --token="$INFISICAL_TOKEN" --env=dev --projectId="$INFISICAL_PROJECT_ID" --path="/llm" -- cargo test -p llm-proxy proxy_e2e::openrouter::non_streaming -- --ignored --nocapture - env: - INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} - INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} - - proxy-streaming: - runs-on: depot-ubuntu-24.04-8 - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - uses: ./.github/actions/infisical_install - - run: infisical run --token="$INFISICAL_TOKEN" --env=dev --projectId="$INFISICAL_PROJECT_ID" --path="/llm" -- cargo test -p llm-proxy proxy_e2e::openrouter::streaming -- --ignored --nocapture - env: - INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} - INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} diff --git a/.github/workflows/local_stt_e2e.yaml b/.github/workflows/local_stt_e2e.yaml deleted file mode 100644 index 2d6f281e8e..0000000000 --- a/.github/workflows/local_stt_e2e.yaml +++ /dev/null @@ -1,67 +0,0 @@ -on: - workflow_dispatch: - inputs: - sha: - description: Commit SHA to test - required: false - type: string - cactus_version: - description: Cactus version to test - required: false - default: "1.11" - type: string - push: - branches: [main] - paths: - - crates/transcribe-cactus/** - - crates/cactus/** - - crates/cactus-sys/** - pull_request: - paths: - - crates/transcribe-cactus/** - - crates/cactus/** - - crates/cactus-sys/** - -jobs: - local-stt-e2e: - runs-on: depot-ubuntu-24.04-arm-8 - strategy: - matrix: - model: - - name: whisper-small - repo: openai/whisper-small - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - run: | - if [[ -n "${INPUT_SHA:-}" ]]; then - git checkout "$INPUT_SHA" - fi - env: - INPUT_SHA: ${{ inputs.sha }} - - run: git clone --depth 1 --branch "v${CACTUS_VERSION}" https://github.com/cactus-compute/cactus.git cactus - env: - CACTUS_VERSION: ${{ inputs.cactus_version || '1.11' }} - - uses: ./.github/actions/rust_install - with: - platform: linux - - run: | - sudo apt-get update - sudo apt-get install -y cmake build-essential libcurl4-openssl-dev libclang-dev libpipewire-0.3-dev libpulse-dev - - run: | - pip3 install --break-system-packages huggingface-hub - pip3 install --break-system-packages -e cactus/python/ --no-deps - - uses: actions/cache@v4 - with: - path: cactus/weights/ - key: cactus-models-${{ matrix.model.name }}-arm-v${{ inputs.cactus_version || '1.11' }} - - run: cactus download ${{ matrix.model.repo }} - - run: cargo test -p transcribe-cactus -- --ignored --nocapture --skip snapshot - env: - CACTUS_STT_MODEL: ${{ github.workspace }}/cactus/weights/${{ matrix.model.name }} - CACTUS_CLOUD_API_KEY: ${{ secrets.CACTUS_CLOUD_API_KEY }} - E2E_AUDIO_SECS: 20 diff --git a/.github/workflows/mobile_bridge_ci.yaml b/.github/workflows/mobile_bridge_ci.yaml deleted file mode 100644 index 087541b647..0000000000 --- a/.github/workflows/mobile_bridge_ci.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: mobile_ci - -on: - pull_request: - paths: - - apps/mobile/** - - crates/mobile-bridge/** - - crates/uniffi-bindgen/** - - crates/xtask/** - - .github/actions/pnpm_install/** - - package.json - - pnpm-lock.yaml - - pnpm-workspace.yaml - - .cargo/config.toml - - Cargo.toml - - Cargo.lock - -jobs: - mobile_ci: - runs-on: depot-macos-15 - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/pnpm_install - - uses: ./.github/actions/rust_install - with: - platform: ios - - run: cargo check -p mobile-bridge -p uniffi-bindgen -p xtask - - run: cargo xtask mobile-bridge - - run: | - test -f apps/mobile/cpp/generated/mobile_bridge.cpp - test -f apps/mobile/cpp/generated/mobile_bridge.hpp - test -f apps/mobile/src/generated/mobile_bridge.ts - test -f apps/mobile/src/generated/mobile_bridge-ffi.ts - test -f apps/mobile/src/bridge/index.ts - test -f apps/mobile/src/bridge/NativeMobile.ts diff --git a/.github/workflows/openstatus.yaml b/.github/workflows/openstatus.yaml deleted file mode 100644 index ebdc1fbda9..0000000000 --- a/.github/workflows/openstatus.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: openstatus - -on: - push: - branches: - - main - paths: - - openstatus.yaml - - .github/workflows/openstatus.yaml - -jobs: - sync: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: Homebrew/actions/setup-homebrew@master - - run: brew install openstatusHQ/cli/openstatus --cask - - run: openstatus monitors apply --access-token ${{ secrets.OPENSTATUS_API_KEY }} -y diff --git a/.github/workflows/sign_passthrough.yaml b/.github/workflows/sign_passthrough.yaml deleted file mode 100644 index 072b0039fa..0000000000 --- a/.github/workflows/sign_passthrough.yaml +++ /dev/null @@ -1,41 +0,0 @@ -on: - workflow_dispatch: - -jobs: - sign-passthrough: - runs-on: macos-14 - steps: - - uses: actions/checkout@v4 - - - run: mkdir -p build - - - uses: ./.github/actions/apple_cert - id: apple-cert - with: - apple-certificate: ${{ secrets.APPLE_CERTIFICATE }} - apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - - - name: Compile and sign passthrough binary - env: - CERT_ID: ${{ steps.apple-cert.outputs.cert-id }} - run: | - swiftc scripts/passthrough.swift -o build/hyprnote-passthrough - codesign -s "$CERT_ID" build/hyprnote-passthrough - - # Verify signing - codesign -dv build/hyprnote-passthrough - - # Verify team identifier is correct - if ! codesign -dv build/hyprnote-passthrough 2>&1 | grep -q "TeamIdentifier=6SLY7V277V"; then - echo "Error: Binary not signed with correct team identifier (6SLY7V277V)" - exit 1 - fi - echo "✓ Binary signed with correct team identifier" - - - uses: actions/upload-artifact@v4 - with: - name: hyprnote-passthrough-${{ github.sha }} - path: | - build/hyprnote-passthrough - retention-days: 7 diff --git a/.github/workflows/slack_internal_cd.yaml b/.github/workflows/slack_internal_cd.yaml deleted file mode 100644 index 4a03aa0a88..0000000000 --- a/.github/workflows/slack_internal_cd.yaml +++ /dev/null @@ -1,31 +0,0 @@ -on: - workflow_dispatch: - push: - paths: - - "apps/slack-internal/**" - - "packages/agent-support/**" - - "packages/agent-designer/**" - branches: - - main - -env: - AGENT_CHANNEL_MAP: >- - { - "C0AB7TVP9B5": "designer", - "C0AA8650ERE": "internal" - } - -jobs: - deploy: - runs-on: ubuntu-latest - timeout-minutes: 60 - concurrency: slack-internal-fly-deploy - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl secrets set AGENT_CHANNEL_MAP='${{ env.AGENT_CHANNEL_MAP }}' --stage --app hyprnote-slack-internal - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - - run: flyctl deploy --config apps/slack-internal/fly.toml --remote-only --build-arg GIT_SHA=${{ github.sha }} - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/stripe_cd.yaml b/.github/workflows/stripe_cd.yaml deleted file mode 100644 index 3e2fe680d4..0000000000 --- a/.github/workflows/stripe_cd.yaml +++ /dev/null @@ -1,45 +0,0 @@ -on: - workflow_dispatch: - -jobs: - compute-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - run: git fetch --tags --force - - uses: ./.github/actions/doxxer_install - - id: version - run: | - VERSION=$(doxxer --config doxxer.stripe.toml next patch) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Computed version: $VERSION" - - deploy: - needs: compute-version - runs-on: depot-ubuntu-24.04-8 - timeout-minutes: 60 - concurrency: stripe-fly-deploy - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --config apps/stripe/fly.toml --dockerfile apps/stripe/Dockerfile --remote-only - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - - tag: - needs: [compute-version, deploy] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: mathieudutour/github-tag-action@v6.2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - custom_tag: stripe_v${{ needs.compute-version.outputs.version }} - tag_prefix: "" diff --git a/.github/workflows/stt_e2e.yaml b/.github/workflows/stt_e2e.yaml deleted file mode 100644 index 77534e3dc5..0000000000 --- a/.github/workflows/stt_e2e.yaml +++ /dev/null @@ -1,206 +0,0 @@ -on: - workflow_dispatch: - inputs: - provider: - type: choice - options: - - deepgram - - assemblyai - - soniox - - gladia - - fireworks - - openai - - elevenlabs - push: - branches: - - main - paths: - - crates/transcribe-proxy/** - - crates/owhisper-client/src/adapter/** - pull_request: - paths: - - crates/transcribe-proxy/** - - crates/owhisper-client/src/adapter/** - -jobs: - detect-changes: - if: github.event_name != 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - providers: ${{ steps.detect.outputs.providers }} - steps: - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - transcribe_proxy: - - 'crates/transcribe-proxy/**' - deepgram: - - 'crates/owhisper-client/src/adapter/deepgram/**' - assemblyai: - - 'crates/owhisper-client/src/adapter/assemblyai/**' - soniox: - - 'crates/owhisper-client/src/adapter/soniox/**' - gladia: - - 'crates/owhisper-client/src/adapter/gladia/**' - fireworks: - - 'crates/owhisper-client/src/adapter/fireworks/**' - openai: - - 'crates/owhisper-client/src/adapter/openai/**' - elevenlabs: - - 'crates/owhisper-client/src/adapter/elevenlabs/**' - - id: detect - run: | - set -euo pipefail - - providers=() - - if [ "${{ steps.filter.outputs.deepgram }}" == "true" ]; then providers+=("deepgram"); fi - if [ "${{ steps.filter.outputs.assemblyai }}" == "true" ]; then providers+=("assemblyai"); fi - if [ "${{ steps.filter.outputs.soniox }}" == "true" ]; then providers+=("soniox"); fi - if [ "${{ steps.filter.outputs.gladia }}" == "true" ]; then providers+=("gladia"); fi - if [ "${{ steps.filter.outputs.fireworks }}" == "true" ]; then providers+=("fireworks"); fi - if [ "${{ steps.filter.outputs.openai }}" == "true" ]; then providers+=("openai"); fi - if [ "${{ steps.filter.outputs.elevenlabs }}" == "true" ]; then providers+=("elevenlabs"); fi - - if [ ${#providers[@]} -eq 0 ]; then - if [ "${{ steps.filter.outputs.transcribe_proxy }}" == "true" ]; then - providers=("deepgram" "soniox") - else - providers=("deepgram") - fi - fi - - json="$(printf '%s\n' "${providers[@]}" | jq -Rsc 'split("\n")[:-1] | unique')" - echo "providers=$json" >> "$GITHUB_OUTPUT" - - setup: - runs-on: ubuntu-latest - needs: [detect-changes] - if: always() - outputs: - providers: ${{ steps.set.outputs.providers }} - steps: - - id: set - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo 'providers=["${{ inputs.provider }}"]' >> $GITHUB_OUTPUT - else - echo "providers=${{ needs.detect-changes.outputs.providers }}" >> $GITHUB_OUTPUT - fi - - direct-provider-batch-e2e: - needs: setup - if: needs.setup.outputs.providers != '' - runs-on: depot-ubuntu-24.04-8 - strategy: - fail-fast: false - matrix: - provider: ${{ fromJson(needs.setup.outputs.providers) }} - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - uses: ./.github/actions/infisical_install - - run: | - infisical run --token="$INFISICAL_TOKEN" --env=dev --projectId="$INFISICAL_PROJECT_ID" --path="/stt" -- bash -lc ' - set -euo pipefail - - provider="${PROVIDER}" - - case "$provider" in - deepgram) - : "${DEEPGRAM_API_KEY:?DEEPGRAM_API_KEY is required}" - ;; - assemblyai) - : "${ASSEMBLYAI_API_KEY:?ASSEMBLYAI_API_KEY is required}" - ;; - soniox) - : "${SONIOX_API_KEY:?SONIOX_API_KEY is required}" - ;; - gladia) - : "${GLADIA_API_KEY:?GLADIA_API_KEY is required}" - ;; - fireworks) - : "${FIREWORKS_API_KEY:?FIREWORKS_API_KEY is required}" - ;; - openai) - : "${OPENAI_API_KEY:?OPENAI_API_KEY is required}" - ;; - elevenlabs) - : "${ELEVENLABS_API_KEY:?ELEVENLABS_API_KEY is required}" - ;; - *) - echo "Unknown provider: $provider" - exit 1 - ;; - esac - - echo "Running direct provider batch e2e for $provider" - cargo test -p owhisper-client --test provider_batch_e2e "direct_batch::${provider}" -- --ignored --nocapture - ' - env: - PROVIDER: ${{ matrix.provider }} - INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} - INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} - - proxy-provider-e2e: - needs: setup - if: needs.setup.outputs.providers != '' - runs-on: depot-ubuntu-24.04-8 - strategy: - fail-fast: false - matrix: - provider: ${{ fromJson(needs.setup.outputs.providers) }} - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/rust_install - with: - platform: linux - - uses: ./.github/actions/infisical_install - - run: | - infisical run --token="$INFISICAL_TOKEN" --env=dev --projectId="$INFISICAL_PROJECT_ID" --path="/stt" -- bash -lc ' - set -euo pipefail - - provider="${PROVIDER}" - - case "$provider" in - deepgram) - : "${DEEPGRAM_API_KEY:?DEEPGRAM_API_KEY is required}" - ;; - assemblyai) - : "${ASSEMBLYAI_API_KEY:?ASSEMBLYAI_API_KEY is required}" - ;; - soniox) - : "${SONIOX_API_KEY:?SONIOX_API_KEY is required}" - ;; - gladia) - : "${GLADIA_API_KEY:?GLADIA_API_KEY is required}" - ;; - fireworks) - : "${FIREWORKS_API_KEY:?FIREWORKS_API_KEY is required}" - ;; - openai) - : "${OPENAI_API_KEY:?OPENAI_API_KEY is required}" - ;; - elevenlabs) - : "${ELEVENLABS_API_KEY:?ELEVENLABS_API_KEY is required}" - ;; - *) - echo "Unknown provider: $provider" - exit 1 - ;; - esac - - echo "Running transcribe-proxy provider e2e tests for $provider" - - for suite in passthrough::live passthrough::batch hyprnote::live hyprnote::batch; do - cargo test -p transcribe-proxy --test proxy_provider_e2e "${suite}::${provider}" -- --ignored --nocapture - done - ' - env: - PROVIDER: ${{ matrix.provider }} - INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} - INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} diff --git a/.github/workflows/submit_flathub.yaml b/.github/workflows/submit_flathub.yaml deleted file mode 100644 index 08caf13ce0..0000000000 --- a/.github/workflows/submit_flathub.yaml +++ /dev/null @@ -1,12 +0,0 @@ -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup_flathub - - - name: Build Flatpak - run: flatpak-builder --user --force-clean build-dir flatpak/com.hyprnote.app.yml diff --git a/.github/workflows/web_ci.yaml b/.github/workflows/web_ci.yaml index 6b857c0808..0a17395d78 100644 --- a/.github/workflows/web_ci.yaml +++ b/.github/workflows/web_ci.yaml @@ -1,6 +1,5 @@ name: web_ci -# https://github.com/tauri-apps/tauri-action/blob/3013cac/examples/test-build-only.yml on: workflow_dispatch: push: @@ -8,24 +7,42 @@ on: - main paths: - apps/web/** - - packages/** + - packages/api-client/** + - packages/changelog/** + - packages/pricing/** + - packages/supabase/** + - packages/tiptap/** + - packages/ui/** + - packages/utils/** + - plugins/auth/** + - plugins/deeplink2/** + - package.json + - pnpm-workspace.yaml + - turbo.json + - .github/workflows/web_ci.yaml + - .github/actions/pnpm_install/** pull_request: paths: - apps/web/** - - packages/** + - packages/api-client/** + - packages/changelog/** + - packages/pricing/** + - packages/supabase/** + - packages/tiptap/** + - packages/ui/** + - packages/utils/** + - plugins/auth/** + - plugins/deeplink2/** + - package.json + - pnpm-workspace.yaml + - turbo.json + - .github/workflows/web_ci.yaml + - .github/actions/pnpm_install/** jobs: ci: runs-on: depot-ubuntu-24.04-4 steps: - uses: actions/checkout@v4 - uses: ./.github/actions/pnpm_install - - uses: denoland/setup-deno@v2 - run: pnpm -F ui build - run: pnpm -F web typecheck - - uses: ./.github/actions/wait-for-netlify-preview - if: github.event_name == 'pull_request' - - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: echo "BASE_URL=https://hyprnote.netlify.app" >> "$GITHUB_ENV" - - run: pnpm exec playwright install --with-deps chromium - working-directory: apps/web - - run: pnpm -F web test diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index ae846d6dfa..bc3becb332 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -1,7 +1,13 @@ on: push: branches: ["main"] + paths: + - .github/workflows/** + - .github/actions/** pull_request: + paths: + - .github/workflows/** + - .github/actions/** jobs: zizmor: diff --git a/.gitignore b/.gitignore index c84f02edfe..20174252c0 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ internal # TypeScript incremental build cache *.tsbuildinfo +# Agent scratch / reference material (not for commit) +.tmp/ + diff --git a/.grit/.gitignore b/.grit/.gitignore deleted file mode 100644 index e4fdfb17c1..0000000000 --- a/.grit/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.gritmodules* -*.log diff --git a/.grit/grit.yaml b/.grit/grit.yaml deleted file mode 100644 index 680fe6b831..0000000000 --- a/.grit/grit.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: 0.0.2 -patterns: - - name: compile_time_env_only - level: warn - body: | - language rust - - `std::env::var($key)` => `env!($key)` where { - $filename <: or { - includes "src-tauri" - } - } - - name: no_env_in_crate - level: warn - body: | - language rust - - `std::env::var($key)` where { - $filename <: and { - includes "crates" - } - } diff --git a/Cargo.lock b/Cargo.lock index fb698a2033..92a225843b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,90 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "aardvark-sys" -version = "0.1.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" -dependencies = [ - "libloading 0.8.9", - "thiserror 2.0.18", -] - -[[package]] -name = "activity-capture" -version = "0.1.0" -dependencies = [ - "activity-capture-interface", - "activity-capture-macos", - "activity-capture-windows", - "screen-core", - "serde", - "thiserror 2.0.18", - "uuid", -] - -[[package]] -name = "activity-capture-dev" -version = "0.1.0" -dependencies = [ - "activity-capture", - "axum 0.8.9", - "chrono", - "clap", - "crossterm", - "futures-util", - "llm-cactus", - "ratatui", - "reqwest 0.13.2", - "screen-core", - "serde", - "serde_json", - "tokio", - "url", -] - -[[package]] -name = "activity-capture-interface" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "futures-core", - "schemars 1.2.1", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "url", -] - -[[package]] -name = "activity-capture-macos" -version = "0.1.0" -dependencies = [ - "activity-capture-interface", - "block2", - "futures-core", - "objc2", - "objc2-app-kit", - "objc2-application-services", - "objc2-core-foundation", - "objc2-foundation", - "tokio", - "tokio-stream", - "url", -] - -[[package]] -name = "activity-capture-windows" -version = "0.1.0" -dependencies = [ - "activity-capture-interface", - "sysinfo", - "windows 0.62.2", -] - [[package]] name = "actix-codec" version = "0.5.2" @@ -255,16 +171,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.6", - "generic-array", -] - [[package]] name = "aec" version = "0.1.0" @@ -302,18 +208,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "agc" -version = "0.1.0" -dependencies = [ - "audio-utils", - "dagc", - "data", - "hound", - "rodio", - "vad-masking", -] - [[package]] name = "agent-core" version = "0.1.0" @@ -423,22 +317,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "amp" -version = "0.1.0" -dependencies = [ - "cli-process", - "dirs 6.0.0", - "futures-util", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", -] - [[package]] name = "analytics" version = "0.1.0" @@ -584,6838 +462,6457 @@ checksum = "623a80caf0a084acf22cc3f43e49b984214e1c00b79779b16683ab55d46fa764" dependencies = [ "apalis-core", "chrono", - "cron 0.16.0", + "cron", "futures-util", "serde", "ulid", ] [[package]] -name = "api" +name = "api-client" version = "0.1.0" dependencies = [ - "analytics", - "api-auth", - "api-cactus", - "api-calendar", - "api-env", - "api-mail", - "api-nango", - "api-pyannote", - "api-research", - "api-subscription", - "api-support", - "api-ticket", - "axum 0.8.9", - "dotenvy", - "envy", - "governor", - "jsonwebtoken", - "linear", - "llm-proxy", - "observability", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry_sdk", - "owhisper-client", + "chrono", + "google-calendar", + "outlook-calendar", + "progenitor-client 0.13.0", + "progenitor-utils", "reqwest 0.13.2", - "rustls 0.23.38", - "sentry", "serde", "serde_json", - "tokio", - "tower 0.5.3", - "tower-http 0.6.8", - "tracing", - "tracing-opentelemetry", - "tracing-subscriber", - "transcribe-proxy", - "url", - "utoipa", + "ticket-interface", ] [[package]] -name = "api-agent" +name = "apple-calendar" version = "0.1.0" dependencies = [ - "reqwest 0.13.2", + "backon", + "block2", + "chrono", + "itertools 0.14.0", + "json-patch 4.1.0", + "jsonschema", + "objc2", + "objc2-contacts", + "objc2-core-graphics", + "objc2-event-kit", + "objc2-foundation", + "schemars 1.2.1", "serde", "serde_json", + "specta", + "strum 0.28.0", "thiserror 2.0.18", "tokio", - "url", -] - -[[package]] -name = "api-auth" -version = "0.1.0" -dependencies = [ - "axum 0.8.9", - "supabase-auth", - "tokio", + "tracing", + "uuid", ] [[package]] -name = "api-bot" +name = "apple-todo" version = "0.1.0" dependencies = [ - "api-error", - "axum 0.8.9", - "recall", - "sentry", + "backon", + "block2", + "chrono", + "objc2", + "objc2-core-graphics", + "objc2-core-location", + "objc2-event-kit", + "objc2-foundation", + "schemars 1.2.1", "serde", "serde_json", + "specta", "thiserror 2.0.18", "tracing", - "urlencoding", - "utoipa", ] [[package]] -name = "api-cactus" -version = "0.1.0" +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ - "axum 0.8.9", - "bytes", - "reqwest 0.13.2", - "serde", - "tracing", + "num-traits", ] [[package]] -name = "api-calendar" -version = "0.1.0" +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ - "api-auth", - "api-error", - "api-nango", - "axum 0.8.9", - "chrono", - "google-calendar", - "nango", - "outlook-calendar", - "serde", - "thiserror 2.0.18", - "utoipa", + "object", ] [[package]] -name = "api-claw" -version = "0.1.0" +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ - "base64 0.22.1", - "chrono", - "exedev", - "rand_core 0.6.4", - "serde", - "serde_json", - "sha2 0.11.0", - "ssh-key", - "thiserror 2.0.18", - "tokio", - "tracing", + "derive_arbitrary", ] [[package]] -name = "api-client" -version = "0.1.0" +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ - "chrono", - "google-calendar", - "outlook-calendar", - "progenitor-client 0.13.0", - "progenitor-utils", - "reqwest 0.13.2", - "serde", - "serde_json", - "ticket-interface", + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", ] [[package]] -name = "api-env" -version = "0.1.0" +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ - "serde", + "rustversion", ] [[package]] -name = "api-error" -version = "0.1.0" -dependencies = [ - "axum 0.8.9", - "sentry", - "serde", - "tracing", - "utoipa", -] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "api-mail" -version = "0.1.0" -dependencies = [ - "api-auth", - "api-error", - "api-nango", - "axum 0.8.9", - "google-mail", - "nango", - "serde", - "thiserror 2.0.18", - "utoipa", -] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "api-messenger" -version = "0.1.0" +name = "askama" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" dependencies = [ - "api-error", - "axum 0.8.9", - "hypr-http-utils", + "askama_macros", + "itoa", + "percent-encoding", "serde", "serde_json", - "slack-web", - "teems", - "thiserror 2.0.18", ] [[package]] -name = "api-nango" +name = "askama-utils" version = "0.1.0" dependencies = [ - "api-auth", - "api-env", - "api-error", - "axum 0.8.9", + "askama", + "askama_parser", "chrono", - "futures-util", - "hex", - "hmac 0.13.0", - "nango", - "reqwest 0.13.2", - "sentry", - "serde", - "serde_json", - "sha2 0.11.0", - "thiserror 2.0.18", - "tokio", - "tracing", - "urlencoding", - "utoipa", - "wiremock", -] - -[[package]] -name = "api-pyannote" -version = "0.1.0" -dependencies = [ - "api-auth", - "api-env", - "api-error", - "axum 0.8.9", - "pyannote-cloud", - "reqwest 0.13.2", - "serde", - "serde_json", - "tokio", - "tower 0.5.3", - "tracing", - "utoipa", - "wiremock", + "insta", + "isolang", ] [[package]] -name = "api-research" -version = "0.1.0" +name = "askama_derive" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" dependencies = [ - "askama 0.15.6", - "axum 0.8.9", - "exa", - "jina", - "mcp", - "rmcp", + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash 2.1.2", "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-util", + "serde_derive", + "syn 2.0.117", ] [[package]] -name = "api-storage" -version = "0.1.0" +name = "askama_macros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" dependencies = [ - "api-error", - "api-nango", - "axum 0.8.9", - "google-drive", - "nango", - "sentry", - "serde", - "serde_json", - "thiserror 2.0.18", - "tracing", - "utoipa", + "askama_derive", ] [[package]] -name = "api-subscription" -version = "0.1.0" +name = "askama_parser" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" dependencies = [ - "analytics", - "api-auth", - "api-env", - "api-error", - "async-stripe", - "async-stripe-billing", - "async-stripe-core", - "axum 0.8.9", - "backon", - "chrono", - "loops", - "observability", - "reqwest 0.13.2", - "sentry", + "rustc-hash 2.1.2", "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tracing", - "urlencoding", - "utoipa", + "serde_derive", + "unicode-ident", + "winnow 1.0.1", ] [[package]] -name = "api-support" -version = "0.1.0" +name = "aspasia" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7c4e7dfd1cb7a872dfa20b445664984f373780b035de6af5a373df3ae14b55" dependencies = [ - "api-auth", - "api-env", - "async-stream", - "async-stripe", - "async-stripe-billing", - "axum 0.8.9", - "chatwoot", - "futures-util", - "jsonwebtoken", - "llm-proxy", - "mcp", - "octocrab", - "openrouter", - "regex", - "reqwest 0.13.2", - "rmcp", - "sentry", - "serde", - "serde_json", - "sqlx", - "strum 0.28.0", - "template-support", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tokio-util", - "tracing", - "urlencoding", - "utoipa", + "buildstructor", + "chardetng", + "encoding_rs", + "encoding_rs_io", + "nom 7.1.3", ] [[package]] -name = "api-sync" -version = "0.1.0" +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ - "api-error", - "axum 0.8.9", - "reqwest 0.13.2", - "sentry", "serde", "serde_json", - "supabase-auth", - "thiserror 2.0.18", - "tokio", - "tracing", - "utoipa", ] [[package]] -name = "api-ticket" -version = "0.1.0" +name = "assert_fs" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" dependencies = [ - "api-auth", - "api-error", - "api-nango", - "axum 0.8.9", - "github-issues", - "linear", - "nango", - "serde", - "serde_json", - "thiserror 2.0.18", - "ticket-interface", - "utoipa", + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", ] [[package]] -name = "apple-calendar" -version = "0.1.0" +name = "ast_node" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4902c7f39335a2390500ee791d6cb1778e742c7b97952497ec81449a5bfa3a7" dependencies = [ - "backon", - "block2", - "chrono", - "itertools 0.14.0", - "json-patch 4.1.0", - "jsonschema", - "objc2", - "objc2-contacts", - "objc2-core-graphics", - "objc2-event-kit", - "objc2-foundation", - "schemars 1.2.1", - "serde", - "serde_json", - "specta", - "strum 0.28.0", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", + "quote", + "swc_macros_common", + "syn 2.0.117", ] [[package]] -name = "apple-note" -version = "0.1.0" +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "flate2", - "prost 0.13.5", - "prost-build", - "serde", - "thiserror 2.0.18", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "apple-todo" -version = "0.1.0" +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ - "backon", - "block2", - "chrono", - "objc2", - "objc2-core-graphics", - "objc2-core-location", - "objc2-event-kit", - "objc2-foundation", - "schemars 1.2.1", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tracing", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "approx" -version = "0.5.1" +name = "async-compression" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ - "num-traits", + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "ar_archive_writer" -version = "0.5.1" +name = "async-executor" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ - "object", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", ] [[package]] -name = "arbitrary" -version = "1.4.2" +name = "async-io" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "derive_arbitrary", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", ] [[package]] -name = "arboard" -version = "3.6.1" +name = "async-lock" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "clipboard-win", - "image", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "parking_lot", - "percent-encoding", - "windows-sys 0.60.2", - "wl-clipboard-rs", - "x11rb", + "event-listener", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "arc-swap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +name = "async-openai" +version = "0.27.2" +source = "git+https://github.com/fastrepl/async-openai?rev=6404d307f3f706e818ad91544dc82fac5c545aee#6404d307f3f706e818ad91544dc82fac5c545aee" dependencies = [ - "rustversion", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder", + "eventsource-stream", + "futures", + "rand 0.8.6", + "reqwest 0.12.28", + "reqwest-eventsource", + "schemars 0.8.22", + "secrecy", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", ] [[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "askama" -version = "0.13.1" +name = "async-process" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "askama_derive 0.13.1", - "itoa", - "percent-encoding", - "serde", - "serde_json", + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", ] [[package]] -name = "askama" -version = "0.14.0" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ - "askama_derive 0.14.0", - "itoa", - "percent-encoding", - "serde", - "serde_json", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "askama" -version = "0.15.6" +name = "async-signal" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ - "askama_macros", - "itoa", - "percent-encoding", - "serde", - "serde_json", + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", ] [[package]] -name = "askama-utils" -version = "0.1.0" +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ - "askama 0.15.6", - "askama_parser 0.15.6", - "chrono", - "insta", - "isolang", + "async-stream-impl", + "futures-core", + "pin-project-lite", ] [[package]] -name = "askama_derive" -version = "0.13.1" +name = "async-stream-impl" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "askama_parser 0.13.0", - "basic-toml", - "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.2", - "serde", - "serde_derive", "syn 2.0.117", ] [[package]] -name = "askama_derive" -version = "0.14.0" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser 0.14.0", - "basic-toml", - "memchr", - "proc-macro2", - "quote", - "rustc-hash 2.1.2", - "serde", - "serde_derive", - "syn 2.0.117", -] +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "askama_derive" -version = "0.15.6" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "askama_parser 0.15.6", - "basic-toml", - "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.2", - "serde", - "serde_derive", "syn 2.0.117", ] [[package]] -name = "askama_macros" -version = "0.15.6" +name = "atk" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" dependencies = [ - "askama_derive 0.15.6", + "atk-sys", + "glib", + "libc", ] [[package]] -name = "askama_parser" -version = "0.13.0" +name = "atk-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" dependencies = [ - "memchr", - "serde", - "serde_derive", - "winnow 0.7.15", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "askama_parser" -version = "0.14.0" +name = "atoi" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ - "memchr", - "serde", - "serde_derive", - "winnow 0.7.15", + "num-traits", ] [[package]] -name = "askama_parser" -version = "0.15.6" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" -dependencies = [ - "rustc-hash 2.1.2", - "serde", - "serde_derive", - "unicode-ident", - "winnow 1.0.1", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "aspasia" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7c4e7dfd1cb7a872dfa20b445664984f373780b035de6af5a373df3ae14b55" -dependencies = [ - "buildstructor", - "chardetng", - "encoding_rs", - "encoding_rs_io", - "nom 7.1.3", -] +name = "audacity" +version = "0.1.0" [[package]] -name = "assert-json-diff" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +name = "audio" +version = "0.1.0" dependencies = [ - "serde", - "serde_json", + "futures-util", + "thiserror 2.0.18", ] [[package]] -name = "assert_fs" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" +name = "audio-actual" +version = "0.1.0" dependencies = [ - "anstyle", - "doc-comment", - "globwalk", - "predicates", - "predicates-core", - "predicates-tree", - "tempfile", -] - -[[package]] -name = "ast_node" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4902c7f39335a2390500ee791d6cb1778e742c7b97952497ec81449a5bfa3a7" -dependencies = [ - "quote", - "swc_macros_common", - "syn 2.0.117", + "aec", + "anyhow", + "audio", + "audio-interface", + "audio-sync", + "audio-utils", + "bytes", + "cidre", + "cpal", + "dasp", + "data", + "ebur128", + "futures-channel", + "futures-util", + "hound", + "libpulse-binding", + "pin-project", + "pipewire", + "resampler", + "ringbuf", + "rodio", + "serde", + "serde_json", + "serial_test", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "wasapi", ] [[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +name = "audio-chunking" +version = "0.1.0" dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "futures-core", - "pin-project-lite", + "audio-interface", + "data", + "futures-util", + "hound", + "onnx", + "pin-project", + "rodio", + "thiserror 2.0.18", + "tokio", + "vad", ] [[package]] -name = "async-channel" -version = "1.9.0" +name = "audio-core" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] +checksum = "f93ebbf82d06013f4c41fe71303feb980cddd78496d904d06be627972de51a24" [[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +name = "audio-device" +version = "0.1.0" dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", + "cidre", + "libpulse-binding", + "serde", + "serde_json", + "specta", + "thiserror 2.0.18", + "tokio", + "tracing", + "windows 0.62.2", + "windows-core 0.62.2", ] [[package]] -name = "async-compression" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +name = "audio-interface" +version = "0.1.0" dependencies = [ - "compression-codecs", - "compression-core", - "pin-project-lite", - "tokio", + "dasp", + "futures-util", + "rodio", ] [[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] +name = "audio-mime" +version = "0.1.0" [[package]] -name = "async-imap" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78dceaba06f029d8f4d7df20addd4b7370a30206e3926267ecda2915b0f3f66" +name = "audio-mock" +version = "0.1.0" dependencies = [ - "async-channel 2.5.0", - "async-compression", - "base64 0.22.1", - "bytes", - "chrono", - "futures", - "imap-proto", - "log", - "nom 7.1.3", - "pin-project", - "pin-utils", - "self_cell", - "stop-token", - "thiserror 1.0.69", + "audio", + "data", + "futures-util", + "rodio", "tokio", + "tokio-stream", + "tokio-util", + "tracing", ] [[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +name = "audio-norm" +version = "0.1.0" dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", + "afconvert", + "assert_fs", + "audio-utils", + "audioadapter-buffers", + "data", + "mp3", + "resampler", + "rodio", + "thiserror 2.0.18", ] [[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +name = "audio-snapshot" +version = "0.1.0" dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", + "approx", + "hound", + "realfft", + "serde", + "serde_json", ] [[package]] -name = "async-openai" -version = "0.27.2" -source = "git+https://github.com/fastrepl/async-openai?rev=6404d307f3f706e818ad91544dc82fac5c545aee#6404d307f3f706e818ad91544dc82fac5c545aee" +name = "audio-sync" +version = "0.1.0" dependencies = [ - "backoff", - "base64 0.22.1", - "bytes", - "derive_builder", - "eventsource-stream", - "futures", - "rand 0.8.6", - "reqwest 0.12.28", - "reqwest-eventsource", - "schemars 0.8.22", - "secrecy", + "approx", + "dasp", + "realfft", + "rodio", "serde", "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", ] [[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +name = "audio-utils" +version = "0.1.0" dependencies = [ - "async-channel 2.5.0", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener 5.4.1", - "futures-lite", - "rustix 1.1.4", + "audio-interface", + "audio-mime", + "audioadapter-buffers", + "bytes", + "data", + "futures-util", + "hound", + "resampler", + "rodio", + "rubato", + "thiserror 2.0.18", + "tracing", + "vorbis_rs", ] [[package]] -name = "async-recursion" -version = "1.1.1" +name = "audioadapter" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "98e72b98fa467adcb7a88c5d1b8b686193185c81b9bf9c3fa3ac3524180cd55c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "audio-core", + "libm", + "num-traits", ] [[package]] -name = "async-signal" -version = "0.2.14" +name = "audioadapter-buffers" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +checksum = "a6af89882334c4e501faa08992888593ada468f9e1ab211635c32f9ada7786e0" dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.1.4", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", + "audioadapter", + "audioadapter-sample", + "num-traits", ] [[package]] -name = "async-stream" -version = "0.3.6" +name = "audioadapter-sample" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +checksum = "4e9a3d502fec0b21aa420febe0b110875cf8a7057c49e83a0cace1df6a73e03e" dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", + "audio-core", + "num-traits", ] [[package]] -name = "async-stream-impl" -version = "0.3.6" +name = "auto-launch" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "dirs 4.0.0", + "thiserror 1.0.69", + "winreg 0.10.1", ] [[package]] -name = "async-stripe" -version = "1.0.0-rc.5" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ec8493f89fb9d9ac1848988955ec192df8e0ebf4d4bbd85fd93df9f681e890" -dependencies = [ - "async-stripe-client-core", - "async-stripe-shared", - "bytes", - "http-body-util", - "hyper 1.9.0", - "hyper-tls", - "hyper-util", - "miniserde", - "thiserror 2.0.18", - "tokio", - "tracing", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "async-stripe-billing" -version = "1.0.0-rc.5" +name = "autotools" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2340b289cd5e34bb9b8b3eebed4e7a9729831ebee01388965ad4d88fd7a5a3b" +checksum = "ef941527c41b0fc0dd48511a8154cd5fc7e29200a0ff8b7203c5d777dbc795cf" dependencies = [ - "async-stripe-client-core", - "async-stripe-shared", - "async-stripe-types", - "miniserde", - "serde", - "smol_str", - "tracing", + "cc", ] [[package]] -name = "async-stripe-client-core" -version = "1.0.0-rc.5" +name = "aws-config" +version = "1.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f095837711eb1c3ee02604b4e1d44b117014fb74da99ad4f2d70e907dbdc41" +checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" dependencies = [ - "async-stripe-shared", - "async-stripe-types", + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", "bytes", - "futures-util", - "miniserde", - "serde", - "serde_json", - "serde_qs", - "thiserror 2.0.18", + "fastrand", + "hex", + "http 1.4.0", + "sha1", + "time", + "tokio", "tracing", + "url", + "zeroize", ] [[package]] -name = "async-stripe-core" -version = "1.0.0-rc.5" +name = "aws-credential-types" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9710c3d64db48dda1ec0b4976d091b7f40cabc8ed7bcab9e93b608b70b405c" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" dependencies = [ - "async-stripe-client-core", - "async-stripe-shared", - "async-stripe-types", - "miniserde", - "serde", - "smol_str", - "tracing", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", ] [[package]] -name = "async-stripe-shared" -version = "1.0.0-rc.5" +name = "aws-lc-rs" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a352c5e36a92aa8bdd4326c8211b9a26e822e6cb4b7516a47282396a4938a231" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ - "async-stripe-types", - "miniserde", - "serde", - "smol_str", - "tracing", + "aws-lc-sys", + "zeroize", ] [[package]] -name = "async-stripe-types" -version = "1.0.0-rc.5" +name = "aws-lc-sys" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1ec9960b89b1f556bf885403dd208d173a8c42d028fcd7baeca9ad2bfb13f6" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ - "miniserde", - "serde", - "smol_str", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "async-task" -version = "4.7.1" +name = "aws-runtime" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http 0.63.6", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] [[package]] -name = "async-trait" -version = "0.1.89" +name = "aws-sdk-bedrock" +version = "1.140.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "930086d6a615f77948c9244edff57d5ea4d7827302152652c24a16bf1430aff2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", ] [[package]] -name = "atk" -version = "0.18.2" +name = "aws-sdk-s3" +version = "1.119.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" dependencies = [ - "atk-sys", - "glib", - "libc", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http 0.62.6", + "aws-smithy-json 0.61.9", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "lru", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", ] [[package]] -name = "atk-sys" -version = "0.18.2" +name = "aws-sdk-sso" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", ] [[package]] -name = "atoi" -version = "2.0.0" +name = "aws-sdk-ssooidc" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8" dependencies = [ - "num-traits", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", ] [[package]] -name = "atomic" -version = "0.6.1" +name = "aws-sdk-sts" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ - "bytemuck", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", ] [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "aws-sigv4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "audacity" -version = "0.1.0" - -[[package]] -name = "audio" -version = "0.1.0" -dependencies = [ - "futures-util", - "thiserror 2.0.18", -] - -[[package]] -name = "audio-actual" -version = "0.1.0" +checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" dependencies = [ - "aec", - "anyhow", - "audio", - "audio-interface", - "audio-sync", - "audio-utils", + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http 0.63.6", + "aws-smithy-runtime-api", + "aws-smithy-types", "bytes", - "cidre", - "cpal", - "dasp", - "data", - "ebur128", - "futures-channel", - "futures-util", - "hound", - "libpulse-binding", - "pin-project", - "pipewire", - "resampler", - "ringbuf", - "rodio", - "serde", - "serde_json", - "serial_test", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "p256 0.11.1", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", "tracing", - "wasapi", + "zeroize", ] [[package]] -name = "audio-chunking" -version = "0.1.0" +name = "aws-smithy-async" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" dependencies = [ - "audio-interface", - "data", "futures-util", - "hound", - "onnx", - "pin-project", - "rodio", - "thiserror 2.0.18", + "pin-project-lite", "tokio", - "vad", ] [[package]] -name = "audio-core" -version = "0.2.1" +name = "aws-smithy-checksums" +version = "0.63.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ebbf82d06013f4c41fe71303feb980cddd78496d904d06be627972de51a24" - -[[package]] -name = "audio-device" -version = "0.1.0" +checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" dependencies = [ - "cidre", - "libpulse-binding", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tokio", + "aws-smithy-http 0.62.6", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", "tracing", - "windows 0.62.2", - "windows-core 0.62.2", ] [[package]] -name = "audio-interface" -version = "0.1.0" +name = "aws-smithy-eventstream" +version = "0.60.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" dependencies = [ - "dasp", - "futures-util", - "rodio", + "aws-smithy-types", + "bytes", + "crc32fast", ] [[package]] -name = "audio-mime" -version = "0.1.0" +name = "aws-smithy-http" +version = "0.62.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] [[package]] -name = "audio-mock" -version = "0.1.0" +name = "aws-smithy-http" +version = "0.63.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" dependencies = [ - "audio", - "data", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", "futures-util", - "rodio", - "tokio", - "tokio-stream", - "tokio-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", "tracing", ] [[package]] -name = "audio-norm" -version = "0.1.0" +name = "aws-smithy-http-client" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" dependencies = [ - "afconvert", - "assert_fs", - "audio-utils", - "audioadapter-buffers", - "data", - "mp3", - "resampler", - "rodio", - "thiserror 2.0.18", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.9.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.9", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.38", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", ] [[package]] -name = "audio-snapshot" -version = "0.1.0" +name = "aws-smithy-json" +version = "0.61.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" dependencies = [ - "approx", - "hound", - "realfft", - "serde", - "serde_json", + "aws-smithy-types", ] [[package]] -name = "audio-sync" -version = "0.1.0" +name = "aws-smithy-json" +version = "0.62.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" dependencies = [ - "approx", - "dasp", - "realfft", - "rodio", - "serde", - "serde_json", + "aws-smithy-types", ] [[package]] -name = "audio-utils" -version = "0.1.0" +name = "aws-smithy-observability" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" dependencies = [ - "audio-interface", - "audio-mime", - "audioadapter-buffers", - "bytes", - "data", - "futures-util", - "hound", - "resampler", - "rodio", - "rubato", - "thiserror 2.0.18", - "tracing", - "vorbis_rs", + "aws-smithy-runtime-api", ] [[package]] -name = "audioadapter" -version = "2.0.1" +name = "aws-smithy-query" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e72b98fa467adcb7a88c5d1b8b686193185c81b9bf9c3fa3ac3524180cd55c" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" dependencies = [ - "audio-core", - "libm", - "num-traits", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "audioadapter-buffers" -version = "2.0.0" +name = "aws-smithy-runtime" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6af89882334c4e501faa08992888593ada468f9e1ab211635c32f9ada7786e0" -dependencies = [ - "audioadapter", - "audioadapter-sample", - "num-traits", -] - -[[package]] -name = "audioadapter-sample" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9a3d502fec0b21aa420febe0b110875cf8a7057c49e83a0cace1df6a73e03e" -dependencies = [ - "audio-core", - "num-traits", -] - -[[package]] -name = "auto-launch" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" -dependencies = [ - "dirs 4.0.0", - "thiserror 1.0.69", - "winreg 0.10.1", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "autotools" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941527c41b0fc0dd48511a8154cd5fc7e29200a0ff8b7203c5d777dbc795cf" -dependencies = [ - "cc", -] - -[[package]] -name = "aws-config" -version = "1.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" +checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sdk-sso", - "aws-sdk-ssooidc", - "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", - "aws-smithy-runtime", + "aws-smithy-http-client", + "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-types", - "aws-types", "bytes", "fastrand", - "hex", + "http 0.2.12", "http 1.4.0", - "sha1", - "time", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", "tokio", "tracing", - "url", - "zeroize", ] [[package]] -name = "aws-credential-types" -version = "1.2.14" +name = "aws-smithy-runtime-api" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" dependencies = [ "aws-smithy-async", - "aws-smithy-runtime-api", + "aws-smithy-runtime-api-macros", "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", "zeroize", ] [[package]] -name = "aws-lc-rs" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.40.0" +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "aws-runtime" -version = "1.7.2" +name = "aws-smithy-types" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" +checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" dependencies = [ - "aws-credential-types", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-eventstream", - "aws-smithy-http 0.63.6", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", + "base64-simd", "bytes", "bytes-utils", - "fastrand", + "futures-core", "http 0.2.12", "http 1.4.0", "http-body 0.4.6", "http-body 1.0.1", - "percent-encoding", + "http-body-util", + "itoa", + "num-integer", "pin-project-lite", - "tracing", - "uuid", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", ] [[package]] -name = "aws-sdk-bedrock" -version = "1.140.0" +name = "aws-smithy-xml" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930086d6a615f77948c9244edff57d5ea4d7827302152652c24a16bf1430aff2" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" dependencies = [ "aws-credential-types", - "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", - "aws-smithy-observability", - "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "http 1.4.0", - "regex-lite", + "rustc_version", "tracing", ] [[package]] -name = "aws-sdk-s3" -version = "1.119.0" +name = "axum" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-checksums", - "aws-smithy-eventstream", - "aws-smithy-http 0.62.6", - "aws-smithy-json 0.61.9", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", "bytes", - "fastrand", - "hex", - "hmac 0.12.1", + "futures-util", "http 0.2.12", - "http 1.4.0", "http-body 0.4.6", - "lru 0.12.5", + "hyper 0.14.32", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", "percent-encoding", - "regex-lite", - "sha2 0.10.9", - "tracing", - "url", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower 0.4.13", + "tower-layer", + "tower-service", ] [[package]] -name = "aws-sdk-sso" -version = "1.97.0" +name = "axum" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", - "aws-smithy-observability", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", + "axum-core 0.5.6", + "base64 0.22.1", "bytes", - "fastrand", - "http 0.2.12", + "form_urlencoded", + "futures-util", "http 1.4.0", - "regex-lite", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper 1.0.2", + "tokio", + "tokio-tungstenite 0.29.0", + "tower 0.5.3", + "tower-layer", + "tower-service", "tracing", ] [[package]] -name = "aws-sdk-ssooidc" -version = "1.99.0" +name = "axum-core" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", - "aws-smithy-observability", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", + "async-trait", "bytes", - "fastrand", + "futures-util", "http 0.2.12", - "http 1.4.0", - "regex-lite", - "tracing", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", ] [[package]] -name = "aws-sdk-sts" -version = "1.102.0" +name = "axum-core" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", - "aws-smithy-observability", - "aws-smithy-query", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "fastrand", - "http 0.2.12", + "bytes", + "futures-core", "http 1.4.0", - "regex-lite", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", "tracing", ] [[package]] -name = "aws-sigv4" -version = "1.4.2" +name = "axum-extra" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" dependencies = [ - "aws-credential-types", - "aws-smithy-eventstream", - "aws-smithy-http 0.63.6", - "aws-smithy-runtime-api", - "aws-smithy-types", + "axum 0.8.9", + "axum-core 0.5.6", "bytes", - "crypto-bigint 0.5.5", "form_urlencoded", - "hex", - "hmac 0.12.1", - "http 0.2.12", + "futures-core", + "futures-util", "http 1.4.0", - "p256 0.11.1", - "percent-encoding", - "ring", - "sha2 0.10.9", - "subtle", - "time", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "serde_core", + "serde_html_form 0.2.8", + "serde_path_to_error", + "tower-layer", + "tower-service", "tracing", - "zeroize", ] [[package]] -name = "aws-smithy-async" -version = "1.2.14" +name = "az" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "futures-util", + "futures-core", + "getrandom 0.2.17", + "instant", "pin-project-lite", + "rand 0.8.6", "tokio", ] [[package]] -name = "aws-smithy-checksums" -version = "0.63.12" +name = "backon" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "aws-smithy-http 0.62.6", - "aws-smithy-types", - "bytes", - "crc-fast", - "hex", - "http 0.2.12", - "http-body 0.4.6", - "md-5", - "pin-project-lite", - "sha1", - "sha2 0.10.9", - "tracing", + "fastrand", + "gloo-timers", + "tokio", ] [[package]] -name = "aws-smithy-eventstream" -version = "0.60.20" +name = "backtrace" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ - "aws-smithy-types", - "bytes", - "crc32fast", + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", ] [[package]] -name = "aws-smithy-http" -version = "0.62.6" +name = "base16ct" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" -dependencies = [ - "aws-smithy-eventstream", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "futures-util", - "http 0.2.12", - "http 1.4.0", - "http-body 0.4.6", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] -name = "aws-smithy-http" -version = "0.63.6" +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" -dependencies = [ - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "aws-smithy-http-client" -version = "1.1.12" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "h2 0.3.27", - "h2 0.4.13", - "http 0.2.12", - "http 1.4.0", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper 1.9.0", - "hyper-rustls 0.24.2", - "hyper-rustls 0.27.9", - "hyper-util", - "pin-project-lite", - "rustls 0.21.12", - "rustls 0.23.38", - "rustls-native-certs 0.8.3", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.4", - "tower 0.5.3", - "tracing", -] +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "aws-smithy-json" -version = "0.61.9" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" -dependencies = [ - "aws-smithy-types", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "aws-smithy-json" -version = "0.62.5" +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "aws-smithy-types", + "outref", + "vsimd", ] [[package]] -name = "aws-smithy-observability" -version = "0.2.6" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ - "aws-smithy-runtime-api", + "serde", ] [[package]] -name = "aws-smithy-query" -version = "0.60.15" +name = "better_scoped_tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" dependencies = [ - "aws-smithy-types", - "urlencoding", + "scoped-tls", ] [[package]] -name = "aws-smithy-runtime" -version = "1.11.1" +name = "biblatex" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" +checksum = "53d0c374feba1b9a59042a7c1cf00ce7c34b977b9134fe7c42b08e5183729f66" dependencies = [ - "aws-smithy-async", - "aws-smithy-http 0.63.6", - "aws-smithy-http-client", - "aws-smithy-observability", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "fastrand", - "http 0.2.12", - "http 1.4.0", - "http-body 0.4.6", - "http-body 1.0.1", - "http-body-util", - "pin-project-lite", - "pin-utils", - "tokio", - "tracing", + "paste", + "roman-numerals-rs", + "strum 0.27.2", + "unic-langid", + "unicode-normalization", + "unscanny", ] [[package]] -name = "aws-smithy-runtime-api" -version = "1.12.0" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api-macros", - "aws-smithy-types", - "bytes", - "http 0.2.12", - "http 1.4.0", - "pin-project-lite", - "tokio", - "tracing", - "zeroize", + "serde", ] [[package]] -name = "aws-smithy-runtime-api-macros" -version = "1.0.0" +name = "bindgen" +version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", "proc-macro2", "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", "syn 2.0.117", + "which", ] [[package]] -name = "aws-smithy-types" -version = "1.4.7" +name = "bindgen" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "base64-simd", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http 1.4.0", - "http-body 0.4.6", - "http-body 1.0.1", - "http-body-util", - "itoa", - "num-integer", - "pin-project-lite", - "pin-utils", - "ryu", - "serde", - "time", - "tokio", - "tokio-util", + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.117", + "which", ] [[package]] -name = "aws-smithy-xml" -version = "0.60.15" +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "xmlparser", + "annotate-snippets", + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex", + "syn 2.0.117", ] [[package]] -name = "aws-types" -version = "1.3.14" +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "aws-credential-types", - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "rustc_version", - "tracing", + "bit-vec", ] [[package]] -name = "axum" -version = "0.6.20" +name = "bit-vec" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower 0.4.13", - "tower-layer", - "tower-service", -] +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "axum" -version = "0.8.9" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ - "axum-core 0.5.6", - "axum-macros", - "base64 0.22.1", - "bytes", - "form_urlencoded", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "hyper-util", - "itoa", - "matchit 0.8.4", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper 1.0.2", - "tokio", - "tokio-tungstenite 0.29.0", - "tower 0.5.3", - "tower-layer", - "tower-service", - "tracing", ] [[package]] -name = "axum-core" -version = "0.3.4" +name = "bitpacking" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "96a7139abd3d9cebf8cd6f920a389cf3dc9576172e32f4563f188cae3c3eb019" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", + "crunchy", ] [[package]] -name = "axum-core" -version = "0.5.6" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "bytes", - "futures-core", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", + "generic-array", ] [[package]] -name = "axum-extra" -version = "0.12.6" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "axum 0.8.9", - "axum-core 0.5.6", - "bytes", - "form_urlencoded", - "futures-core", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "serde_core", - "serde_html_form 0.2.8", - "serde_path_to_error", - "tower-layer", - "tower-service", - "tracing", + "generic-array", ] [[package]] -name = "axum-macros" -version = "0.5.1" +name = "block2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "objc2", ] [[package]] -name = "az" -version = "1.3.0" +name = "blocking" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] [[package]] -name = "backoff" -version = "0.4.0" +name = "bollard" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", "futures-core", - "getrandom 0.2.17", - "instant", + "futures-util", + "hex", + "home", + "http 1.4.0", + "http-body-util", + "hyper 1.9.0", + "hyper-named-pipe", + "hyper-rustls 0.27.9", + "hyper-util", + "hyperlocal", + "log", "pin-project-lite", - "rand 0.8.6", + "rustls 0.23.38", + "rustls-native-certs 0.8.3", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.18", "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", ] [[package]] -name = "backon" -version = "1.6.0" +name = "bollard-stubs" +version = "1.47.1-rc.27.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" dependencies = [ - "fastrand", - "gloo-timers", - "tokio", + "serde", + "serde_repr", + "serde_with", ] [[package]] -name = "backtrace" -version = "0.3.76" +name = "bon" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", + "bon-macros 2.3.0", + "rustversion", ] [[package]] -name = "base16ct" -version = "0.1.1" +name = "bon" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros 3.9.1", + "rustversion", +] [[package]] -name = "base16ct" -version = "0.2.0" +name = "bon-macros" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" +dependencies = [ + "darling 0.20.11", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "base64" -version = "0.21.7" +name = "bon-macros" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling 0.23.0", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] [[package]] -name = "base64" -version = "0.22.1" +name = "borrow-or-share" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] -name = "base64-simd" -version = "0.8.0" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ - "outref", - "vsimd", + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", ] [[package]] -name = "base64ct" -version = "1.8.3" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] [[package]] -name = "basic-toml" -version = "0.1.10" +name = "bstr" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ + "memchr", + "regex-automata", "serde", ] [[package]] -name = "better_scoped_tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" +name = "buffer" +version = "0.1.0" dependencies = [ - "scoped-tls", + "insta", + "markdown", + "mdast_util_to_markdown", + "regex", + "thiserror 2.0.18", + "tl", ] [[package]] -name = "biblatex" -version = "0.11.0" +name = "buildstructor" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d0c374feba1b9a59042a7c1cf00ce7c34b977b9134fe7c42b08e5183729f66" +checksum = "c3907aac66c65520545ae3cb3c195306e20d5ed5c90bfbb992e061cf12a104d0" dependencies = [ - "paste", - "roman-numerals-rs", - "strum 0.27.2", - "unic-langid", - "unicode-normalization", - "unscanny", + "lazy_static", + "proc-macro2", + "quote", + "str_inflector", + "syn 2.0.117", + "thiserror 1.0.69", + "try_match", ] [[package]] -name = "bincode" -version = "1.3.3" +name = "bumpalo" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bundle" +version = "0.1.0" dependencies = [ - "serde", + "cidre", + "objc2-app-kit", + "objc2-foundation", + "plist", ] [[package]] -name = "bindgen" -version = "0.66.1" +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ - "bitflags 2.11.1", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.117", - "which 4.4.2", + "bytemuck_derive", ] [[package]] -name = "bindgen" -version = "0.69.5" +name = "bytemuck_derive" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ - "bitflags 2.11.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", "proc-macro2", "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", "syn 2.0.117", - "which 4.4.2", ] [[package]] -name = "bindgen" -version = "0.72.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "annotate-snippets", - "bitflags 2.11.1", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.2", - "shlex", - "syn 2.0.117", -] +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bit-set" -version = "0.5.3" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] -name = "bit-set" -version = "0.8.0" +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ - "bit-vec 0.8.0", + "serde", ] [[package]] -name = "bit-vec" -version = "0.6.3" +name = "bytes-str" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bytestring" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] [[package]] -name = "bitflags" -version = "2.11.1" +name = "bzip2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ - "serde_core", + "bzip2-sys", ] [[package]] -name = "bitpacking" -version = "0.9.3" +name = "bzip2-sys" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a7139abd3d9cebf8cd6f920a389cf3dc9576172e32f4563f188cae3c3eb019" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ - "crunchy", + "cc", + "pkg-config", ] [[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +name = "cactus" +version = "0.1.0" dependencies = [ - "generic-array", + "cactus-model", + "cactus-sys", + "colored", + "criterion", + "data", + "futures-util", + "insta", + "jsonschema", + "language", + "llm-types", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", ] [[package]] -name = "block-buffer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +name = "cactus-model" +version = "0.1.0" dependencies = [ - "hybrid-array", + "language", + "serde", + "specta", + "strum 0.28.0", ] [[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +name = "cactus-sys" +version = "0.1.0" +source = "git+https://github.com/cactus-compute/cactus?rev=130a60dcd4767a994a34f319d26d02eedb50f293#130a60dcd4767a994a34f319d26d02eedb50f293" dependencies = [ - "generic-array", + "bindgen 0.72.1", + "cmake", ] [[package]] -name = "block2" -version = "0.6.2" +name = "cairo-rs" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "objc2", + "bitflags 2.11.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", ] [[package]] -name = "blocking" -version = "1.6.2" +name = "cairo-sys-rs" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", + "glib-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "bollard" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +name = "calendar" +version = "0.1.0" dependencies = [ - "base64 0.22.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "home", - "http 1.4.0", - "http-body-util", - "hyper 1.9.0", - "hyper-named-pipe", - "hyper-rustls 0.27.9", - "hyper-util", - "hyperlocal", - "log", - "pin-project-lite", - "rustls 0.23.38", - "rustls-native-certs 0.8.3", - "rustls-pemfile", - "rustls-pki-types", + "api-client", + "apple-calendar", + "calendar-interface", + "chrono", + "chrono-tz 0.10.4", + "google-calendar", + "outlook-calendar", + "regex", + "reqwest 0.13.2", "serde", - "serde_derive", "serde_json", - "serde_repr", - "serde_urlencoded", + "specta", "thiserror 2.0.18", - "tokio", - "tokio-util", - "tower-service", - "url", - "winapi", + "tracing", ] [[package]] -name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +name = "calendar-interface" +version = "0.1.0" dependencies = [ + "chrono", "serde", - "serde_repr", - "serde_with", + "specta", ] [[package]] -name = "bon" -version = "2.3.0" +name = "camino" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "bon-macros 2.3.0", - "rustversion", + "serde_core", ] [[package]] -name = "bon" -version = "3.9.1" +name = "cargo-platform" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ - "bon-macros 3.9.1", - "rustversion", + "serde", ] [[package]] -name = "bon-macros" -version = "2.3.0" +name = "cargo_metadata" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ - "darling 0.20.11", - "ident_case", - "proc-macro2", - "quote", - "syn 2.0.117", + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", ] [[package]] -name = "bon-macros" -version = "3.9.1" +name = "cargo_toml" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ - "darling 0.23.0", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.117", + "serde", + "toml 0.9.12+spec-1.1.0", ] [[package]] -name = "borrow-or-share" -version = "0.2.4" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] -name = "borsh" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "bytes", - "cfg_aliases 0.2.1", + "cipher", ] [[package]] -name = "brotli" -version = "8.0.2" +name = "cc" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "find-msvc-tools", + "jobserver", + "libc", + "shlex", ] [[package]] -name = "brotli-decompressor" -version = "5.0.0" +name = "census" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" [[package]] -name = "bstr" -version = "1.12.1" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "buffer" -version = "0.1.0" +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "insta", - "markdown", - "mdast_util_to_markdown", - "regex", - "thiserror 2.0.18", - "tl", + "nom 7.1.3", ] [[package]] -name = "buildstructor" -version = "0.5.4" +name = "cfb" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3907aac66c65520545ae3cb3c195306e20d5ed5c90bfbb992e061cf12a104d0" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ - "lazy_static", - "proc-macro2", - "quote", - "str_inflector", - "syn 2.0.117", - "thiserror 1.0.69", - "try_match", + "byteorder", + "fnv", + "uuid", ] [[package]] -name = "bumpalo" -version = "3.20.2" +name = "cfg-expr" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec 1.15.1", + "target-lexicon 0.12.16", +] [[package]] -name = "bundle" -version = "0.1.0" +name = "cfg-expr" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ - "cidre", - "objc2-app-kit", - "objc2-foundation", - "plist", + "smallvec 1.15.1", + "target-lexicon 0.13.3", ] [[package]] -name = "by_address" -version = "1.2.1" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "bytecount" -version = "0.6.9" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] -name = "bytemuck" -version = "1.25.0" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ - "bytemuck_derive", + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] -name = "bytemuck_derive" -version = "1.10.2" +name = "chardetng" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "cfg-if", + "encoding_rs", + "memchr", ] [[package]] -name = "byteorder" -version = "1.5.0" +name = "chinese-number" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "3e964125508474a83c95eb935697abbeb446ff4e9d62c71ce880e3986d1c606b" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] [[package]] -name = "byteorder-lite" -version = "0.1.0" +name = "chinese-variant" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" +checksum = "58b52a9840ffff5d4d0058ae529fa066a75e794e3125546acfc61c23ad755e49" [[package]] -name = "bytes" -version = "1.11.1" +name = "chrono" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", "serde", + "wasm-bindgen", + "windows-link 0.2.1", ] [[package]] -name = "bytes-str" -version = "0.2.7" +name = "chrono-tz" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ - "bytes", - "serde", + "chrono", + "chrono-tz-build", + "phf 0.11.3", ] [[package]] -name = "bytes-utils" -version = "0.1.4" +name = "chrono-tz" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ - "bytes", - "either", + "chrono", + "phf 0.12.1", ] [[package]] -name = "bytestring" -version = "1.5.0" +name = "chrono-tz-build" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ - "bytes", + "parse-zoneinfo", + "phf 0.11.3", + "phf_codegen 0.11.3", ] [[package]] -name = "bzip2" -version = "0.5.2" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ - "bzip2-sys", + "ciborium-io", + "ciborium-ll", + "serde", ] [[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] -name = "cactus" -version = "0.1.0" +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ - "cactus-model", - "cactus-sys", - "colored", - "criterion", - "data", - "futures-util", - "insta", - "jsonschema", - "language", - "llm-types", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "url", + "ciborium-io", + "half", ] [[package]] -name = "cactus-model" -version = "0.1.0" +name = "cidre" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c903ff1729987d29e07295d2c5841993db25ff86cca43bee04505878d3ec1a7" dependencies = [ - "language", - "serde", - "specta", - "strum 0.28.0", + "cc", + "cidre-macros", + "half", + "parking_lot", + "tokio", ] [[package]] -name = "cactus-sys" -version = "0.1.0" -source = "git+https://github.com/cactus-compute/cactus?rev=130a60dcd4767a994a34f319d26d02eedb50f293#130a60dcd4767a994a34f319d26d02eedb50f293" -dependencies = [ - "bindgen 0.72.1", - "cmake", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" +name = "cidre-macros" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.11.1", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] +checksum = "6facfeffa8b9792dc014bb4e65e364d13518b74e06783d56249810a3e48cfad7" [[package]] -name = "cairo-sys-rs" -version = "0.18.2" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", + "crypto-common", + "inout", ] [[package]] -name = "calendar" -version = "0.1.0" +name = "citationberg" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6597e8bdbca37f1f56e5a80d15857b0932aead21a78d20de49e99e74933046" dependencies = [ - "api-client", - "apple-calendar", - "calendar-interface", - "chrono", - "chrono-tz 0.10.4", - "google-calendar", - "outlook-calendar", - "regex", - "reqwest 0.13.2", + "quick-xml 0.38.4", "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tracing", ] [[package]] -name = "calendar-interface" -version = "0.1.0" +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "chrono", - "serde", - "specta", + "glob", + "libc", + "libloading 0.8.9", ] [[package]] -name = "camino" -version = "1.2.2" +name = "clap" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ - "serde_core", + "clap_builder", + "clap_derive", ] [[package]] -name = "cargo-platform" -version = "0.1.9" +name = "clap_builder" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "serde", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "cargo-platform" -version = "0.3.2" +name = "clap_derive" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "serde", - "serde_core", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "cargo_metadata" -version = "0.19.2" +name = "clap_lex" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "claude" +version = "0.1.0" dependencies = [ - "camino", - "cargo-platform 0.1.9", - "semver", + "cli-process", + "dirs 6.0.0", + "futures-util", "serde", "serde_json", + "tempfile", "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", ] [[package]] -name = "cargo_metadata" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +name = "cli-process" +version = "0.1.0" dependencies = [ - "camino", - "cargo-platform 0.3.2", - "semver", - "serde", - "serde_json", + "futures-util", "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", ] [[package]] -name = "cargo_toml" -version = "0.22.3" +name = "clipboard-win" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", + "error-code", ] [[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +name = "cloudsync" +version = "0.1.0" +dependencies = [ + "dirs 6.0.0", + "sqlx", + "thiserror 2.0.18", + "tokio", +] [[package]] -name = "castaway" -version = "0.2.4" +name = "clru" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" dependencies = [ - "rustversion", + "hashbrown 0.16.1", ] [[package]] -name = "cbc" -version = "0.1.2" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "cipher", + "cc", ] [[package]] -name = "cc" -version = "1.2.60" +name = "cobs" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", + "thiserror 2.0.18", ] [[package]] -name = "census" -version = "0.4.2" +name = "codes-agency" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" +checksum = "809a7790247f949cc35a1f9b497fd13a79ee330e34f049c6e837685363d20faf" +dependencies = [ + "codes-common", + "serde", +] [[package]] -name = "cesu8" -version = "1.1.0" +name = "codes-common" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +checksum = "a2b16b1037e7111ea4a4bd4a2b48733b990a76ecc321539858a5ab534792b287" +dependencies = [ + "csv", + "tera", + "tracing", +] [[package]] -name = "cexpr" -version = "0.6.0" +name = "codes-iso-639" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "30dbd0357410908cd59147abbb5d40a9b9a5d9261285c272844cf53dbaa6f7a8" dependencies = [ - "nom 7.1.3", + "codes-agency", + "codes-common", + "csv", + "serde", + "tera", ] [[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +name = "codex" +version = "0.1.0" dependencies = [ - "byteorder", - "fnv", - "uuid", + "cli-process", + "dirs 6.0.0", + "futures-util", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "toml 0.8.2", + "url", ] [[package]] -name = "cfg-expr" -version = "0.15.8" +name = "codex" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec 1.15.1", - "target-lexicon 0.12.16", -] +checksum = "9589e1effc5cacbea347899645c654158b03b2053d24bb426fd3128ced6e423c" [[package]] -name = "cfg-expr" -version = "0.20.7" +name = "color-eyre" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ - "smallvec 1.15.1", - "target-lexicon 0.13.3", + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "color-spantrace" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] [[package]] -name = "cfg_aliases" -version = "0.2.1" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "chacha20" -version = "0.9.1" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "chacha20" -version = "0.10.0" +name = "colored" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", + "windows-sys 0.61.2", ] [[package]] -name = "chacha20poly1305" -version = "0.10.1" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher", - "poly1305", - "zeroize", + "bytes", + "memchr", ] [[package]] -name = "chardetng" -version = "0.1.17" +name = "comemo" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" +checksum = "3c963350b2b08aa4b725d7802593245380ab53dacfedcaa971385fc33306c0d4" dependencies = [ - "cfg-if", - "encoding_rs", - "memchr", + "comemo-macros", + "parking_lot", + "rustc-hash 2.1.2", + "siphasher 1.0.2", + "slab", ] [[package]] -name = "chatwoot" -version = "0.1.0" +name = "comemo-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c400139ba1389ef9e20ad2d87cda68b437a66483aa0da616bdf2cea7413853" dependencies = [ - "chrono", - "progenitor-client 0.13.0", - "progenitor-utils", - "reqwest 0.13.2", - "serde", - "serde_json", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "chinese-number" -version = "0.7.8" +name = "compression-codecs" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e964125508474a83c95eb935697abbeb446ff4e9d62c71ce880e3986d1c606b" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ - "chinese-variant", - "enum-ordinalize", - "num-bigint", - "num-traits", + "compression-core", + "flate2", + "memchr", ] [[package]] -name = "chinese-variant" -version = "1.1.5" +name = "compression-core" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b52a9840ffff5d4d0058ae529fa066a75e794e3125546acfc61c23ad755e49" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" [[package]] -name = "chrome-native-host" -version = "0.1.0" +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "dirs 6.0.0", - "serde", - "serde_json", - "tempfile", + "crossbeam-utils", ] [[package]] -name = "chrono" -version = "0.4.44" +name = "console" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", + "encode_unicode", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "chrono-tz" -version = "0.9.0" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf 0.11.3", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "chrono-tz" -version = "0.10.4" +name = "const-random" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ - "chrono", - "phf 0.12.1", + "const-random-macro", ] [[package]] -name = "chrono-tz-build" -version = "0.3.0" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "parse-zoneinfo", - "phf 0.11.3", - "phf_codegen 0.11.3", + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", ] [[package]] -name = "ciborium" -version = "0.2.2" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] -name = "ciborium-io" -version = "0.2.2" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "ciborium-ll" -version = "0.2.2" +name = "convert_case" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ - "ciborium-io", - "half", + "unicode-segmentation", ] [[package]] -name = "cidre" -version = "0.15.0" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c903ff1729987d29e07295d2c5841993db25ff86cca43bee04505878d3ec1a7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "cc", - "cidre-macros", - "half", - "parking_lot", - "tokio", + "unicode-segmentation", ] [[package]] -name = "cidre-macros" -version = "0.5.0" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6facfeffa8b9792dc014bb4e65e364d13518b74e06783d56249810a3e48cfad7" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] [[package]] -name = "cipher" -version = "0.4.4" +name = "cookie-factory" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.6", - "inout", - "zeroize", -] +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" [[package]] -name = "citationberg" -version = "0.6.1" +name = "cookie_store" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6597e8bdbca37f1f56e5a80d15857b0932aead21a78d20de49e99e74933046" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" dependencies = [ - "quick-xml 0.38.4", + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", "serde", + "serde_derive", + "serde_json", + "time", + "url", ] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "glob", + "core-foundation-sys", "libc", - "libloading 0.8.9", ] [[package]] -name = "clap" -version = "4.6.1" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "clap_builder", - "clap_derive", + "core-foundation-sys", + "libc", ] [[package]] -name = "clap-verbosity-flag" -version = "3.0.4" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d92b1fab272fe943881b77cc6e920d6543e5b1bfadbd5ed81c7c5a755742394" -dependencies = [ - "clap", - "log", - "tracing-core", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "clap_builder" -version = "4.6.0" +name = "core-graphics" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", ] [[package]] -name = "clap_complete" -version = "4.6.2" +name = "core-graphics-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "clap", + "bitflags 2.11.1", + "core-foundation 0.10.1", + "libc", ] [[package]] -name = "clap_derive" -version = "4.6.1" +name = "core_maths" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "libm", ] [[package]] -name = "clap_lex" -version = "1.1.0" +name = "coreaudio-rs" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "claude" -version = "0.1.0" +checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586" dependencies = [ - "cli-process", - "dirs 6.0.0", - "futures-util", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", + "bitflags 2.11.1", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", ] [[package]] -name = "claw" -version = "0.1.0" +name = "cpal" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7" dependencies = [ - "anyhow", - "axum 0.8.9", - "serde", - "serde_json", - "supabase-auth", - "tokio", - "tower 0.5.3", - "tracing", - "tracing-subscriber", - "zeroclaw-api", - "zeroclaw-channels", - "zeroclaw-config", - "zeroclaw-gateway", - "zeroclaw-infra", - "zeroclaw-providers", - "zeroclaw-runtime", + "alsa", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2 0.5.0", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.62.2", ] [[package]] -name = "cli" -version = "0.1.0" +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "agent-core", - "analytics", - "audio", - "audio-actual", - "audio-norm", - "audio-utils", - "chrono", - "clap", - "clap-verbosity-flag", - "clap_complete", - "cli-docs", - "clio", - "codex 0.1.0", - "colored", - "comfy-table", - "crossterm", - "db-cli", - "dirs 6.0.0", - "host", - "language", "libc", - "listener2-core", - "local-model", - "local-stt-core", - "local-stt-server", - "model-downloader", - "mp3", - "open", - "owhisper-client", - "owhisper-interface", - "portable-pty", - "ratatui", - "reqwest 0.13.2", - "rodio", - "serde", - "serde_json", - "strsim", - "strum 0.28.0", - "tempfile", - "textwrap", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "tracing-subscriber", - "transcript", - "url", - "uuid", - "vt100", ] [[package]] -name = "cli-docs" -version = "0.1.0" +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "clap", - "serde", - "serde_json", + "libc", ] [[package]] -name = "cli-process" -version = "0.1.0" +name = "crash-context" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3" dependencies = [ - "futures-util", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", + "cfg-if", + "libc", + "mach2 0.4.3", ] [[package]] -name = "clio" -version = "0.3.5" +name = "crash-handler" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7fc6734af48458f72f5a3fa7b840903606427d98a710256e808f76a965047d9" +checksum = "2066907075af649bcb8bcb1b9b986329b243677e6918b2d920aa64b0aac5ace3" dependencies = [ "cfg-if", - "clap", - "is-terminal", + "crash-context", "libc", - "tempfile", - "walkdir", - "windows-sys 0.42.0", + "mach2 0.4.3", + "parking_lot", ] [[package]] -name = "clipboard-win" -version = "5.4.1" +name = "crc" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ - "error-code", + "crc-catalog", ] [[package]] -name = "cloudsync" -version = "0.1.0" -dependencies = [ - "dirs 6.0.0", - "sqlx", - "thiserror 2.0.18", - "tokio", -] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "clru" -version = "0.6.3" +name = "crc-fast" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" dependencies = [ - "hashbrown 0.16.1", + "crc", + "digest", + "rand 0.9.4", + "regex", + "rustversion", ] [[package]] -name = "cmake" -version = "0.1.58" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cc", + "cfg-if", ] [[package]] -name = "cmov" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" - -[[package]] -name = "cobs" -version = "0.3.0" +name = "criterion" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ - "thiserror 2.0.18", + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", ] [[package]] -name = "codes-agency" -version = "0.1.9" +name = "criterion-plot" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809a7790247f949cc35a1f9b497fd13a79ee330e34f049c6e837685363d20faf" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ - "codes-common", - "serde", + "cast", + "itertools 0.13.0", ] [[package]] -name = "codes-common" -version = "0.1.9" +name = "cron" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b16b1037e7111ea4a4bd4a2b48733b990a76ecc321539858a5ab534792b287" +checksum = "089df96cf6a25253b4b6b6744d86f91150a3d4df546f31a95def47976b8cba97" dependencies = [ - "csv", - "tera", - "tracing", + "chrono", + "once_cell", + "phf 0.11.3", + "winnow 0.7.15", ] [[package]] -name = "codes-iso-639" -version = "0.1.5" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30dbd0357410908cd59147abbb5d40a9b9a5d9261285c272844cf53dbaa6f7a8" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "codes-agency", - "codes-common", - "csv", - "serde", - "tera", + "crossbeam-utils", ] [[package]] -name = "codex" -version = "0.1.0" +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cli-process", - "dirs 6.0.0", - "futures-util", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "toml 0.8.2", - "url", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "codex" -version = "0.2.0" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9589e1effc5cacbea347899645c654158b03b2053d24bb426fd3128ced6e423c" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "color-eyre" -version = "0.6.5" +name = "crossbeam-queue" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", + "crossbeam-utils", ] [[package]] -name = "color-spantrace" -version = "0.3.0" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", + "bitflags 2.11.1", + "crossterm_winapi", + "derive_more 2.1.1", + "document-features", + "futures-core", + "mio", + "parking_lot", + "rustix 1.1.4", + "signal-hook 0.3.18", + "signal-hook-mio", + "winapi", ] [[package]] -name = "color_quant" -version = "1.1.0" +name = "crossterm_winapi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] [[package]] -name = "colorchoice" -version = "1.0.5" +name = "crunchy" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "colored" -version = "3.1.1" +name = "crypto-bigint" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "windows-sys 0.61.2", + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", ] [[package]] -name = "combine" -version = "4.6.7" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "bytes", - "memchr", + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", ] [[package]] -name = "comemo" -version = "0.5.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c963350b2b08aa4b725d7802593245380ab53dacfedcaa971385fc33306c0d4" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "comemo-macros", - "parking_lot", - "rustc-hash 2.1.2", - "siphasher 1.0.2", - "slab", + "generic-array", + "typenum", ] [[package]] -name = "comemo-macros" -version = "0.5.1" +name = "cssparser" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c400139ba1389ef9e20ad2d87cda68b437a66483aa0da616bdf2cea7413853" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", "proc-macro2", "quote", - "syn 2.0.117", + "smallvec 1.15.1", + "syn 1.0.109", ] [[package]] -name = "comfy-table" -version = "7.2.2" +name = "cssparser" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ - "crossterm", - "unicode-segmentation", - "unicode-width 0.2.2", + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec 1.15.1", ] [[package]] -name = "compact_str" -version = "0.9.0" +name = "cssparser-macros" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ - "castaway", - "cfg-if", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", "itoa", - "rustversion", "ryu", - "static_assertions", + "serde_core", ] [[package]] -name = "compression-codecs" -version = "0.4.37" +name = "csv-core" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ - "compression-core", - "flate2", "memchr", ] [[package]] -name = "compression-core" -version = "0.4.31" +name = "ctor" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "crossbeam-utils", + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", ] [[package]] -name = "config" -version = "0.15.22" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "convert_case 0.6.0", - "indexmap 2.14.0", - "json5", - "pathdiff", - "ron", - "serde-untagged", - "serde_core", - "serde_json", - "toml 1.1.2+spec-1.1.0", - "winnow 1.0.1", - "yaml-rust2", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "console" -version = "0.16.3" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "encode_unicode", - "libc", - "unicode-width 0.2.2", - "windows-sys 0.61.2", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] [[package]] -name = "const-oid" -version = "0.10.2" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] [[package]] -name = "const-random" -version = "0.1.18" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "const-random-macro", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", ] [[package]] -name = "const-random-macro" -version = "0.1.16" +name = "darling_macro" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", + "darling_core 0.20.11", + "quote", + "syn 2.0.117", ] [[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.6.0" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "unicode-segmentation", + "darling_core 0.23.0", + "quote", + "syn 2.0.117", ] [[package]] -name = "convert_case" -version = "0.8.0" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ - "unicode-segmentation", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "convert_case" -version = "0.10.0" +name = "dasp" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "7381b67da416b639690ac77c73b86a7b5e64a29e31d1f75fb3b1102301ef355a" dependencies = [ - "unicode-segmentation", + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_signal", + "dasp_slice", + "dasp_window", ] [[package]] -name = "cookie" -version = "0.18.1" +name = "dasp_envelope" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6" dependencies = [ - "percent-encoding", - "time", - "version_check", + "dasp_frame", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", ] [[package]] -name = "cookie-factory" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" - -[[package]] -name = "cookie_store" -version = "0.22.1" +name = "dasp_frame" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" dependencies = [ - "cookie", - "document-features", - "idna", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", + "dasp_sample", ] [[package]] -name = "core-foundation" -version = "0.9.4" +name = "dasp_interpolate" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486" dependencies = [ - "core-foundation-sys", - "libc", + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", ] [[package]] -name = "core-foundation" -version = "0.10.1" +name = "dasp_peak" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf" dependencies = [ - "core-foundation-sys", - "libc", + "dasp_frame", + "dasp_sample", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "dasp_ring_buffer" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1" [[package]] -name = "core-graphics" -version = "0.25.0" +name = "dasp_rms" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa" dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", ] [[package]] -name = "core-graphics-types" -version = "0.2.0" +name = "dasp_sample" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "libc", -] +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] -name = "core_maths" -version = "0.1.1" +name = "dasp_signal" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7" dependencies = [ - "libm", + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_window", ] [[package]] -name = "coreaudio-rs" -version = "0.14.1" +name = "dasp_slice" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586" +checksum = "4e1c7335d58e7baedafa516cb361360ff38d6f4d3f9d9d5ee2a2fc8e27178fa1" dependencies = [ - "bitflags 2.11.1", - "libc", - "objc2-audio-toolbox", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", + "dasp_frame", + "dasp_sample", ] [[package]] -name = "cpal" -version = "0.17.3" +name = "dasp_window" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7" +checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076" dependencies = [ - "alsa", - "coreaudio-rs", "dasp_sample", - "jni", - "js-sys", - "libc", - "mach2 0.5.0", - "ndk", - "ndk-context", - "num-derive", - "num-traits", - "objc2", - "objc2-audio-toolbox", - "objc2-avf-audio", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.62.2", ] [[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +name = "data" +version = "0.1.0" dependencies = [ - "libc", + "owhisper-interface", + "serde", + "serde_json", ] [[package]] -name = "cpufeatures" -version = "0.3.0" +name = "data-encoding" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] -name = "crash-context" -version = "0.6.3" +name = "data-url" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "db-app" +version = "0.1.0" dependencies = [ - "cfg-if", - "libc", - "mach2 0.4.3", + "db-core", + "db-migrate", + "sqlx", + "tokio", ] [[package]] -name = "crash-handler" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2066907075af649bcb8bcb1b9b986329b243677e6918b2d920aa64b0aac5ace3" +name = "db-change" +version = "0.1.0" dependencies = [ - "cfg-if", - "crash-context", - "libc", - "mach2 0.4.3", - "parking_lot", + "sqlx", + "tokio", ] [[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +name = "db-core" +version = "0.1.0" dependencies = [ - "crc-catalog", + "backon", + "cloudsync", + "db-change", + "libsqlite3-sys", + "serde", + "sqlx", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc-fast" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" +name = "db-execute" +version = "0.1.0" dependencies = [ - "crc", - "digest 0.10.7", - "rand 0.9.4", - "regex", - "rustversion", + "db-core", + "serde", + "serde_json", + "sqlx", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +name = "db-migrate" +version = "0.1.0" dependencies = [ - "cfg-if", + "db-core", + "sqlx", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "criterion" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +name = "db-parser" +version = "0.1.0" dependencies = [ - "alloca", - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "itertools 0.13.0", - "num-traits", - "oorandom", - "page_size", - "plotters", - "rayon", - "regex", + "db-user", + "dirs 6.0.0", + "htmd", + "importer-core", + "legacy-db-core", + "owhisper-interface", "serde", "serde_json", - "tinytemplate", - "walkdir", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "criterion-plot" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +name = "db-reactive" +version = "0.1.0" dependencies = [ - "cast", - "itertools 0.13.0", + "anyhow", + "cloudsync", + "db-change", + "db-core", + "db-execute", + "db-migrate", + "serde", + "serde_json", + "specta", + "sqlx", + "tempfile", + "thiserror 2.0.18", + "tokio", + "uuid", ] [[package]] -name = "cron" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" +name = "db-user" +version = "0.1.0" dependencies = [ + "buffer", "chrono", - "once_cell", - "winnow 0.6.26", + "indoc", + "language", + "legacy-db-core", + "libsql", + "owhisper-interface", + "schemars 1.2.1", + "serde", + "serde_json", + "specta", + "strum 0.28.0", + "tokio", + "uuid", ] [[package]] -name = "cron" -version = "0.16.0" +name = "deadpool" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "089df96cf6a25253b4b6b6744d86f91150a3d4df546f31a95def47976b8cba97" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" dependencies = [ - "chrono", - "once_cell", - "phf 0.11.3", - "winnow 0.7.15", + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", ] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "deadpool-runtime" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "debugid" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "serde", + "uuid", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "deepgram" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "49bf11c4dc8fc1e7c94fc4198f82f64536fdb9eded7b5a076d9597d8b67e1fd1" dependencies = [ - "crossbeam-utils", + "anyhow", + "bytes", + "futures", + "http 1.4.0", + "pin-project", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_urlencoded", + "sha256", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.27.0", + "tokio-util", + "tracing", + "tungstenite 0.27.0", + "url", + "uuid", ] [[package]] -name = "crossbeam-queue" -version = "0.3.12" +name = "deflate64" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "denoise" +version = "0.1.0" dependencies = [ - "crossbeam-utils", + "approx", + "audio-snapshot", + "criterion", + "dasp", + "data", + "hound", + "onnx", + "realfft", + "rodio", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "der" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] -name = "crossterm" -version = "0.29.0" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "bitflags 2.11.1", - "crossterm_winapi", - "derive_more 2.1.1", - "document-features", - "futures-core", - "mio", - "parking_lot", - "rustix 1.1.4", - "signal-hook 0.3.18", - "signal-hook-mio", - "winapi", + "const-oid", + "pem-rfc7468 0.7.0", + "zeroize", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" +name = "der" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" dependencies = [ - "winapi", + "pem-rfc7468 1.0.0", + "zeroize", ] [[package]] -name = "crunchy" -version = "0.2.4" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", + "powerfmt", + "serde_core", ] [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "derive_arbitrary" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "derive_builder" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", + "derive_builder_macro", ] [[package]] -name = "crypto-common" -version = "0.2.1" +name = "derive_builder_core" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "hybrid-array", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "csscolorparser" -version = "0.6.2" +name = "derive_builder_macro" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ - "lab", - "phf 0.11.3", + "derive_builder_core", + "syn 2.0.117", ] [[package]] -name = "cssparser" -version = "0.29.6" +name = "derive_more" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", + "convert_case 0.4.0", "proc-macro2", "quote", - "smallvec 1.15.1", - "syn 1.0.109", + "rustc_version", + "syn 2.0.117", ] [[package]] -name = "cssparser" -version = "0.36.0" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf 0.13.1", - "smallvec 1.15.1", + "derive_more-impl", ] [[package]] -name = "cssparser-macros" -version = "0.6.1" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case 0.10.0", + "proc-macro2", "quote", + "rustc_version", "syn 2.0.117", + "unicode-xid", ] [[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +name = "desktop" +version = "0.0.0" dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", + "audio-actual", + "audio-mock", + "db-core", + "dirs 6.0.0", + "host", + "intercept", + "pico-args", + "ractor", + "sentry", + "serde", + "serde_json", + "specta", + "specta-typescript", + "strum 0.28.0", + "supervisor", + "tauri", + "tauri-build", + "tauri-plugin-agent", + "tauri-plugin-analytics", + "tauri-plugin-audio-priority", + "tauri-plugin-auth", + "tauri-plugin-automation", + "tauri-plugin-autostart", + "tauri-plugin-bedrock", + "tauri-plugin-calendar", + "tauri-plugin-clipboard-manager", + "tauri-plugin-db", + "tauri-plugin-deep-link", + "tauri-plugin-deeplink2", + "tauri-plugin-detect", + "tauri-plugin-dialog", + "tauri-plugin-dictation", + "tauri-plugin-dock", + "tauri-plugin-export", + "tauri-plugin-flag", + "tauri-plugin-fs-sync", + "tauri-plugin-fs2", + "tauri-plugin-git", + "tauri-plugin-hooks", + "tauri-plugin-http", + "tauri-plugin-icon", + "tauri-plugin-importer", + "tauri-plugin-js", + "tauri-plugin-local-llm", + "tauri-plugin-local-stt", + "tauri-plugin-mcp", + "tauri-plugin-messenger", + "tauri-plugin-misc", + "tauri-plugin-network", + "tauri-plugin-notification", + "tauri-plugin-notify", + "tauri-plugin-opener", + "tauri-plugin-opener2", + "tauri-plugin-os", + "tauri-plugin-overlay", + "tauri-plugin-path2", + "tauri-plugin-permissions", + "tauri-plugin-prevent-default", + "tauri-plugin-process", + "tauri-plugin-relay", + "tauri-plugin-sentry", + "tauri-plugin-settings", + "tauri-plugin-sfx", + "tauri-plugin-shell", + "tauri-plugin-shortcut", + "tauri-plugin-sidecar2", + "tauri-plugin-single-instance", + "tauri-plugin-store", + "tauri-plugin-store2", + "tauri-plugin-tantivy", + "tauri-plugin-template", + "tauri-plugin-todo", + "tauri-plugin-tracing", + "tauri-plugin-transcription", + "tauri-plugin-tray", + "tauri-plugin-updater", + "tauri-plugin-updater2", + "tauri-plugin-window-state", + "tauri-plugin-windows", + "tauri-specta", + "tempfile", + "tokio", + "tracing", ] [[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +name = "detect" +version = "0.1.0" dependencies = [ - "memchr", + "block2", + "bundle", + "cidre", + "isolang", + "language", + "lazy_static", + "libpulse-binding", + "macos-accessibility-client", + "objc2", + "objc2-app-kit", + "objc2-application-services", + "objc2-core-foundation", + "objc2-foundation", + "plist", + "regex", + "serde", + "specta", + "sysinfo", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", ] [[package]] -name = "ctor" -version = "0.2.9" +name = "deunicode" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.117", -] +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +name = "device-monitor" +version = "0.1.0" dependencies = [ - "cmov", + "audio-device", + "cidre", + "libpulse-binding", + "tracing", ] [[package]] -name = "cursor" +name = "dictation-ui-macos" version = "0.1.0" dependencies = [ - "base64 0.22.1", - "reqwest 0.13.2", + "objc2", + "objc2-app-kit", + "objc2-foundation", "serde", "serde_json", - "thiserror 2.0.18", - "tokio", - "url", + "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", ] [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "difflib" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] -name = "dagc" -version = "0.1.1" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b391a0dc2723958e71bff67bc8f4c1fa14f8734e0b0e4d65f96fd25f4fdd057" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] [[package]] -name = "darling" -version = "0.20.11" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", + "dirs-sys 0.5.0", ] [[package]] -name = "darling" -version = "0.23.0" +name = "dirs-sys" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] -name = "darling_core" -version = "0.20.11" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "dispatch2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "ident_case", "proc-macro2", "quote", - "strsim", "syn 2.0.117", ] [[package]] -name = "darling_macro" -version = "0.20.11" +name = "dlopen2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", + "dlopen2_derive", + "libc", + "once_cell", + "winapi", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "dlopen2_derive" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ - "darling_core 0.23.0", + "proc-macro2", "quote", "syn 2.0.117", ] [[package]] -name = "dashmap" -version = "6.1.0" +name = "dlv-list" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "const-random", ] [[package]] -name = "dasp" -version = "0.11.0" +name = "doc-comment" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7381b67da416b639690ac77c73b86a7b5e64a29e31d1f75fb3b1102301ef355a" -dependencies = [ - "dasp_envelope", - "dasp_frame", - "dasp_interpolate", - "dasp_peak", - "dasp_ring_buffer", - "dasp_rms", - "dasp_sample", - "dasp_signal", - "dasp_slice", - "dasp_window", -] +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] -name = "dasp_envelope" -version = "0.11.0" +name = "docker_credential" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" dependencies = [ - "dasp_frame", - "dasp_peak", - "dasp_ring_buffer", - "dasp_rms", - "dasp_sample", + "base64 0.21.7", + "serde", + "serde_json", ] [[package]] -name = "dasp_frame" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" +name = "docs" +version = "0.1.0" dependencies = [ - "dasp_sample", + "serde", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", ] [[package]] -name = "dasp_interpolate" -version = "0.11.0" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "dasp_frame", - "dasp_ring_buffer", - "dasp_sample", + "litrs", ] [[package]] -name = "dasp_peak" -version = "0.11.0" +name = "dom_query" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ - "dasp_frame", - "dasp_sample", + "bit-set", + "cssparser 0.36.0", + "foldhash 0.2.0", + "html5ever 0.38.0", + "precomputed-hash", + "selectors 0.36.1", + "tendril 0.5.0", ] [[package]] -name = "dasp_ring_buffer" -version = "0.11.0" +name = "dotenvy" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "dasp_rms" -version = "0.11.0" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa" -dependencies = [ - "dasp_frame", - "dasp_ring_buffer", - "dasp_sample", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "dasp_sample" -version = "0.11.0" +name = "downcast-rs" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" [[package]] -name = "dasp_signal" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7" +name = "download-interface" +version = "0.1.0" dependencies = [ - "dasp_envelope", - "dasp_frame", - "dasp_interpolate", - "dasp_peak", - "dasp_ring_buffer", - "dasp_rms", - "dasp_sample", - "dasp_window", + "dirs 6.0.0", + "serde", ] [[package]] -name = "dasp_slice" -version = "0.11.0" +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1c7335d58e7baedafa516cb361360ff38d6f4d3f9d9d5ee2a2fc8e27178fa1" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ - "dasp_frame", - "dasp_sample", + "serde", ] [[package]] -name = "dasp_window" -version = "0.11.1" +name = "dtoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076" -dependencies = [ - "dasp_sample", -] +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] -name = "data" -version = "0.1.0" +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ - "owhisper-interface", - "serde", - "serde_json", + "dtoa", ] [[package]] -name = "data-encoding" -version = "2.10.0" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "data-url" -version = "0.3.2" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "db-app" +name = "earshot" version = "0.1.0" -dependencies = [ - "db-core", - "db-migrate", - "sqlx", - "tokio", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6e21bffead7abb6d9202cfb3285b34b2e750335d8cf3460b004f886c6f6db" [[package]] -name = "db-change" -version = "0.1.0" +name = "ebur128" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e227cc62d64d6fe01abbef48134b9c1f17d470cef1e7a56337ad05b1f81df7f9" dependencies = [ - "sqlx", - "tokio", + "bitflags 1.3.2", + "dasp_frame", + "dasp_sample", + "smallvec 1.15.1", ] [[package]] -name = "db-cli" -version = "0.1.0" +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "clap", - "db-app", - "db-core", - "db-migrate", - "dirs 6.0.0", - "serde", - "serde_json", - "sqlx", - "tempfile", - "thiserror 2.0.18", - "tokio", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", ] [[package]] -name = "db-core" -version = "0.1.0" +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "backon", - "cloudsync", - "db-change", - "libsqlite3-sys", - "serde", - "sqlx", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", + "der 0.7.10", + "digest", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] -name = "db-execute" -version = "0.1.0" +name = "ecow" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" dependencies = [ - "db-core", "serde", - "serde_json", - "sqlx", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "db-migrate" -version = "0.1.0" -dependencies = [ - "db-core", - "sqlx", - "thiserror 2.0.18", - "tokio", ] [[package]] -name = "db-parser" -version = "0.1.0" +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "db-user", - "dirs 6.0.0", - "htmd", - "importer-core", - "legacy-db-core", - "owhisper-interface", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", + "pkcs8 0.10.2", + "signature 2.2.0", ] [[package]] -name = "db-reactive" -version = "0.1.0" +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "anyhow", - "cloudsync", - "db-change", - "db-core", - "db-execute", - "db-migrate", + "curve25519-dalek", + "ed25519", "serde", - "serde_json", - "specta", - "sqlx", - "tempfile", - "thiserror 2.0.18", - "tokio", - "uuid", + "sha2", + "subtle", + "zeroize", ] [[package]] -name = "db-user" -version = "0.1.0" +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ - "buffer", - "chrono", - "indoc", - "language", - "legacy-db-core", - "libsql", - "owhisper-interface", - "schemars 1.2.1", "serde", - "serde_json", - "specta", - "strum 0.28.0", - "tokio", - "uuid", ] [[package]] -name = "deadpool" +name = "elliptic-curve" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "deadpool-runtime", - "lazy_static", - "num_cpus", - "tokio", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", ] [[package]] -name = "deadpool-runtime" -version = "0.1.4" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.1", + "generic-array", + "group 0.13.0", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle", + "zeroize", +] [[package]] -name = "debugid" -version = "0.8.0" +name = "email_address" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" dependencies = [ "serde", - "uuid", ] [[package]] -name = "deepgram" -version = "0.7.0" +name = "embed-resource" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49bf11c4dc8fc1e7c94fc4198f82f64536fdb9eded7b5a076d9597d8b67e1fd1" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" dependencies = [ - "anyhow", - "bytes", - "futures", - "http 1.4.0", - "pin-project", - "reqwest 0.12.28", - "serde", - "serde_json", - "serde_urlencoded", - "sha256", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.27.0", - "tokio-util", - "tracing", - "tungstenite 0.27.0", - "url", - "uuid", + "cc", + "memchr", + "rustc_version", + "toml 0.9.12+spec-1.1.0", + "vswhom", + "winreg 0.55.0", ] [[package]] -name = "deflate64" -version = "0.1.12" +name = "embed_plist" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "deltae" -version = "0.3.2" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" - -[[package]] -name = "denoise" -version = "0.1.0" -dependencies = [ - "approx", - "audio-snapshot", - "criterion", - "dasp", - "data", - "hound", - "onnx", - "realfft", - "rodio", - "serde", - "thiserror 2.0.18", -] +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] -name = "der" +name = "embedded-io" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid 0.9.6", - "zeroize", -] +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", - "zeroize", -] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "der" -version = "0.8.0" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "pem-rfc7468 1.0.0", - "zeroize", + "cfg-if", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "encoding_rs_io" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" dependencies = [ - "powerfmt", - "serde_core", + "encoding_rs", ] [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "derive_builder" -version = "0.20.2" +name = "enum-ordinalize" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ - "derive_builder_macro", + "enum-ordinalize-derive", ] [[package]] -name = "derive_builder_core" -version = "0.20.2" +name = "enum-ordinalize-derive" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ - "darling 0.20.11", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] -name = "derive_builder_macro" -version = "0.20.2" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "derive_builder_core", - "syn 2.0.117", + "enumflags2_derive", + "serde", ] [[package]] -name = "derive_more" -version = "0.99.20" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", "syn 2.0.117", ] [[package]] -name = "derive_more" -version = "2.1.1" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "derive_more-impl" -version = "2.1.1" +name = "erased-serde" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case 0.10.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", - "unicode-xid", -] - -[[package]] -name = "desktop" -version = "0.0.0" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ - "audio-actual", - "audio-mock", - "db-core", - "dirs 6.0.0", - "host", - "intercept", - "pico-args", - "ractor", - "sentry", "serde", - "serde_json", - "specta", - "specta-typescript", - "strum 0.28.0", - "supervisor", - "tauri", - "tauri-build", - "tauri-plugin-activity-capture", - "tauri-plugin-agent", - "tauri-plugin-analytics", - "tauri-plugin-audio-priority", - "tauri-plugin-auth", - "tauri-plugin-automation", - "tauri-plugin-autostart", - "tauri-plugin-bedrock", - "tauri-plugin-calendar", - "tauri-plugin-clipboard-manager", - "tauri-plugin-db", - "tauri-plugin-deep-link", - "tauri-plugin-deeplink2", - "tauri-plugin-detect", - "tauri-plugin-dialog", - "tauri-plugin-dictation", - "tauri-plugin-dock", - "tauri-plugin-export", - "tauri-plugin-flag", - "tauri-plugin-fs-sync", - "tauri-plugin-fs2", - "tauri-plugin-git", - "tauri-plugin-hooks", - "tauri-plugin-http", - "tauri-plugin-icon", - "tauri-plugin-importer", - "tauri-plugin-js", - "tauri-plugin-local-llm", - "tauri-plugin-local-stt", - "tauri-plugin-mcp", - "tauri-plugin-messenger", - "tauri-plugin-misc", - "tauri-plugin-network", - "tauri-plugin-notification", - "tauri-plugin-notify", - "tauri-plugin-opener", - "tauri-plugin-opener2", - "tauri-plugin-os", - "tauri-plugin-overlay", - "tauri-plugin-path2", - "tauri-plugin-permissions", - "tauri-plugin-prevent-default", - "tauri-plugin-process", - "tauri-plugin-relay", - "tauri-plugin-screen", - "tauri-plugin-sentry", - "tauri-plugin-settings", - "tauri-plugin-sfx", - "tauri-plugin-shell", - "tauri-plugin-shortcut", - "tauri-plugin-sidecar2", - "tauri-plugin-single-instance", - "tauri-plugin-store", - "tauri-plugin-store2", - "tauri-plugin-tantivy", - "tauri-plugin-template", - "tauri-plugin-todo", - "tauri-plugin-tracing", - "tauri-plugin-transcription", - "tauri-plugin-tray", - "tauri-plugin-updater", - "tauri-plugin-updater2", - "tauri-plugin-window-state", - "tauri-plugin-windows", - "tauri-specta", - "tempfile", - "tokio", - "tracing", + "serde_core", + "typeid", ] [[package]] -name = "detect" -version = "0.1.0" +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "block2", - "bundle", - "cidre", - "isolang", - "language", - "lazy_static", - "libpulse-binding", - "macos-accessibility-client", - "objc2", - "objc2-app-kit", - "objc2-application-services", - "objc2-core-foundation", - "objc2-foundation", - "plist", - "regex", - "serde", - "specta", - "sysinfo", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "deunicode" -version = "1.6.2" +name = "error-code" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] -name = "device-monitor" -version = "0.1.0" -dependencies = [ - "audio-device", - "cidre", - "libpulse-binding", - "tracing", -] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" [[package]] -name = "devin" -version = "0.1.0" +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" dependencies = [ - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "url", - "wiremock", + "cfg-if", + "home", + "windows-sys 0.59.0", ] [[package]] -name = "dialoguer" -version = "0.12.0" +name = "euclid" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ - "console", - "fuzzy-matcher", - "shell-words", - "tempfile", - "zeroize", + "num-traits", ] [[package]] -name = "dictation-ui-macos" -version = "0.1.0" +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "serde", - "serde_json", - "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "digest" -version = "0.10.7" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.6", - "subtle", + "event-listener", + "pin-project-lite", ] [[package]] -name = "digest" -version = "0.11.2" +name = "eventsource-stream" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" dependencies = [ - "block-buffer 0.12.0", - "const-oid 0.10.2", - "crypto-common 0.2.1", - "ctutils", + "futures-core", + "nom 7.1.3", + "pin-project-lite", ] [[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +name = "export-core" +version = "0.1.0" dependencies = [ - "dirs-sys 0.5.0", + "chrono", + "pulldown-cmark", + "serde", + "specta", + "thiserror 2.0.18", + "typst", + "typst-assets", + "typst-pdf", ] [[package]] -name = "dirs" -version = "4.0.0" +name = "extended" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" [[package]] -name = "dirs" -version = "6.0.0" +name = "eyre" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ - "dirs-sys 0.5.0", + "indenter", + "once_cell", ] [[package]] -name = "dirs-sys" -version = "0.3.7" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] -name = "dirs-sys" -version = "0.5.0" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "dispatch2" -version = "0.3.1" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "displaydoc" -version = "0.2.5" +name = "fancy-regex" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] -name = "dlib" -version = "0.5.3" +name = "fancy-regex" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" dependencies = [ - "libloading 0.8.9", + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] -name = "dlopen2" -version = "0.8.2" +name = "fast-srgb8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] -name = "dlopen2_derive" -version = "0.4.3" +name = "fastdivide" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" [[package]] -name = "dlv-list" -version = "0.5.2" +name = "faster-hex" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ - "const-random", + "heapless", + "serde", ] [[package]] -name = "doc-comment" -version = "0.3.4" +name = "fastrand" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] -name = "docker_credential" -version = "1.3.2" +name = "fax" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", + "fax_derive", ] [[package]] -name = "docs" -version = "0.1.0" +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ - "serde", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "document-features" -version = "0.2.12" +name = "fdeflate" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ - "litrs", + "simd-adler32", ] [[package]] -name = "dom_query" -version = "0.27.0" +name = "ff" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ - "bit-set 0.8.0", - "cssparser 0.36.0", - "foldhash 0.2.0", - "html5ever 0.38.0", - "precomputed-hash", - "selectors 0.36.1", - "tendril 0.5.0", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] -name = "downcast-rs" -version = "2.0.2" +name = "field-offset" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] [[package]] -name = "download-interface" +name = "file" version = "0.1.0" dependencies = [ + "base64 0.22.1", + "crc32fast", "dirs 6.0.0", - "serde", + "download-interface", + "futures-util", + "reqwest 0.13.2", + "s3", + "tempfile", + "testcontainers-modules", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "wiremock", ] [[package]] -name = "dpi" -version = "0.1.2" +name = "file-id" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" dependencies = [ - "serde", + "windows-sys 0.60.2", ] [[package]] -name = "drm" -version = "0.14.1" +name = "file-rotate" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +checksum = "6e8e2fa049328a1f3295991407a88585805d126dfaadf74b9fe8c194c730aafc" dependencies = [ - "bitflags 2.11.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "libc", - "rustix 0.38.44", + "chrono", + "flate2", ] [[package]] -name = "drm-ffi" -version = "0.9.1" +name = "filetime" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ - "drm-sys", - "rustix 1.1.4", + "cfg-if", + "libc", + "libredox", ] [[package]] -name = "drm-fourcc" -version = "2.2.0" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "drm-sys" -version = "0.8.1" +name = "findshlibs" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" dependencies = [ + "cc", + "lazy_static", "libc", - "linux-raw-sys 0.9.4", + "winapi", ] [[package]] -name = "dtoa" -version = "1.0.11" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] -name = "dtoa-short" -version = "0.3.5" +name = "flate2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "dtoa", + "crc32fast", + "miniz_oxide", + "zlib-rs 0.6.3", ] [[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] -name = "earshot" -version = "0.1.0" +name = "float-cmp" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6e21bffead7abb6d9202cfb3285b34b2e750335d8cf3460b004f886c6f6db" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] [[package]] -name = "ebur128" -version = "0.1.10" +name = "fluent-uri" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e227cc62d64d6fe01abbef48134b9c1f17d470cef1e7a56337ad05b1f81df7f9" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ - "bitflags 1.3.2", - "dasp_frame", - "dasp_sample", - "smallvec 1.15.1", + "borrow-or-share", + "ref-cast", + "serde", ] [[package]] -name = "ecdsa" -version = "0.14.8" +name = "flume" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", + "futures-core", + "futures-sink", + "spin", ] [[package]] -name = "ecdsa" -version = "0.16.9" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der 0.7.10", - "digest 0.10.7", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "ecow" -version = "0.2.6" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" -dependencies = [ - "serde", -] +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "ed25519" -version = "2.2.3" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", -] +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] -name = "ed25519-dalek" -version = "2.2.0" +name = "font-types" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2 0.10.9", - "subtle", - "zeroize", + "bytemuck", ] [[package]] -name = "either" -version = "1.15.0" +name = "fontconfig-parser" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ - "serde", + "roxmltree", ] [[package]] -name = "elliptic-curve" -version = "0.12.3" +name = "fontdb" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", ] [[package]] -name = "elliptic-curve" -version = "0.13.8" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", - "digest 0.10.7", - "ff 0.13.1", - "generic-array", - "group 0.13.0", - "hkdf", - "pem-rfc7468 0.7.0", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "sec1 0.7.3", - "subtle", - "zeroize", + "foreign-types-shared 0.1.1", ] [[package]] -name = "email" -version = "0.1.0" +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "url", + "foreign-types-macros", + "foreign-types-shared 0.3.1", ] [[package]] -name = "email-encoding" -version = "0.4.1" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ - "base64 0.22.1", - "memchr", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "email_address" -version = "0.2.9" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" -dependencies = [ - "serde", -] +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "embed-resource" -version = "3.0.8" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.12+spec-1.1.0", - "vswhom", - "winreg 0.55.0", -] +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] -name = "embed_plist" +name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] [[package]] -name = "embedded-io" -version = "0.4.0" +name = "fraction" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] [[package]] -name = "embedded-io" -version = "0.6.1" +name = "from_variant" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +checksum = "308530a56b099da144ebc5d8e179f343ad928fa2b3558d1eb3db9af18d6eff43" +dependencies = [ + "swc_macros_common", + "syn 2.0.117", +] [[package]] -name = "embedding" +name = "frontmatter" version = "0.1.0" dependencies = [ - "approx", - "data", - "knf-rs", - "onnx", + "insta", "serde", "serde_json", + "serde_yaml", "thiserror 2.0.18", ] [[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "encoding_rs_io" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +name = "fs-format" +version = "0.1.0" dependencies = [ - "encoding_rs", + "serde", + "serde_json", + "specta", ] [[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enum-ordinalize" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +name = "fs-sync-core" +version = "0.1.0" dependencies = [ - "enum-ordinalize-derive", + "assert_fs", + "audio-norm", + "audio-utils", + "chrono", + "criterion", + "data", + "frontmatter", + "fs-format", + "glob", + "predicates", + "rayon", + "serde", + "serde_json", + "serde_yaml", + "specta", + "tauri-specta", + "tempfile", + "thiserror 2.0.18", + "tiptap", + "tracing", + "uuid", ] [[package]] -name = "enum-ordinalize-derive" -version = "4.3.2" +name = "fs4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "rustix 1.1.4", + "windows-sys 0.59.0", ] [[package]] -name = "enumflags2" -version = "0.7.12" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] -name = "enumflags2_derive" -version = "0.7.12" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "libc", ] [[package]] -name = "env_filter" -version = "1.0.1" +name = "futf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ - "log", - "regex", + "mac", + "new_debug_unreachable", ] [[package]] -name = "env_logger" -version = "0.11.10" +name = "futures" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "envy" -version = "0.4.2" +name = "futures-channel" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ - "serde", + "futures-core", + "futures-sink", ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "futures-core" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] -name = "erased-serde" -version = "0.4.10" +name = "futures-executor" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ - "serde", - "serde_core", - "typeid", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "errno" -version = "0.3.14" +name = "futures-intrusive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ - "libc", - "windows-sys 0.61.2", + "futures-core", + "lock_api", + "parking_lot", ] [[package]] -name = "error-code" -version = "3.3.2" +name = "futures-io" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] -name = "escape8259" -version = "0.5.3" +name = "futures-lite" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] -name = "etcetera" -version = "0.10.0" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "cfg-if", - "home", - "windows-sys 0.59.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "euclid" -version = "0.22.14" +name = "futures-sink" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", -] +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] -name = "event-listener" -version = "2.5.3" +name = "futures-task" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] -name = "event-listener" -version = "5.4.1" +name = "futures-timer" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] -name = "event-listener-strategy" -version = "0.5.4" +name = "futures-util" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "event-listener 5.4.1", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", "pin-project-lite", + "slab", ] [[package]] -name = "eventsource-stream" -version = "0.2.3" +name = "fxhash" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "futures-core", - "nom 7.1.3", - "pin-project-lite", + "byteorder", ] [[package]] -name = "exa" +name = "gbnf" version = "0.1.0" dependencies = [ - "reqwest 0.13.2", - "schemars 1.2.1", + "colored", + "gbnf-validator", + "indoc", + "insta", "serde", "serde_json", "specta", - "thiserror 2.0.18", - "tokio", - "url", + "tracing", ] [[package]] -name = "exedev" +name = "gbnf-validator" version = "0.1.0" +source = "git+https://github.com/fastrepl/gbnf-validator?rev=3dec055#3dec0551311840ae8c9a0feb10e556dd18ffe291" dependencies = [ - "base64 0.22.1", - "chrono", - "rand_core 0.6.4", - "reqwest 0.13.2", - "serde", - "serde_json", - "shell-escape", - "ssh-key", - "thiserror 2.0.18", - "tokio", - "url", + "anyhow", + "tempfile", ] [[package]] -name = "export-core" -version = "0.1.0" +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" dependencies = [ - "chrono", - "pulldown-cmark", - "serde", - "specta", - "thiserror 2.0.18", - "typst", - "typst-assets", - "typst-pdf", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", ] [[package]] -name = "extended" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" - -[[package]] -name = "eyre" -version = "0.6.12" +name = "gdk-pixbuf" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" dependencies = [ - "indenter", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", "once_cell", ] [[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fancy-regex" -version = "0.11.0" +name = "gdk-pixbuf-sys" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" dependencies = [ - "bit-set 0.5.3", - "regex", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "fancy-regex" -version = "0.16.2" +name = "gdk-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" dependencies = [ - "bit-set 0.8.0", - "regex-automata", - "regex-syntax", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.2.2", ] [[package]] -name = "fancy-regex" -version = "0.17.0" +name = "gdkwayland-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" dependencies = [ - "bit-set 0.8.0", - "regex-automata", - "regex-syntax", + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps 6.2.2", ] [[package]] -name = "fast-srgb8" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" - -[[package]] -name = "fastdivide" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" - -[[package]] -name = "faster-hex" -version = "0.10.0" +name = "gdkx11" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" dependencies = [ - "heapless", - "serde", + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", ] [[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fax" -version = "0.2.6" +name = "gdkx11-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" dependencies = [ - "fax_derive", + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.2.2", + "x11", ] [[package]] -name = "fax_derive" -version = "0.2.0" +name = "generic-array" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "typenum", + "version_check", + "zeroize", ] [[package]] -name = "fdeflate" -version = "0.3.7" +name = "gethostname" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "simd-adler32", + "rustix 1.1.4", + "windows-link 0.2.1", ] [[package]] -name = "ff" -version = "0.12.1" +name = "getrandom" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "rand_core 0.6.4", - "subtle", + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] -name = "ff" -version = "0.13.1" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "rand_core 0.6.4", - "subtle", + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] -name = "fiat-crypto" -version = "0.2.9" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] [[package]] -name = "field-offset" -version = "0.3.6" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "memoffset", - "rustc_version", + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", ] [[package]] -name = "file" +name = "gguf" version = "0.1.0" dependencies = [ - "base64 0.22.1", - "crc32fast", + "byteorder", "dirs 6.0.0", - "download-interface", - "futures-util", - "reqwest 0.13.2", - "s3", - "tempfile", - "testcontainers-modules", + "memmap2", + "strum 0.28.0", "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", - "wiremock", ] [[package]] -name = "file-id" -version = "0.2.3" +name = "gif" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ - "windows-sys 0.60.2", + "color_quant", + "weezl", ] [[package]] -name = "file-rotate" -version = "0.8.0" +name = "gif" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8e2fa049328a1f3295991407a88585805d126dfaadf74b9fe8c194c730aafc" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ - "chrono", - "flate2", + "color_quant", + "weezl", ] [[package]] -name = "filedescriptor" -version = "0.8.3" +name = "gimli" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] -name = "filetime" -version = "0.2.27" +name = "gio" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" dependencies = [ - "cfg-if", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", "libc", - "libredox", + "once_cell", + "pin-project-lite", + "smallvec 1.15.1", + "thiserror 1.0.69", ] [[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "findshlibs" -version = "0.10.2" +name = "gio-sys" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" dependencies = [ - "cc", - "lazy_static", + "glib-sys", + "gobject-sys", "libc", + "system-deps 6.2.2", "winapi", ] [[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" +name = "github-issues" +version = "0.1.0" +dependencies = [ + "hypr-http-utils", + "serde", + "serde_json", + "specta", + "thiserror 2.0.18", + "tokio", + "urlencoding", + "utoipa", +] [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "gix" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8" +dependencies = [ + "gix-actor 0.35.6", + "gix-attributes 0.26.1", + "gix-command", + "gix-commitgraph 0.28.0", + "gix-config 0.45.1", + "gix-credentials", + "gix-date 0.10.7", + "gix-diff 0.52.1", + "gix-dir 0.14.1", + "gix-discover 0.40.1", + "gix-features 0.42.1", + "gix-filter 0.19.2", + "gix-fs 0.15.0", + "gix-glob 0.20.1", + "gix-hash 0.18.0", + "gix-hashtable 0.8.1", + "gix-ignore 0.15.0", + "gix-index 0.40.1", + "gix-lock 17.1.0", + "gix-negotiate", + "gix-object 0.49.1", + "gix-odb 0.69.1", + "gix-pack 0.59.1", + "gix-path", + "gix-pathspec 0.11.0", + "gix-prompt", + "gix-protocol 0.50.1", + "gix-ref 0.52.1", + "gix-refspec 0.30.1", + "gix-revision 0.34.1", + "gix-revwalk 0.20.1", + "gix-sec 0.11.0", + "gix-shallow 0.4.0", + "gix-status 0.19.1", + "gix-submodule 0.19.1", + "gix-tempfile 17.1.0", + "gix-trace", + "gix-transport 0.47.0", + "gix-traverse 0.46.2", + "gix-url 0.31.0", + "gix-utils", + "gix-validate", + "gix-worktree 0.41.0", + "gix-worktree-state", + "once_cell", + "smallvec 1.15.1", + "thiserror 2.0.18", +] [[package]] -name = "fixedbitset" -version = "0.5.7" +name = "gix" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +checksum = "3d8284d86a2f5c0987fbf7219a128815cc04af5a18f5fd7eec6a76d83c2b78cc" +dependencies = [ + "gix-actor 0.37.1", + "gix-attributes 0.29.0", + "gix-command", + "gix-commitgraph 0.31.0", + "gix-config 0.50.0", + "gix-date 0.12.1", + "gix-diff 0.57.1", + "gix-dir 0.19.0", + "gix-discover 0.45.0", + "gix-features 0.45.2", + "gix-filter 0.24.1", + "gix-fs 0.18.2", + "gix-glob 0.23.0", + "gix-hash 0.21.2", + "gix-hashtable 0.11.0", + "gix-ignore 0.18.0", + "gix-index 0.45.1", + "gix-lock 20.0.1", + "gix-object 0.54.1", + "gix-odb 0.74.0", + "gix-pack 0.64.1", + "gix-path", + "gix-pathspec 0.14.0", + "gix-protocol 0.55.0", + "gix-ref 0.57.0", + "gix-refspec 0.35.0", + "gix-revision 0.39.0", + "gix-revwalk 0.25.0", + "gix-sec 0.12.2", + "gix-shallow 0.7.0", + "gix-status 0.24.0", + "gix-submodule 0.24.0", + "gix-tempfile 20.0.1", + "gix-trace", + "gix-traverse 0.51.1", + "gix-url 0.34.0", + "gix-utils", + "gix-validate", + "gix-worktree 0.46.0", + "parking_lot", + "signal-hook 0.3.18", + "smallvec 1.15.1", + "thiserror 2.0.18", +] [[package]] -name = "flate2" -version = "1.1.9" +name = "gix-actor" +version = "0.35.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "987a51a7e66db6ef4dc030418eb2a42af6b913a79edd8670766122d8af3ba59e" dependencies = [ - "crc32fast", - "miniz_oxide", - "zlib-rs 0.6.3", + "bstr", + "gix-date 0.10.7", + "gix-utils", + "itoa", + "thiserror 2.0.18", + "winnow 0.7.15", ] [[package]] -name = "float-cmp" -version = "0.9.0" +name = "gix-actor" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "c345528d405eab51d20f505f5fe1a4680973953694e0292c6bbe97827daa55c4" +dependencies = [ + "bstr", + "gix-date 0.12.1", + "gix-utils", + "itoa", + "thiserror 2.0.18", + "winnow 0.7.15", +] [[package]] -name = "float-cmp" -version = "0.10.0" +name = "gix-attributes" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +checksum = "6f50d813d5c2ce9463ba0c29eea90060df08e38ad8f34b8a192259f8bce5c078" dependencies = [ - "num-traits", + "bstr", + "gix-glob 0.20.1", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec 1.15.1", + "thiserror 2.0.18", + "unicode-bom", ] [[package]] -name = "fluent-uri" -version = "0.4.1" +name = "gix-attributes" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +checksum = "f47dabf8a50f1558c3a55d978440c7c4f22f87ac897bef03b4edbc96f6115966" dependencies = [ - "borrow-or-share", - "ref-cast", - "serde", + "bstr", + "gix-glob 0.23.0", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec 1.15.1", + "thiserror 2.0.18", + "unicode-bom", ] [[package]] -name = "flume" -version = "0.11.1" +name = "gix-bitmap" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" dependencies = [ - "futures-core", - "futures-sink", - "spin", + "thiserror 2.0.18", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "gix-chunk" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" +dependencies = [ + "thiserror 2.0.18", +] [[package]] -name = "foldhash" -version = "0.1.5" +name = "gix-command" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "46f9c425730a654835351e6da8c3c69ba1804f8b8d4e96d027254151138d5c64" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] [[package]] -name = "foldhash" -version = "0.2.0" +name = "gix-commitgraph" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951" +dependencies = [ + "bstr", + "gix-chunk", + "gix-hash 0.18.0", + "memmap2", + "thiserror 2.0.18", +] [[package]] -name = "font-types" -version = "0.10.1" +name = "gix-commitgraph" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +checksum = "efdcba8048045baf15225daf949d597c3e6183d130245e22a7fbd27084abe63a" dependencies = [ - "bytemuck", + "bstr", + "gix-chunk", + "gix-hash 0.21.2", + "memmap2", + "thiserror 2.0.18", ] [[package]] -name = "fontconfig-parser" -version = "0.5.8" +name = "gix-config" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" dependencies = [ - "roxmltree", + "bstr", + "gix-config-value 0.15.3", + "gix-features 0.42.1", + "gix-glob 0.20.1", + "gix-path", + "gix-ref 0.52.1", + "gix-sec 0.11.0", + "memchr", + "once_cell", + "smallvec 1.15.1", + "thiserror 2.0.18", + "unicode-bom", + "winnow 0.7.15", ] [[package]] -name = "fontdb" -version = "0.23.0" +name = "gix-config" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +checksum = "b58e2ff8eef96b71f2c5e260f02ca0475caff374027c5cc5a29bda69fac67404" dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser", + "bstr", + "gix-config-value 0.16.0", + "gix-features 0.45.2", + "gix-glob 0.23.0", + "gix-path", + "gix-ref 0.57.0", + "gix-sec 0.12.2", + "memchr", + "smallvec 1.15.1", + "thiserror 2.0.18", + "unicode-bom", + "winnow 0.7.15", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "gix-config-value" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "2c489abb061c74b0c3ad790e24a606ef968cebab48ec673d6a891ece7d5aef64" dependencies = [ - "foreign-types-shared 0.1.1", + "bitflags 2.11.1", + "bstr", + "gix-path", + "libc", + "thiserror 2.0.18", ] [[package]] -name = "foreign-types" -version = "0.5.0" +name = "gix-config-value" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +checksum = "2409cffa4fe8b303847d5b6ba8df9da9ba65d302fc5ee474ea0cac5afde79840" dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", + "bitflags 2.11.1", + "bstr", + "gix-path", + "libc", + "thiserror 2.0.18", ] [[package]] -name = "foreign-types-macros" -version = "0.2.3" +name = "gix-credentials" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "ce1c7307e36026b6088e5b12014ffe6d4f509c911ee453e22a7be4003a159c9b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bstr", + "gix-command", + "gix-config-value 0.15.3", + "gix-path", + "gix-prompt", + "gix-sec 0.11.0", + "gix-trace", + "gix-url 0.31.0", + "thiserror 2.0.18", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "gix-date" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "661245d045aa7c16ba4244daaabd823c562c3e45f1f25b816be2c57ee09f2171" +dependencies = [ + "bstr", + "itoa", + "jiff", + "smallvec 1.15.1", + "thiserror 2.0.18", +] [[package]] -name = "foreign-types-shared" -version = "0.3.1" +name = "gix-date" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +checksum = "fe4a31bab8159e233094fa70d2e5fd3ec6f19e593f67e6ae01281daa48f8d8e7" +dependencies = [ + "bstr", + "itoa", + "jiff", + "smallvec 1.15.1", + "thiserror 2.0.18", +] [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "gix-diff" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252" dependencies = [ - "percent-encoding", + "bstr", + "gix-attributes 0.26.1", + "gix-command", + "gix-filter 0.19.2", + "gix-fs 0.15.0", + "gix-hash 0.18.0", + "gix-index 0.40.1", + "gix-object 0.49.1", + "gix-path", + "gix-pathspec 0.11.0", + "gix-tempfile 17.1.0", + "gix-trace", + "gix-traverse 0.46.2", + "gix-worktree 0.41.0", + "imara-diff", + "thiserror 2.0.18", ] [[package]] -name = "fraction" -version = "0.15.3" +name = "gix-diff" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +checksum = "3506936e63ce14cd54b5f28ed06c8e43b92ef9f41c2238cc0bc271a9259b4e90" dependencies = [ - "lazy_static", - "num", + "bstr", + "gix-attributes 0.29.0", + "gix-command", + "gix-filter 0.24.1", + "gix-fs 0.18.2", + "gix-hash 0.21.2", + "gix-index 0.45.1", + "gix-object 0.54.1", + "gix-path", + "gix-pathspec 0.14.0", + "gix-tempfile 20.0.1", + "gix-trace", + "gix-traverse 0.51.1", + "gix-worktree 0.46.0", + "imara-diff", + "thiserror 2.0.18", ] [[package]] -name = "from_variant" -version = "2.0.2" +name = "gix-dir" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308530a56b099da144ebc5d8e179f343ad928fa2b3558d1eb3db9af18d6eff43" +checksum = "01e6e2dc5b8917142d0ffe272209d1671e45b771e433f90186bc71c016792e87" dependencies = [ - "swc_macros_common", - "syn 2.0.117", + "bstr", + "gix-discover 0.40.1", + "gix-fs 0.15.0", + "gix-ignore 0.15.0", + "gix-index 0.40.1", + "gix-object 0.49.1", + "gix-path", + "gix-pathspec 0.11.0", + "gix-trace", + "gix-utils", + "gix-worktree 0.41.0", + "thiserror 2.0.18", ] [[package]] -name = "frontmatter" -version = "0.1.0" +name = "gix-dir" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d9fad32d2eb8b0129850874246569e801b6d5877e0c41356c23e9e2501e06" dependencies = [ - "insta", - "serde", - "serde_json", - "serde_yaml", + "bstr", + "gix-discover 0.45.0", + "gix-fs 0.18.2", + "gix-ignore 0.18.0", + "gix-index 0.45.1", + "gix-object 0.54.1", + "gix-path", + "gix-pathspec 0.14.0", + "gix-trace", + "gix-utils", + "gix-worktree 0.46.0", "thiserror 2.0.18", ] [[package]] -name = "fs-err" -version = "2.11.0" +name = "gix-discover" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c" dependencies = [ - "autocfg", + "bstr", + "dunce", + "gix-fs 0.15.0", + "gix-hash 0.18.0", + "gix-path", + "gix-ref 0.52.1", + "gix-sec 0.11.0", + "thiserror 2.0.18", ] [[package]] -name = "fs-format" -version = "0.1.0" +name = "gix-discover" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ce096dc132533802a09d6fd5d4008858f2038341dfe2e69e0d0239edb359de" dependencies = [ - "serde", - "serde_json", - "specta", + "bstr", + "dunce", + "gix-fs 0.18.2", + "gix-hash 0.21.2", + "gix-path", + "gix-ref 0.57.0", + "gix-sec 0.12.2", + "thiserror 2.0.18", ] [[package]] -name = "fs-sync-core" -version = "0.1.0" -dependencies = [ - "assert_fs", - "audio-norm", - "audio-utils", - "chrono", - "criterion", - "data", - "frontmatter", - "fs-format", - "glob", - "predicates", - "rayon", - "serde", - "serde_json", - "serde_yaml", - "specta", - "tauri-specta", - "tempfile", - "thiserror 2.0.18", - "tiptap", - "tracing", - "uuid", -] - -[[package]] -name = "fs4" -version = "0.13.1" +name = "gix-features" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" dependencies = [ - "rustix 1.1.4", - "windows-sys 0.59.0", + "bytes", + "crc32fast", + "flate2", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "prodash 29.0.2", + "thiserror 2.0.18", + "walkdir", ] [[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "fsevent-sys" -version = "4.1.0" +name = "gix-features" +version = "0.45.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +checksum = "d56aad357ae016449434705033df644ac6253dfcf1281aad3af3af9e907560d1" dependencies = [ + "crc32fast", + "gix-path", + "gix-trace", + "gix-utils", "libc", + "once_cell", + "prodash 30.0.1", + "thiserror 2.0.18", + "walkdir", + "zlib-rs 0.5.5", ] [[package]] -name = "futf" -version = "0.1.5" +name = "gix-filter" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +checksum = "ecf004912949bbcf308d71aac4458321748ecb59f4d046830d25214208c471f1" dependencies = [ - "mac 0.1.1", - "new_debug_unreachable", + "bstr", + "encoding_rs", + "gix-attributes 0.26.1", + "gix-command", + "gix-hash 0.18.0", + "gix-object 0.49.1", + "gix-packetline-blocking", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "futures" -version = "0.3.32" +name = "gix-filter" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "10c02464962482570c1f94ad451a608c4391514f803e8074662d02c5629a25dc" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "bstr", + "encoding_rs", + "gix-attributes 0.29.0", + "gix-command", + "gix-hash 0.21.2", + "gix-object 0.54.1", + "gix-packetline 0.20.0", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "futures-channel" -version = "0.3.32" +name = "gix-fs" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" dependencies = [ - "futures-core", - "futures-sink", + "bstr", + "fastrand", + "gix-features 0.42.1", + "gix-path", + "gix-utils", + "thiserror 2.0.18", ] [[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" +name = "gix-fs" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "785b9c499e46bc78d7b81c148c21b3fca18655379ee729a856ed19ce50d359ec" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "bstr", + "fastrand", + "gix-features 0.45.2", + "gix-path", + "gix-utils", + "thiserror 2.0.18", ] [[package]] -name = "futures-intrusive" -version = "0.5.0" +name = "gix-glob" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" dependencies = [ - "futures-core", - "lock_api", - "parking_lot", + "bitflags 2.11.1", + "bstr", + "gix-features 0.42.1", + "gix-path", ] [[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" +name = "gix-glob" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +checksum = "e8546300aee4c65c5862c22a3e321124a69b654a61a8b60de546a9284812b7e2" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "bitflags 2.11.1", + "bstr", + "gix-features 0.45.2", + "gix-path", ] [[package]] -name = "futures-macro" -version = "0.3.32" +name = "gix-hash" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "faster-hex", + "gix-features 0.42.1", + "sha1-checked", + "thiserror 2.0.18", ] [[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-timer" -version = "3.0.3" +name = "gix-hash" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "e153930f42ccdab8a3306b1027cd524879f6a8996cd0c474d18b0e56cae7714d" +dependencies = [ + "faster-hex", + "gix-features 0.45.2", + "sha1-checked", + "thiserror 2.0.18", +] [[package]] -name = "futures-util" -version = "0.3.32" +name = "gix-hashtable" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", + "gix-hash 0.18.0", + "hashbrown 0.14.5", + "parking_lot", ] [[package]] -name = "fuzzy-matcher" -version = "0.3.7" +name = "gix-hashtable" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +checksum = "222f7428636020bef272a87ed833ea48bf5fb3193f99852ae16fbb5a602bd2f0" dependencies = [ - "thread_local", + "gix-hash 0.21.2", + "hashbrown 0.16.1", + "parking_lot", ] [[package]] -name = "fxhash" -version = "0.2.1" +name = "gix-ignore" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" dependencies = [ - "byteorder", + "bstr", + "gix-glob 0.20.1", + "gix-path", + "gix-trace", + "unicode-bom", ] [[package]] -name = "gbm" +name = "gix-ignore" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +checksum = "dfa727fdf54fd9fb53fa3fbb1a5c17172d3073e8e336bf155f3cac3e25b81b21" dependencies = [ - "bitflags 2.11.1", - "drm", - "drm-fourcc", - "gbm-sys", - "libc", - "wayland-backend", - "wayland-server", + "bstr", + "gix-glob 0.23.0", + "gix-path", + "gix-trace", + "unicode-bom", ] [[package]] -name = "gbm-sys" -version = "0.4.0" +name = "gix-index" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" dependencies = [ + "bitflags 2.11.1", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features 0.42.1", + "gix-fs 0.15.0", + "gix-hash 0.18.0", + "gix-lock 17.1.0", + "gix-object 0.49.1", + "gix-traverse 0.46.2", + "gix-utils", + "gix-validate", + "hashbrown 0.14.5", + "itoa", "libc", + "memmap2", + "rustix 1.1.4", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "gbnf" -version = "0.1.0" -dependencies = [ - "colored", - "gbnf-validator", - "indoc", - "insta", - "serde", - "serde_json", - "specta", - "tracing", -] - -[[package]] -name = "gbnf-validator" -version = "0.1.0" -source = "git+https://github.com/fastrepl/gbnf-validator?rev=3dec055#3dec0551311840ae8c9a0feb10e556dd18ffe291" -dependencies = [ - "anyhow", - "tempfile", -] - -[[package]] -name = "gdk" -version = "0.18.2" +name = "gix-index" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +checksum = "9ea6d3e9e11647ba49f441dea0782494cc6d2875ff43fa4ad9094e6957f42051" dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", + "bitflags 2.11.1", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features 0.45.2", + "gix-fs 0.18.2", + "gix-hash 0.21.2", + "gix-lock 20.0.1", + "gix-object 0.54.1", + "gix-traverse 0.51.1", + "gix-utils", + "gix-validate", + "hashbrown 0.16.1", + "itoa", "libc", - "pango", + "memmap2", + "rustix 1.1.4", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "gdk-pixbuf" -version = "0.18.5" +name = "gix-lock" +version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", + "gix-tempfile 17.1.0", + "gix-utils", + "thiserror 2.0.18", ] [[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" +name = "gix-lock" +version = "20.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "115268ae5e3b3b7bc7fc77260eecee05acca458e45318ca45d35467fa81a3ac5" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", + "gix-tempfile 20.0.1", + "gix-utils", + "thiserror 2.0.18", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "gix-negotiate" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.2.2", + "bitflags 2.11.1", + "gix-commitgraph 0.28.0", + "gix-date 0.10.7", + "gix-hash 0.18.0", + "gix-object 0.49.1", + "gix-revwalk 0.20.1", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "gix-object" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps 6.2.2", + "bstr", + "gix-actor 0.35.6", + "gix-date 0.10.7", + "gix-features 0.42.1", + "gix-hash 0.18.0", + "gix-hashtable 0.8.1", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec 1.15.1", + "thiserror 2.0.18", + "winnow 0.7.15", ] [[package]] -name = "gdkx11" -version = "0.18.2" +name = "gix-object" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +checksum = "363d6a879c52e4890180e0ffa7d8c9a364fd0b7e807caa368e860b80e8d0bc81" dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", + "bstr", + "gix-actor 0.37.1", + "gix-date 0.12.1", + "gix-features 0.45.2", + "gix-hash 0.21.2", + "gix-hashtable 0.11.0", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec 1.15.1", + "thiserror 2.0.18", + "winnow 0.7.15", ] [[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "gix-odb" +version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9" dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps 6.2.2", - "x11", + "arc-swap", + "gix-date 0.10.7", + "gix-features 0.42.1", + "gix-fs 0.15.0", + "gix-hash 0.18.0", + "gix-hashtable 0.8.1", + "gix-object 0.49.1", + "gix-pack 0.59.1", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror 2.0.18", ] [[package]] -name = "generic-array" -version = "0.14.9" +name = "gix-odb" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "165a907df369a12ed4330faf8baf7ae597aadb08cfacb4ed8649f93d90bcc0c5" dependencies = [ - "typenum", - "version_check", - "zeroize", + "arc-swap", + "gix-date 0.12.1", + "gix-features 0.45.2", + "gix-fs 0.18.2", + "gix-hash 0.21.2", + "gix-hashtable 0.11.0", + "gix-object 0.54.1", + "gix-pack 0.64.1", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror 2.0.18", ] [[package]] -name = "gethostname" -version = "1.1.0" +name = "gix-pack" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450" dependencies = [ - "rustix 1.1.4", - "windows-link 0.2.1", + "clru", + "gix-chunk", + "gix-features 0.42.1", + "gix-hash 0.18.0", + "gix-hashtable 0.8.1", + "gix-object 0.49.1", + "gix-path", + "gix-tempfile 17.1.0", + "memmap2", + "parking_lot", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "gix-pack" +version = "0.64.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "b04a73d5ab07ea0faae55e2c0ae6f24e36e365ac8ce140394dee3a2c89cd4366" dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "clru", + "gix-chunk", + "gix-features 0.45.2", + "gix-hash 0.21.2", + "gix-hashtable 0.11.0", + "gix-object 0.54.1", + "gix-path", + "memmap2", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "gix-packetline" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "64286a8b5148e76ab80932e72762dd27ccf6169dd7a134b027c8a262a8262fcf" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.18", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "gix-packetline" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "fad0ffb982a289888087a165d3e849cbac724f2aa5431236b050dd2cb9c7de31" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.18", ] [[package]] -name = "getrandom" -version = "0.4.2" +name = "gix-packetline-blocking" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasip2", - "wasip3", -] - -[[package]] -name = "gguf" -version = "0.1.0" +checksum = "89c59c3ad41e68cb38547d849e9ef5ccfc0d00f282244ba1441ae856be54d001" dependencies = [ - "byteorder", - "dirs 6.0.0", - "memmap2", - "strum 0.28.0", + "bstr", + "faster-hex", + "gix-trace", "thiserror 2.0.18", ] [[package]] -name = "gif" -version = "0.13.3" +name = "gix-path" +version = "0.10.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" dependencies = [ - "color_quant", - "weezl", + "bstr", + "gix-trace", + "gix-validate", + "thiserror 2.0.18", ] [[package]] -name = "gif" -version = "0.14.2" +name = "gix-pathspec" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" dependencies = [ - "color_quant", - "weezl", + "bitflags 2.11.1", + "bstr", + "gix-attributes 0.26.1", + "gix-config-value 0.15.3", + "gix-glob 0.20.1", + "gix-path", + "thiserror 2.0.18", ] [[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gio" -version = "0.18.4" +name = "gix-pathspec" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "ed9e0c881933c37a7ef45288d6c5779c4a7b3ad240b4c37657e1d9829eb90085" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec 1.15.1", - "thiserror 1.0.69", + "bitflags 2.11.1", + "bstr", + "gix-attributes 0.29.0", + "gix-config-value 0.16.0", + "gix-glob 0.23.0", + "gix-path", + "thiserror 2.0.18", ] [[package]] -name = "gio-sys" -version = "0.18.1" +name = "gix-prompt" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", - "winapi", -] - -[[package]] -name = "github-issues" -version = "0.1.0" +checksum = "868e6516dfa16fdcbc5f8c935167d085f2ae65ccd4c9476a4319579d12a69d8d" dependencies = [ - "hypr-http-utils", - "serde", - "serde_json", - "specta", + "gix-command", + "gix-config-value 0.15.3", + "parking_lot", + "rustix 1.1.4", "thiserror 2.0.18", - "tokio", - "urlencoding", - "utoipa", ] [[package]] -name = "gix" -version = "0.72.1" +name = "gix-protocol" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8" +checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401" dependencies = [ - "gix-actor 0.35.6", - "gix-attributes 0.26.1", - "gix-command", - "gix-commitgraph 0.28.0", - "gix-config 0.45.1", + "bstr", "gix-credentials", "gix-date 0.10.7", - "gix-diff 0.52.1", - "gix-dir 0.14.1", - "gix-discover 0.40.1", "gix-features 0.42.1", - "gix-filter 0.19.2", - "gix-fs 0.15.0", - "gix-glob 0.20.1", "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-ignore 0.15.0", - "gix-index 0.40.1", "gix-lock 17.1.0", "gix-negotiate", "gix-object 0.49.1", - "gix-odb 0.69.1", - "gix-pack 0.59.1", - "gix-path", - "gix-pathspec 0.11.0", - "gix-prompt", - "gix-protocol 0.50.1", "gix-ref 0.52.1", "gix-refspec 0.30.1", - "gix-revision 0.34.1", "gix-revwalk 0.20.1", - "gix-sec 0.11.0", "gix-shallow 0.4.0", - "gix-status 0.19.1", - "gix-submodule 0.19.1", - "gix-tempfile 17.1.0", "gix-trace", "gix-transport 0.47.0", - "gix-traverse 0.46.2", - "gix-url 0.31.0", "gix-utils", - "gix-validate", - "gix-worktree 0.41.0", - "gix-worktree-state", - "once_cell", - "smallvec 1.15.1", + "maybe-async", "thiserror 2.0.18", + "winnow 0.7.15", ] [[package]] -name = "gix" -version = "0.77.0" +name = "gix-protocol" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8284d86a2f5c0987fbf7219a128815cc04af5a18f5fd7eec6a76d83c2b78cc" +checksum = "02c5dfd068789442c5709e702ef42d851765f2c09a11bf0a13749d24363f4d07" dependencies = [ - "gix-actor 0.37.1", - "gix-attributes 0.29.0", - "gix-command", - "gix-commitgraph 0.31.0", - "gix-config 0.50.0", + "bstr", "gix-date 0.12.1", - "gix-diff 0.57.1", - "gix-dir 0.19.0", - "gix-discover 0.45.0", "gix-features 0.45.2", - "gix-filter 0.24.1", - "gix-fs 0.18.2", - "gix-glob 0.23.0", "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-ignore 0.18.0", - "gix-index 0.45.1", - "gix-lock 20.0.1", - "gix-object 0.54.1", - "gix-odb 0.74.0", - "gix-pack 0.64.1", - "gix-path", - "gix-pathspec 0.14.0", - "gix-protocol 0.55.0", "gix-ref 0.57.0", - "gix-refspec 0.35.0", - "gix-revision 0.39.0", - "gix-revwalk 0.25.0", - "gix-sec 0.12.2", "gix-shallow 0.7.0", - "gix-status 0.24.0", - "gix-submodule 0.24.0", - "gix-tempfile 20.0.1", - "gix-trace", - "gix-traverse 0.51.1", - "gix-url 0.34.0", + "gix-transport 0.52.1", "gix-utils", - "gix-validate", - "gix-worktree 0.46.0", - "parking_lot", - "signal-hook 0.3.18", - "smallvec 1.15.1", + "maybe-async", "thiserror 2.0.18", + "winnow 0.7.15", ] [[package]] -name = "gix-actor" -version = "0.35.6" +name = "gix-quote" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987a51a7e66db6ef4dc030418eb2a42af6b913a79edd8670766122d8af3ba59e" +checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" dependencies = [ "bstr", - "gix-date 0.10.7", "gix-utils", - "itoa", "thiserror 2.0.18", - "winnow 0.7.15", ] [[package]] -name = "gix-actor" -version = "0.37.1" +name = "gix-ref" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c345528d405eab51d20f505f5fe1a4680973953694e0292c6bbe97827daa55c4" +checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" dependencies = [ - "bstr", - "gix-date 0.12.1", + "gix-actor 0.35.6", + "gix-features 0.42.1", + "gix-fs 0.15.0", + "gix-hash 0.18.0", + "gix-lock 17.1.0", + "gix-object 0.49.1", + "gix-path", + "gix-tempfile 17.1.0", "gix-utils", - "itoa", + "gix-validate", + "memmap2", "thiserror 2.0.18", "winnow 0.7.15", ] [[package]] -name = "gix-attributes" -version = "0.26.1" +name = "gix-ref" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50d813d5c2ce9463ba0c29eea90060df08e38ad8f34b8a192259f8bce5c078" +checksum = "ccb33aa97006e37e9e83fde233569a66b02ed16fd4b0406cdf35834b06cf8a63" dependencies = [ - "bstr", - "gix-glob 0.20.1", + "gix-actor 0.37.1", + "gix-features 0.45.2", + "gix-fs 0.18.2", + "gix-hash 0.21.2", + "gix-lock 20.0.1", + "gix-object 0.54.1", "gix-path", - "gix-quote", - "gix-trace", - "kstring", - "smallvec 1.15.1", + "gix-tempfile 20.0.1", + "gix-utils", + "gix-validate", + "memmap2", "thiserror 2.0.18", - "unicode-bom", + "winnow 0.7.15", ] [[package]] -name = "gix-attributes" -version = "0.29.0" +name = "gix-refspec" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47dabf8a50f1558c3a55d978440c7c4f22f87ac897bef03b4edbc96f6115966" +checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42" dependencies = [ "bstr", - "gix-glob 0.23.0", - "gix-path", - "gix-quote", - "gix-trace", - "kstring", + "gix-hash 0.18.0", + "gix-revision 0.34.1", + "gix-validate", "smallvec 1.15.1", "thiserror 2.0.18", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" -dependencies = [ - "thiserror 2.0.18", -] - -[[package]] -name = "gix-chunk" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" -dependencies = [ - "thiserror 2.0.18", ] [[package]] -name = "gix-command" -version = "0.6.5" +name = "gix-refspec" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f9c425730a654835351e6da8c3c69ba1804f8b8d4e96d027254151138d5c64" +checksum = "dcbba6ae5389f4021f73a2d62a4195aace7db1e8bb684b25521d3d685f57da02" dependencies = [ "bstr", - "gix-path", - "gix-quote", - "gix-trace", - "shell-words", + "gix-glob 0.23.0", + "gix-hash 0.21.2", + "gix-revision 0.39.0", + "gix-validate", + "smallvec 1.15.1", + "thiserror 2.0.18", ] [[package]] -name = "gix-commitgraph" -version = "0.28.0" +name = "gix-revision" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951" +checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" dependencies = [ + "bitflags 2.11.1", "bstr", - "gix-chunk", + "gix-commitgraph 0.28.0", + "gix-date 0.10.7", "gix-hash 0.18.0", - "memmap2", + "gix-hashtable 0.8.1", + "gix-object 0.49.1", + "gix-revwalk 0.20.1", + "gix-trace", "thiserror 2.0.18", ] [[package]] -name = "gix-commitgraph" -version = "0.31.0" +name = "gix-revision" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdcba8048045baf15225daf949d597c3e6183d130245e22a7fbd27084abe63a" +checksum = "91898c83b18c635696f7355d171cfa74a52f38022ff89581f567768935ebc4c8" dependencies = [ + "bitflags 2.11.1", "bstr", - "gix-chunk", + "gix-commitgraph 0.31.0", + "gix-date 0.12.1", "gix-hash 0.21.2", - "memmap2", + "gix-hashtable 0.11.0", + "gix-object 0.54.1", + "gix-revwalk 0.25.0", + "gix-trace", "thiserror 2.0.18", ] [[package]] -name = "gix-config" -version = "0.45.1" +name = "gix-revwalk" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" +checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28" dependencies = [ - "bstr", - "gix-config-value 0.15.3", - "gix-features 0.42.1", - "gix-glob 0.20.1", - "gix-path", - "gix-ref 0.52.1", - "gix-sec 0.11.0", - "memchr", - "once_cell", + "gix-commitgraph 0.28.0", + "gix-date 0.10.7", + "gix-hash 0.18.0", + "gix-hashtable 0.8.1", + "gix-object 0.49.1", "smallvec 1.15.1", "thiserror 2.0.18", - "unicode-bom", - "winnow 0.7.15", ] [[package]] -name = "gix-config" -version = "0.50.0" +name = "gix-revwalk" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58e2ff8eef96b71f2c5e260f02ca0475caff374027c5cc5a29bda69fac67404" +checksum = "0d063699278485016863d0d2bb0db7609fd2e8ba9a89379717bf06fd96949eb2" dependencies = [ - "bstr", - "gix-config-value 0.16.0", - "gix-features 0.45.2", - "gix-glob 0.23.0", - "gix-path", - "gix-ref 0.57.0", - "gix-sec 0.12.2", - "memchr", + "gix-commitgraph 0.31.0", + "gix-date 0.12.1", + "gix-hash 0.21.2", + "gix-hashtable 0.11.0", + "gix-object 0.54.1", "smallvec 1.15.1", "thiserror 2.0.18", - "unicode-bom", - "winnow 0.7.15", ] [[package]] -name = "gix-config-value" -version = "0.15.3" +name = "gix-sec" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c489abb061c74b0c3ad790e24a606ef968cebab48ec673d6a891ece7d5aef64" +checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" dependencies = [ "bitflags 2.11.1", - "bstr", "gix-path", "libc", - "thiserror 2.0.18", + "windows-sys 0.59.0", ] [[package]] -name = "gix-config-value" -version = "0.16.0" +name = "gix-sec" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2409cffa4fe8b303847d5b6ba8df9da9ba65d302fc5ee474ea0cac5afde79840" +checksum = "ea9962ed6d9114f7f100efe038752f41283c225bb507a2888903ac593dffa6be" dependencies = [ "bitflags 2.11.1", - "bstr", "gix-path", "libc", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-credentials" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1c7307e36026b6088e5b12014ffe6d4f509c911ee453e22a7be4003a159c9b" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value 0.15.3", - "gix-path", - "gix-prompt", - "gix-sec 0.11.0", - "gix-trace", - "gix-url 0.31.0", - "thiserror 2.0.18", + "windows-sys 0.61.2", ] [[package]] -name = "gix-date" -version = "0.10.7" +name = "gix-shallow" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661245d045aa7c16ba4244daaabd823c562c3e45f1f25b816be2c57ee09f2171" +checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" dependencies = [ "bstr", - "itoa", - "jiff", - "smallvec 1.15.1", + "gix-hash 0.18.0", + "gix-lock 17.1.0", "thiserror 2.0.18", ] [[package]] -name = "gix-date" -version = "0.12.1" +name = "gix-shallow" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4a31bab8159e233094fa70d2e5fd3ec6f19e593f67e6ae01281daa48f8d8e7" +checksum = "9c1c467fb9f7ec1d33613c2ea5482de514bcb84b8222a793cdc4c71955832356" dependencies = [ "bstr", - "itoa", - "jiff", - "smallvec 1.15.1", + "gix-hash 0.21.2", + "gix-lock 20.0.1", "thiserror 2.0.18", ] [[package]] -name = "gix-diff" -version = "0.52.1" +name = "gix-status" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252" +checksum = "072099c2415cfa5397df7d47eacbcb6016d2cd17e0d674c74965e6ad1b17289f" dependencies = [ "bstr", - "gix-attributes 0.26.1", - "gix-command", + "filetime", + "gix-diff 0.52.1", + "gix-dir 0.14.1", + "gix-features 0.42.1", "gix-filter 0.19.2", "gix-fs 0.15.0", "gix-hash 0.18.0", @@ -7423,23 +6920,22 @@ dependencies = [ "gix-object 0.49.1", "gix-path", "gix-pathspec 0.11.0", - "gix-tempfile 17.1.0", - "gix-trace", - "gix-traverse 0.46.2", "gix-worktree 0.41.0", - "imara-diff", + "portable-atomic", "thiserror 2.0.18", ] [[package]] -name = "gix-diff" -version = "0.57.1" +name = "gix-status" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3506936e63ce14cd54b5f28ed06c8e43b92ef9f41c2238cc0bc271a9259b4e90" +checksum = "ed0d94c685a831c679ca5454c22f350e8c233f50dcf377ca00d858bcba9696d2" dependencies = [ "bstr", - "gix-attributes 0.29.0", - "gix-command", + "filetime", + "gix-diff 0.57.1", + "gix-dir 0.19.0", + "gix-features 0.45.2", "gix-filter 0.24.1", "gix-fs 0.18.2", "gix-hash 0.21.2", @@ -7447,7816 +6943,7382 @@ dependencies = [ "gix-object 0.54.1", "gix-path", "gix-pathspec 0.14.0", - "gix-tempfile 20.0.1", - "gix-trace", - "gix-traverse 0.51.1", "gix-worktree 0.46.0", - "imara-diff", + "portable-atomic", "thiserror 2.0.18", ] [[package]] -name = "gix-dir" -version = "0.14.1" +name = "gix-submodule" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e6e2dc5b8917142d0ffe272209d1671e45b771e433f90186bc71c016792e87" +checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" dependencies = [ "bstr", - "gix-discover 0.40.1", - "gix-fs 0.15.0", - "gix-ignore 0.15.0", - "gix-index 0.40.1", - "gix-object 0.49.1", + "gix-config 0.45.1", "gix-path", "gix-pathspec 0.11.0", - "gix-trace", - "gix-utils", - "gix-worktree 0.41.0", + "gix-refspec 0.30.1", + "gix-url 0.31.0", "thiserror 2.0.18", ] [[package]] -name = "gix-dir" -version = "0.19.0" +name = "gix-submodule" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709d9fad32d2eb8b0129850874246569e801b6d5877e0c41356c23e9e2501e06" +checksum = "efee2a61198413d80de10028aa507344537827d776ade781760130721bec2419" dependencies = [ "bstr", - "gix-discover 0.45.0", - "gix-fs 0.18.2", - "gix-ignore 0.18.0", - "gix-index 0.45.1", - "gix-object 0.54.1", + "gix-config 0.50.0", "gix-path", "gix-pathspec 0.14.0", - "gix-trace", - "gix-utils", - "gix-worktree 0.46.0", + "gix-refspec 0.35.0", + "gix-url 0.34.0", "thiserror 2.0.18", ] [[package]] -name = "gix-discover" -version = "0.40.1" +name = "gix-tempfile" +version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c" +checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" dependencies = [ - "bstr", - "dunce", + "dashmap", "gix-fs 0.15.0", - "gix-hash 0.18.0", - "gix-path", - "gix-ref 0.52.1", - "gix-sec 0.11.0", - "thiserror 2.0.18", + "libc", + "once_cell", + "parking_lot", + "tempfile", ] [[package]] -name = "gix-discover" -version = "0.45.0" +name = "gix-tempfile" +version = "20.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ce096dc132533802a09d6fd5d4008858f2038341dfe2e69e0d0239edb359de" +checksum = "ad89218e74850f42d364ed3877c7291f0474c8533502df91bb877ecc5cb0dd40" dependencies = [ - "bstr", - "dunce", + "dashmap", "gix-fs 0.18.2", - "gix-hash 0.21.2", - "gix-path", - "gix-ref 0.57.0", - "gix-sec 0.12.2", - "thiserror 2.0.18", + "libc", + "parking_lot", + "signal-hook 0.4.4", + "signal-hook-registry", + "tempfile", ] [[package]] -name = "gix-features" -version = "0.42.1" +name = "gix-trace" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" +checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" + +[[package]] +name = "gix-transport" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" dependencies = [ - "bytes", - "crc32fast", - "flate2", - "gix-path", - "gix-trace", - "gix-utils", - "libc", - "once_cell", - "prodash 29.0.2", + "base64 0.22.1", + "bstr", + "gix-command", + "gix-credentials", + "gix-features 0.42.1", + "gix-packetline 0.19.3", + "gix-quote", + "gix-sec 0.11.0", + "gix-url 0.31.0", + "reqwest 0.12.28", "thiserror 2.0.18", - "walkdir", ] [[package]] -name = "gix-features" -version = "0.45.2" +name = "gix-transport" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56aad357ae016449434705033df644ac6253dfcf1281aad3af3af9e907560d1" +checksum = "a4d4ed02a2ebe771a26111896ecda0b98b58ed35e1d9c0ccf07251c1abb4918d" dependencies = [ - "crc32fast", - "gix-path", - "gix-trace", - "gix-utils", - "libc", - "once_cell", - "prodash 30.0.1", + "bstr", + "gix-command", + "gix-features 0.45.2", + "gix-packetline 0.20.0", + "gix-quote", + "gix-sec 0.12.2", + "gix-url 0.34.0", "thiserror 2.0.18", - "walkdir", - "zlib-rs 0.5.5", ] [[package]] -name = "gix-filter" -version = "0.19.2" +name = "gix-traverse" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf004912949bbcf308d71aac4458321748ecb59f4d046830d25214208c471f1" +checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes 0.26.1", - "gix-command", + "bitflags 2.11.1", + "gix-commitgraph 0.28.0", + "gix-date 0.10.7", "gix-hash 0.18.0", + "gix-hashtable 0.8.1", "gix-object 0.49.1", - "gix-packetline-blocking", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", + "gix-revwalk 0.20.1", "smallvec 1.15.1", "thiserror 2.0.18", ] [[package]] -name = "gix-filter" -version = "0.24.1" +name = "gix-traverse" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c02464962482570c1f94ad451a608c4391514f803e8074662d02c5629a25dc" +checksum = "d052b83d1d1744be95ac6448ac02f95f370a8f6720e466be9ce57146e39f5280" dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes 0.29.0", - "gix-command", + "bitflags 2.11.1", + "gix-commitgraph 0.31.0", + "gix-date 0.12.1", "gix-hash 0.21.2", + "gix-hashtable 0.11.0", "gix-object 0.54.1", - "gix-packetline 0.20.0", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", + "gix-revwalk 0.25.0", "smallvec 1.15.1", "thiserror 2.0.18", ] [[package]] -name = "gix-fs" -version = "0.15.0" +name = "gix-url" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" +checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" dependencies = [ "bstr", - "fastrand", "gix-features 0.42.1", "gix-path", - "gix-utils", + "percent-encoding", "thiserror 2.0.18", + "url", ] [[package]] -name = "gix-fs" -version = "0.18.2" +name = "gix-url" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "785b9c499e46bc78d7b81c148c21b3fca18655379ee729a856ed19ce50d359ec" +checksum = "cff1996dfb9430b3699d89224c674169c1ae355eacc52bf30a03c0b8bffe73d9" dependencies = [ "bstr", - "fastrand", "gix-features 0.45.2", "gix-path", - "gix-utils", + "percent-encoding", "thiserror 2.0.18", ] [[package]] -name = "gix-glob" -version = "0.20.1" +name = "gix-utils" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" +checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" dependencies = [ - "bitflags 2.11.1", "bstr", - "gix-features 0.42.1", - "gix-path", + "fastrand", + "unicode-normalization", ] [[package]] -name = "gix-glob" -version = "0.23.0" +name = "gix-validate" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8546300aee4c65c5862c22a3e321124a69b654a61a8b60de546a9284812b7e2" +checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" dependencies = [ - "bitflags 2.11.1", "bstr", - "gix-features 0.45.2", - "gix-path", + "thiserror 2.0.18", ] [[package]] -name = "gix-hash" -version = "0.18.0" +name = "gix-worktree" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" +checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" dependencies = [ - "faster-hex", + "bstr", + "gix-attributes 0.26.1", "gix-features 0.42.1", - "sha1-checked", - "thiserror 2.0.18", + "gix-fs 0.15.0", + "gix-glob 0.20.1", + "gix-hash 0.18.0", + "gix-ignore 0.15.0", + "gix-index 0.40.1", + "gix-object 0.49.1", + "gix-path", + "gix-validate", ] [[package]] -name = "gix-hash" -version = "0.21.2" +name = "gix-worktree" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e153930f42ccdab8a3306b1027cd524879f6a8996cd0c474d18b0e56cae7714d" +checksum = "1cfb7ce8cdbfe06117d335d1ad329351468d20331e0aafd108ceb647c1326aca" dependencies = [ - "faster-hex", + "bstr", + "gix-attributes 0.29.0", "gix-features 0.45.2", - "sha1-checked", - "thiserror 2.0.18", + "gix-fs 0.18.2", + "gix-glob 0.23.0", + "gix-hash 0.21.2", + "gix-ignore 0.18.0", + "gix-index 0.45.1", + "gix-object 0.54.1", + "gix-path", + "gix-validate", ] [[package]] -name = "gix-hashtable" -version = "0.8.1" +name = "gix-worktree-state" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +checksum = "f81e31496d034dbdac87535b0b9d4659dbbeabaae1045a0dce7c69b5d16ea7d6" dependencies = [ + "bstr", + "gix-features 0.42.1", + "gix-filter 0.19.2", + "gix-fs 0.15.0", + "gix-glob 0.20.1", "gix-hash 0.18.0", - "hashbrown 0.14.5", - "parking_lot", + "gix-index 0.40.1", + "gix-object 0.49.1", + "gix-path", + "gix-worktree 0.41.0", + "io-close", + "thiserror 2.0.18", ] [[package]] -name = "gix-hashtable" -version = "0.11.0" +name = "glib" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222f7428636020bef272a87ed833ea48bf5fb3193f99852ae16fbb5a602bd2f0" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "gix-hash 0.21.2", - "hashbrown 0.16.1", - "parking_lot", + "bitflags 2.11.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec 1.15.1", + "thiserror 1.0.69", ] [[package]] -name = "gix-ignore" -version = "0.15.0" +name = "glib-macros" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ - "bstr", - "gix-glob 0.20.1", - "gix-path", - "gix-trace", - "unicode-bom", + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "gix-ignore" -version = "0.18.0" +name = "glib-sys" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa727fdf54fd9fb53fa3fbb1a5c17172d3073e8e336bf155f3cac3e25b81b21" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" dependencies = [ - "bstr", - "gix-glob 0.23.0", - "gix-path", - "gix-trace", - "unicode-bom", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "gix-index" -version = "0.40.1" +name = "glidesort" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" -dependencies = [ - "bitflags 2.11.1", - "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-features 0.42.1", - "gix-fs 0.15.0", - "gix-hash 0.18.0", - "gix-lock 17.1.0", - "gix-object 0.49.1", - "gix-traverse 0.46.2", - "gix-utils", - "gix-validate", - "hashbrown 0.14.5", - "itoa", - "libc", - "memmap2", - "rustix 1.1.4", - "smallvec 1.15.1", - "thiserror 2.0.18", -] +checksum = "f2e102e6eb644d3e0b186fc161e4460417880a0a0b87d235f2e5b8fb30f2e9e0" [[package]] -name = "gix-index" -version = "0.45.1" +name = "glob" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea6d3e9e11647ba49f441dea0782494cc6d2875ff43fa4ad9094e6957f42051" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ - "bitflags 2.11.1", + "aho-corasick", "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-features 0.45.2", - "gix-fs 0.18.2", - "gix-hash 0.21.2", - "gix-lock 20.0.1", - "gix-object 0.54.1", - "gix-traverse 0.51.1", - "gix-utils", - "gix-validate", - "hashbrown 0.16.1", - "itoa", - "libc", - "memmap2", - "rustix 1.1.4", - "smallvec 1.15.1", - "thiserror 2.0.18", + "log", + "regex-automata", + "regex-syntax", ] [[package]] -name = "gix-lock" -version = "17.1.0" +name = "globwalk" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "gix-tempfile 17.1.0", - "gix-utils", - "thiserror 2.0.18", + "bitflags 2.11.1", + "ignore", + "walkdir", ] [[package]] -name = "gix-lock" -version = "20.0.1" +name = "gloo-timers" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115268ae5e3b3b7bc7fc77260eecee05acca458e45318ca45d35467fa81a3ac5" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ - "gix-tempfile 20.0.1", - "gix-utils", - "thiserror 2.0.18", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "gix-negotiate" -version = "0.20.1" +name = "gobject-sys" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ - "bitflags 2.11.1", - "gix-commitgraph 0.28.0", - "gix-date 0.10.7", - "gix-hash 0.18.0", - "gix-object 0.49.1", - "gix-revwalk 0.20.1", - "smallvec 1.15.1", - "thiserror 2.0.18", + "glib-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "gix-object" -version = "0.49.1" +name = "goblin" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" dependencies = [ - "bstr", - "gix-actor 0.35.6", - "gix-date 0.10.7", - "gix-features 0.42.1", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-path", - "gix-utils", - "gix-validate", - "itoa", - "smallvec 1.15.1", - "thiserror 2.0.18", - "winnow 0.7.15", + "log", + "plain", + "scroll", ] [[package]] -name = "gix-object" -version = "0.54.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363d6a879c52e4890180e0ffa7d8c9a364fd0b7e807caa368e860b80e8d0bc81" +name = "google-calendar" +version = "0.1.0" dependencies = [ - "bstr", - "gix-actor 0.37.1", - "gix-date 0.12.1", - "gix-features 0.45.2", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-path", - "gix-utils", - "gix-validate", - "itoa", - "smallvec 1.15.1", + "chrono", + "hypr-http-utils", + "serde", + "serde_json", + "specta", "thiserror 2.0.18", - "winnow 0.7.15", + "tokio", + "urlencoding", + "utoipa", ] [[package]] -name = "gix-odb" -version = "0.69.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9" +name = "granola" +version = "0.1.0" dependencies = [ - "arc-swap", - "gix-date 0.10.7", - "gix-features 0.42.1", - "gix-fs 0.15.0", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-object 0.49.1", - "gix-pack 0.59.1", - "gix-path", - "gix-quote", - "parking_lot", + "chrono", + "dirs 6.0.0", + "importer-core", + "regex", + "reqwest 0.13.2", + "serde", + "serde_json", + "serde_yaml", "tempfile", "thiserror 2.0.18", + "tokio", + "uuid", ] [[package]] -name = "gix-odb" -version = "0.74.0" +name = "group" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165a907df369a12ed4330faf8baf7ae597aadb08cfacb4ed8649f93d90bcc0c5" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "arc-swap", - "gix-date 0.12.1", - "gix-features 0.45.2", - "gix-fs 0.18.2", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-object 0.54.1", - "gix-pack 0.64.1", - "gix-path", - "gix-quote", - "parking_lot", - "tempfile", - "thiserror 2.0.18", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "gix-pack" -version = "0.59.1" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "clru", - "gix-chunk", - "gix-features 0.42.1", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-object 0.49.1", - "gix-path", - "gix-tempfile 17.1.0", - "memmap2", - "parking_lot", - "smallvec 1.15.1", - "thiserror 2.0.18", + "ff 0.13.1", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "gix-pack" -version = "0.64.1" +name = "gtk" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04a73d5ab07ea0faae55e2c0ae6f24e36e365ac8ce140394dee3a2c89cd4366" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" dependencies = [ - "clru", - "gix-chunk", - "gix-features 0.45.2", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-object 0.54.1", - "gix-path", - "memmap2", - "smallvec 1.15.1", - "thiserror 2.0.18", + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", ] [[package]] -name = "gix-packetline" -version = "0.19.3" +name = "gtk-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64286a8b5148e76ab80932e72762dd27ccf6169dd7a134b027c8a262a8262fcf" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.18", + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.2.2", ] [[package]] -name = "gix-packetline" -version = "0.20.0" +name = "gtk3-macros" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad0ffb982a289888087a165d3e849cbac724f2aa5431236b050dd2cb9c7de31" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.18", + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "gix-packetline-blocking" -version = "0.19.3" +name = "h2" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c59c3ad41e68cb38547d849e9ef5ccfc0d00f282244ba1441ae856be54d001" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.18", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "gix-path" -version = "0.10.22" +name = "h2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ - "bstr", - "gix-trace", - "gix-validate", - "thiserror 2.0.18", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "gix-pathspec" -version = "0.11.0" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "bitflags 2.11.1", - "bstr", - "gix-attributes 0.26.1", - "gix-config-value 0.15.3", - "gix-glob 0.20.1", - "gix-path", - "thiserror 2.0.18", + "cfg-if", + "crunchy", + "zerocopy 0.8.48", ] [[package]] -name = "gix-pathspec" -version = "0.14.0" +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9e0c881933c37a7ef45288d6c5779c4a7b3ad240b4c37657e1d9829eb90085" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ - "bitflags 2.11.1", - "bstr", - "gix-attributes 0.29.0", - "gix-config-value 0.16.0", - "gix-glob 0.23.0", - "gix-path", - "thiserror 2.0.18", + "byteorder", ] [[package]] -name = "gix-prompt" -version = "0.11.2" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e6516dfa16fdcbc5f8c935167d085f2ae65ccd4c9476a4319579d12a69d8d" -dependencies = [ - "gix-command", - "gix-config-value 0.15.3", - "parking_lot", - "rustix 1.1.4", - "thiserror 2.0.18", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "gix-protocol" -version = "0.50.1" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "bstr", - "gix-credentials", - "gix-date 0.10.7", - "gix-features 0.42.1", - "gix-hash 0.18.0", - "gix-lock 17.1.0", - "gix-negotiate", - "gix-object 0.49.1", - "gix-ref 0.52.1", - "gix-refspec 0.30.1", - "gix-revwalk 0.20.1", - "gix-shallow 0.4.0", - "gix-trace", - "gix-transport 0.47.0", - "gix-utils", - "maybe-async", - "thiserror 2.0.18", - "winnow 0.7.15", + "ahash", + "allocator-api2", ] [[package]] -name = "gix-protocol" -version = "0.55.0" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c5dfd068789442c5709e702ef42d851765f2c09a11bf0a13749d24363f4d07" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "bstr", - "gix-date 0.12.1", - "gix-features 0.45.2", - "gix-hash 0.21.2", - "gix-ref 0.57.0", - "gix-shallow 0.7.0", - "gix-transport 0.52.1", - "gix-utils", - "maybe-async", - "thiserror 2.0.18", - "winnow 0.7.15", + "allocator-api2", + "equivalent", + "foldhash 0.1.5", ] [[package]] -name = "gix-quote" -version = "0.6.2" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "bstr", - "gix-utils", - "thiserror 2.0.18", + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] -name = "gix-ref" -version = "0.52.1" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" -dependencies = [ - "gix-actor 0.35.6", - "gix-features 0.42.1", - "gix-fs 0.15.0", - "gix-hash 0.18.0", - "gix-lock 17.1.0", - "gix-object 0.49.1", - "gix-path", - "gix-tempfile 17.1.0", - "gix-utils", - "gix-validate", - "memmap2", - "thiserror 2.0.18", - "winnow 0.7.15", -] +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] -name = "gix-ref" -version = "0.57.0" +name = "hashlink" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb33aa97006e37e9e83fde233569a66b02ed16fd4b0406cdf35834b06cf8a63" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "gix-actor 0.37.1", - "gix-features 0.45.2", - "gix-fs 0.18.2", - "gix-hash 0.21.2", - "gix-lock 20.0.1", - "gix-object 0.54.1", - "gix-path", - "gix-tempfile 20.0.1", - "gix-utils", - "gix-validate", - "memmap2", - "thiserror 2.0.18", - "winnow 0.7.15", + "hashbrown 0.14.5", ] [[package]] -name = "gix-refspec" -version = "0.30.1" +name = "hashlink" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "bstr", - "gix-hash 0.18.0", - "gix-revision 0.34.1", - "gix-validate", - "smallvec 1.15.1", - "thiserror 2.0.18", + "hashbrown 0.15.5", ] [[package]] -name = "gix-refspec" -version = "0.35.0" +name = "hayagriva" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbba6ae5389f4021f73a2d62a4195aace7db1e8bb684b25521d3d685f57da02" +checksum = "1cb69425736f184173b3ca6e27fcba440a61492a790c786b1c6af7e06a03e575" dependencies = [ - "bstr", - "gix-glob 0.23.0", - "gix-hash 0.21.2", - "gix-revision 0.39.0", - "gix-validate", - "smallvec 1.15.1", + "biblatex", + "ciborium", + "citationberg", + "indexmap 2.14.0", + "paste", + "roman-numerals-rs", + "serde", + "serde_yaml", "thiserror 2.0.18", + "unic-langid", + "unicode-segmentation", + "unscanny", + "url", ] [[package]] -name = "gix-revision" -version = "0.34.1" +name = "hayro" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" +checksum = "048488ba88552bb0fb2a7e4001c64d5bed65d1a92167186a1bb9151571f32e60" dependencies = [ - "bitflags 2.11.1", - "bstr", - "gix-commitgraph 0.28.0", - "gix-date 0.10.7", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-object 0.49.1", - "gix-revwalk 0.20.1", - "gix-trace", - "thiserror 2.0.18", + "bytemuck", + "hayro-interpret", + "image", + "kurbo 0.12.0", ] [[package]] -name = "gix-revision" -version = "0.39.0" +name = "hayro-font" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91898c83b18c635696f7355d171cfa74a52f38022ff89581f567768935ebc4c8" +checksum = "10e7e97ce840a6a70e7901e240ec65ba61106b66b37a4a1b899a2ce484248463" dependencies = [ - "bitflags 2.11.1", - "bstr", - "gix-commitgraph 0.31.0", - "gix-date 0.12.1", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-object 0.54.1", - "gix-revwalk 0.25.0", - "gix-trace", - "thiserror 2.0.18", + "log", + "phf 0.13.1", ] [[package]] -name = "gix-revwalk" -version = "0.20.1" +name = "hayro-interpret" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28" +checksum = "56204c972d08e844f3db13b1e14be769f846e576699b46d4f4637cc4f8f70102" dependencies = [ - "gix-commitgraph 0.28.0", - "gix-date 0.10.7", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-object 0.49.1", + "bitflags 2.11.1", + "hayro-font", + "hayro-syntax", + "kurbo 0.12.0", + "log", + "moxcms 0.7.11", + "phf 0.13.1", + "rustc-hash 2.1.2", + "siphasher 1.0.2", + "skrifa", "smallvec 1.15.1", - "thiserror 2.0.18", + "yoke 0.8.2", ] [[package]] -name = "gix-revwalk" -version = "0.25.0" +name = "hayro-svg" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d063699278485016863d0d2bb0db7609fd2e8ba9a89379717bf06fd96949eb2" +checksum = "e8c673304cec6e0dfd3b4f71fccecd45646899aa70279b62d3f933842abc4ac5" dependencies = [ - "gix-commitgraph 0.31.0", - "gix-date 0.12.1", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-object 0.54.1", - "smallvec 1.15.1", - "thiserror 2.0.18", + "base64 0.22.1", + "hayro-interpret", + "image", + "kurbo 0.12.0", + "siphasher 1.0.2", + "xmlwriter", ] [[package]] -name = "gix-sec" -version = "0.11.0" +name = "hayro-syntax" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" +checksum = "3f9e5c7dbc0f11dc42775d1a6cc00f5f5137b90b6288dd7fe5f71d17b14d10be" dependencies = [ - "bitflags 2.11.1", - "gix-path", - "libc", - "windows-sys 0.59.0", + "flate2", + "kurbo 0.12.0", + "log", + "rustc-hash 2.1.2", + "smallvec 1.15.1", + "zune-jpeg 0.4.21", ] [[package]] -name = "gix-sec" -version = "0.12.2" +name = "hayro-write" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9962ed6d9114f7f100efe038752f41283c225bb507a2888903ac593dffa6be" +checksum = "cc05d8b4bc878b9aee48d980ecb25ed08f1dd9fad6da5ab4d9b7c56ec03a0cf6" dependencies = [ - "bitflags 2.11.1", - "gix-path", - "libc", - "windows-sys 0.61.2", + "flate2", + "hayro-syntax", + "log", + "pdf-writer", ] [[package]] -name = "gix-shallow" -version = "0.4.0" +name = "heapless" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "bstr", - "gix-hash 0.18.0", - "gix-lock 17.1.0", - "thiserror 2.0.18", + "hash32", + "stable_deref_trait", ] [[package]] -name = "gix-shallow" -version = "0.7.0" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1c467fb9f7ec1d33613c2ea5482de514bcb84b8222a793cdc4c71955832356" -dependencies = [ - "bstr", - "gix-hash 0.21.2", - "gix-lock 20.0.1", - "thiserror 2.0.18", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "gix-status" -version = "0.19.1" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072099c2415cfa5397df7d47eacbcb6016d2cd17e0d674c74965e6ad1b17289f" -dependencies = [ - "bstr", - "filetime", - "gix-diff 0.52.1", - "gix-dir 0.14.1", - "gix-features 0.42.1", - "gix-filter 0.19.2", - "gix-fs 0.15.0", - "gix-hash 0.18.0", - "gix-index 0.40.1", - "gix-object 0.49.1", - "gix-path", - "gix-pathspec 0.11.0", - "gix-worktree 0.41.0", - "portable-atomic", - "thiserror 2.0.18", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "gix-status" -version = "0.24.0" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0d94c685a831c679ca5454c22f350e8c233f50dcf377ca00d858bcba9696d2" -dependencies = [ - "bstr", - "filetime", - "gix-diff 0.57.1", - "gix-dir 0.19.0", - "gix-features 0.45.2", - "gix-filter 0.24.1", - "gix-fs 0.18.2", - "gix-hash 0.21.2", - "gix-index 0.45.1", - "gix-object 0.54.1", - "gix-path", - "gix-pathspec 0.14.0", - "gix-worktree 0.46.0", - "portable-atomic", - "thiserror 2.0.18", -] +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] -name = "gix-submodule" -version = "0.19.1" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "bstr", - "gix-config 0.45.1", - "gix-path", - "gix-pathspec 0.11.0", - "gix-refspec 0.30.1", - "gix-url 0.31.0", - "thiserror 2.0.18", + "hmac", ] [[package]] -name = "gix-submodule" -version = "0.24.0" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efee2a61198413d80de10028aa507344537827d776ade781760130721bec2419" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "bstr", - "gix-config 0.50.0", - "gix-path", - "gix-pathspec 0.14.0", - "gix-refspec 0.35.0", - "gix-url 0.34.0", - "thiserror 2.0.18", + "digest", ] [[package]] -name = "gix-tempfile" -version = "17.1.0" +name = "home" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "dashmap", - "gix-fs 0.15.0", - "libc", - "once_cell", - "parking_lot", - "tempfile", + "windows-sys 0.61.2", ] [[package]] -name = "gix-tempfile" -version = "20.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad89218e74850f42d364ed3877c7291f0474c8533502df91bb877ecc5cb0dd40" +name = "hooks" +version = "0.1.0" dependencies = [ - "dashmap", - "gix-fs 0.18.2", - "libc", - "parking_lot", - "signal-hook 0.4.4", - "signal-hook-registry", - "tempfile", + "futures-util", + "serde", + "serde_json", + "shellexpand", + "specta", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "gix-trace" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" +name = "host" +version = "0.1.0" +dependencies = [ + "mac_address2", + "machine-uid", + "sysinfo", +] [[package]] -name = "gix-transport" -version = "0.47.0" +name = "hostname" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ - "base64 0.22.1", - "bstr", - "gix-command", - "gix-credentials", - "gix-features 0.42.1", - "gix-packetline 0.19.3", - "gix-quote", - "gix-sec 0.11.0", - "gix-url 0.31.0", - "reqwest 0.12.28", - "thiserror 2.0.18", + "cfg-if", + "libc", + "windows-link 0.2.1", ] [[package]] -name = "gix-transport" -version = "0.52.1" +name = "hound" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d4ed02a2ebe771a26111896ecda0b98b58ed35e1d9c0ccf07251c1abb4918d" -dependencies = [ - "bstr", - "gix-command", - "gix-features 0.45.2", - "gix-packetline 0.20.0", - "gix-quote", - "gix-sec 0.12.2", - "gix-url 0.34.0", - "thiserror 2.0.18", -] +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" [[package]] -name = "gix-traverse" -version = "0.46.2" +name = "hstr" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" +checksum = "faa57007c3c9dab34df2fa4c1fb52fe9c34ec5a27ed9d8edea53254b50cd7887" dependencies = [ - "bitflags 2.11.1", - "gix-commitgraph 0.28.0", - "gix-date 0.10.7", - "gix-hash 0.18.0", - "gix-hashtable 0.8.1", - "gix-object 0.49.1", - "gix-revwalk 0.20.1", - "smallvec 1.15.1", - "thiserror 2.0.18", + "hashbrown 0.14.5", + "new_debug_unreachable", + "once_cell", + "rustc-hash 2.1.2", + "serde", + "triomphe", ] [[package]] -name = "gix-traverse" -version = "0.51.1" +name = "htmd" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d052b83d1d1744be95ac6448ac02f95f370a8f6720e466be9ce57146e39f5280" +checksum = "7eee9b00ee2e599b4f86507157e3db786e7a3319fc225f0e9584151dbea2291d" dependencies = [ - "bitflags 2.11.1", - "gix-commitgraph 0.31.0", - "gix-date 0.12.1", - "gix-hash 0.21.2", - "gix-hashtable 0.11.0", - "gix-object 0.54.1", - "gix-revwalk 0.25.0", - "smallvec 1.15.1", - "thiserror 2.0.18", + "html5ever 0.38.0", + "markup5ever_rcdom", + "phf 0.13.1", ] [[package]] -name = "gix-url" -version = "0.31.0" +name = "html5ever" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ - "bstr", - "gix-features 0.42.1", - "gix-path", - "percent-encoding", - "thiserror 2.0.18", - "url", + "log", + "mac", + "markup5ever 0.14.1", + "match_token", ] [[package]] -name = "gix-url" -version = "0.34.0" +name = "html5ever" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff1996dfb9430b3699d89224c674169c1ae355eacc52bf30a03c0b8bffe73d9" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ - "bstr", - "gix-features 0.45.2", - "gix-path", - "percent-encoding", - "thiserror 2.0.18", + "log", + "markup5ever 0.38.0", ] [[package]] -name = "gix-utils" +name = "htmlescape" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" -dependencies = [ - "bstr", - "fastrand", - "unicode-normalization", -] +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" [[package]] -name = "gix-validate" -version = "0.10.1" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bstr", - "thiserror 2.0.18", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "gix-worktree" -version = "0.41.0" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "bstr", - "gix-attributes 0.26.1", - "gix-features 0.42.1", - "gix-fs 0.15.0", - "gix-glob 0.20.1", - "gix-hash 0.18.0", - "gix-ignore 0.15.0", - "gix-index 0.40.1", - "gix-object 0.49.1", - "gix-path", - "gix-validate", + "bytes", + "itoa", ] [[package]] -name = "gix-worktree" -version = "0.46.0" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfb7ce8cdbfe06117d335d1ad329351468d20331e0aafd108ceb647c1326aca" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bstr", - "gix-attributes 0.29.0", - "gix-features 0.45.2", - "gix-fs 0.18.2", - "gix-glob 0.23.0", - "gix-hash 0.21.2", - "gix-ignore 0.18.0", - "gix-index 0.45.1", - "gix-object 0.54.1", - "gix-path", - "gix-validate", + "bytes", + "http 0.2.12", + "pin-project-lite", ] [[package]] -name = "gix-worktree-state" -version = "0.19.0" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81e31496d034dbdac87535b0b9d4659dbbeabaae1045a0dce7c69b5d16ea7d6" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bstr", - "gix-features 0.42.1", - "gix-filter 0.19.2", - "gix-fs 0.15.0", - "gix-glob 0.20.1", - "gix-hash 0.18.0", - "gix-index 0.40.1", - "gix-object 0.49.1", - "gix-path", - "gix-worktree 0.41.0", - "io-close", - "thiserror 2.0.18", + "bytes", + "http 1.4.0", ] [[package]] -name = "gl" -version = "0.14.0" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ - "gl_generator", + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", ] [[package]] -name = "gl_generator" -version = "0.14.0" +name = "http-range" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" [[package]] -name = "glib" -version = "0.18.5" +name = "http-range-header" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec 1.15.1", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps 6.2.2", -] +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] -name = "glidesort" -version = "0.1.2" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e102e6eb644d3e0b186fc161e4460417880a0a0b87d235f2e5b8fb30f2e9e0" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "glob" -version = "0.3.3" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "globset" -version = "0.4.18" +name = "humansize" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", + "libm", ] [[package]] -name = "globwalk" -version = "0.9.1" +name = "hyper" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "bitflags 2.11.1", - "ignore", - "walkdir", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "gloo-timers" -version = "0.3.0" +name = "hyper" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", + "bytes", "futures-channel", "futures-core", - "js-sys", - "wasm-bindgen", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec 1.15.1", + "tokio", + "want", ] [[package]] -name = "gobject-sys" -version = "0.18.0" +name = "hyper-named-pipe" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", + "hex", + "hyper 1.9.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", ] [[package]] -name = "goblin" -version = "0.8.2" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", "log", - "plain", - "scroll", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", ] [[package]] -name = "google-calendar" -version = "0.1.0" +name = "hyper-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" dependencies = [ - "chrono", - "hypr-http-utils", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.22.4", + "rustls-native-certs 0.7.3", + "rustls-pki-types", "tokio", - "urlencoding", - "utoipa", + "tokio-rustls 0.25.0", + "webpki-roots 0.26.11", ] [[package]] -name = "google-drive" -version = "0.1.0" +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "hypr-http-utils", - "serde", - "serde_json", - "thiserror 2.0.18", + "http 1.4.0", + "hyper 1.9.0", + "hyper-util", + "rustls 0.23.38", + "rustls-native-certs 0.8.3", "tokio", - "urlencoding", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.7", ] [[package]] -name = "google-mail" -version = "0.1.0" +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hypr-http-utils", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", + "hyper 0.14.32", + "pin-project-lite", "tokio", - "urlencoding", - "utoipa", + "tokio-io-timeout", ] [[package]] -name = "governor" -version = "0.10.4" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "cfg-if", - "dashmap", - "futures-sink", - "futures-timer", - "futures-util", - "getrandom 0.3.4", - "hashbrown 0.16.1", - "nonzero_ext", - "parking_lot", - "portable-atomic", - "quanta", - "rand 0.9.4", - "smallvec 1.15.1", - "spinning_top", - "web-time", + "bytes", + "http-body-util", + "hyper 1.9.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] -name = "granola" -version = "0.1.0" +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "chrono", - "dirs 6.0.0", - "importer-core", - "regex", - "reqwest 0.13.2", - "serde", - "serde_json", - "serde_yaml", - "tempfile", - "thiserror 2.0.18", + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.9.0", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", "tokio", - "uuid", + "tower-service", + "tracing", + "windows-registry 0.6.1", ] [[package]] -name = "group" -version = "0.12.1" +name = "hyperlocal" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", + "hex", + "http-body-util", + "hyper 1.9.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] -name = "group" -version = "0.13.0" +name = "hyperloglogplus" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" dependencies = [ - "ff 0.13.1", - "rand_core 0.6.4", - "subtle", + "serde", ] [[package]] -name = "gtk" -version = "0.18.2" +name = "hypher" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] +checksum = "bef68590049bab63a464eee1a1158ac04c6f6613a546d8d90f78636b8b94f171" [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "hypr-http-utils" +version = "0.1.0" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps 6.2.2", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", ] [[package]] -name = "gtk3-macros" -version = "0.18.2" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", + "cc", ] [[package]] -name = "h2" -version = "0.3.27" +name = "ico" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", + "byteorder", + "png 0.17.16", ] [[package]] -name = "h2" -version = "0.4.13" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.4.0", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", + "displaydoc", + "serde", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", ] [[package]] -name = "half" -version = "2.7.1" +name = "icu_collections" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ - "cfg-if", - "crunchy", - "zerocopy 0.8.48", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke 0.8.2", + "zerofrom", + "zerovec 0.11.6", ] [[package]] -name = "hash32" -version = "0.3.1" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ - "byteorder", + "displaydoc", + "litemap 0.8.2", + "tinystr 0.8.3", + "writeable 0.6.3", + "zerovec 0.11.6", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", + "zerovec 0.10.4", +] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "ahash", - "allocator-api2", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "icu_locid_transform_data" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", -] +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] -name = "hashbrown" -version = "0.16.1" +name = "icu_normalizer" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", + "icu_collections 2.2.0", + "icu_normalizer_data", + "icu_properties 2.2.0", + "icu_provider 2.2.0", + "smallvec 1.15.1", + "zerovec 0.11.6", ] [[package]] -name = "hashbrown" -version = "0.17.0" +name = "icu_normalizer_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] -name = "hashify" -version = "0.2.9" +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1246c0e5493286aeb2dde35b1f4eb9c4ce00e628641210a5e553fc001a1f26" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "indexmap 2.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "displaydoc", + "icu_collections 1.5.0", + "icu_locid_transform", + "icu_properties_data 1.5.1", + "icu_provider 1.5.0", + "serde", + "tinystr 0.7.6", + "zerovec 0.10.4", ] [[package]] -name = "hashlink" -version = "0.8.4" +name = "icu_properties" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "hashbrown 0.14.5", + "icu_collections 2.2.0", + "icu_locale_core", + "icu_properties_data 2.2.0", + "icu_provider 2.2.0", + "zerotrie 0.2.4", + "zerovec 0.11.6", ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "icu_properties_data" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] -name = "hayagriva" -version = "0.9.1" +name = "icu_properties_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb69425736f184173b3ca6e27fcba440a61492a790c786b1c6af7e06a03e575" -dependencies = [ - "biblatex", - "ciborium", - "citationberg", - "indexmap 2.14.0", - "paste", - "roman-numerals-rs", - "serde", - "serde_yaml", - "thiserror 2.0.18", - "unic-langid", - "unicode-segmentation", - "unscanny", - "url", -] - -[[package]] -name = "hayro" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048488ba88552bb0fb2a7e4001c64d5bed65d1a92167186a1bb9151571f32e60" -dependencies = [ - "bytemuck", - "hayro-interpret", - "image", - "kurbo 0.12.0", -] +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] -name = "hayro-font" -version = "0.3.0" +name = "icu_provider" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10e7e97ce840a6a70e7901e240ec65ba61106b66b37a4a1b899a2ce484248463" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "log", - "phf 0.13.1", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "postcard", + "serde", + "stable_deref_trait", + "tinystr 0.7.6", + "writeable 0.5.5", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", ] [[package]] -name = "hayro-interpret" -version = "0.4.0" +name = "icu_provider" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56204c972d08e844f3db13b1e14be769f846e576699b46d4f4637cc4f8f70102" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ - "bitflags 2.11.1", - "hayro-font", - "hayro-syntax", - "kurbo 0.12.0", - "log", - "moxcms 0.7.11", - "phf 0.13.1", - "rustc-hash 2.1.2", - "siphasher 1.0.2", - "skrifa", - "smallvec 1.15.1", + "displaydoc", + "icu_locale_core", + "writeable 0.6.3", "yoke 0.8.2", + "zerofrom", + "zerotrie 0.2.4", + "zerovec 0.11.6", ] [[package]] -name = "hayro-svg" -version = "0.2.0" +name = "icu_provider_adapters" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c673304cec6e0dfd3b4f71fccecd45646899aa70279b62d3f933842abc4ac5" +checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc" dependencies = [ - "base64 0.22.1", - "hayro-interpret", - "image", - "kurbo 0.12.0", - "siphasher 1.0.2", - "xmlwriter", + "icu_locid", + "icu_locid_transform", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] [[package]] -name = "hayro-syntax" -version = "0.4.0" +name = "icu_provider_blob" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9e5c7dbc0f11dc42775d1a6cc00f5f5137b90b6288dd7fe5f71d17b14d10be" +checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51" dependencies = [ - "flate2", - "kurbo 0.12.0", - "log", - "rustc-hash 2.1.2", - "smallvec 1.15.1", - "zune-jpeg 0.4.21", + "icu_provider 1.5.0", + "postcard", + "serde", + "writeable 0.5.5", + "zerotrie 0.1.3", + "zerovec 0.10.4", ] [[package]] -name = "hayro-write" -version = "0.3.0" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc05d8b4bc878b9aee48d980ecb25ed08f1dd9fad6da5ab4d9b7c56ec03a0cf6" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "flate2", - "hayro-syntax", - "log", - "pdf-writer", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "heapless" -version = "0.8.0" +name = "icu_segmenter" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" dependencies = [ - "hash32", - "stable_deref_trait", + "core_maths", + "displaydoc", + "icu_collections 1.5.0", + "icu_locid", + "icu_provider 1.5.0", + "icu_segmenter_data", + "serde", + "utf8_iter", + "zerovec 0.10.4", ] [[package]] -name = "heck" -version = "0.4.1" +name = "icu_segmenter_data" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb" [[package]] -name = "heck" -version = "0.5.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "hex" -version = "0.4.3" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hf" -version = "0.1.0" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "download-interface", - "hf-hub", - "thiserror 2.0.18", - "tokio", + "idna_adapter", + "smallvec 1.15.1", + "utf8_iter", ] [[package]] -name = "hf-hub" -version = "0.4.3" -source = "git+https://github.com/huggingface/hf-hub?rev=5510260#5510260d5fd1d93238f8c43272a1a14e6848ec4b" +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "dirs 6.0.0", - "futures", - "indicatif", - "libc", - "log", - "num_cpus", - "rand 0.9.4", - "reqwest 0.12.28", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "windows-sys 0.61.2", + "icu_normalizer", + "icu_properties 2.2.0", ] [[package]] -name = "hid-host" -version = "0.1.0" +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ - "hid-interface", - "hidapi", - "thiserror 2.0.18", - "tracing", + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] [[package]] -name = "hid-interface" -version = "0.1.0" +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ - "thiserror 2.0.18", + "bytemuck", + "byteorder-lite", + "color_quant", + "gif 0.14.2", + "image-webp", + "moxcms 0.8.1", + "num-traits", + "png 0.18.1", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.15", ] [[package]] -name = "hidapi" -version = "2.6.5" +name = "image-webp" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b71e1f4791fb9e93b9d7ee03d70b501ab48f6151432fbcadeabc30fe15396e" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ - "cc", - "cfg-if", - "libc", - "pkg-config", - "windows-sys 0.61.2", + "byteorder-lite", + "quick-error", ] [[package]] -name = "hkdf" -version = "0.12.4" +name = "imagesize" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] -name = "hmac" -version = "0.12.1" +name = "imagesize" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] -name = "hmac" -version = "0.13.0" +name = "imara-diff" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" dependencies = [ - "digest 0.11.2", + "hashbrown 0.15.5", ] [[package]] -name = "home" -version = "0.5.12" +name = "impl-more" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] -name = "hooks" +name = "importer-core" version = "0.1.0" dependencies = [ - "futures-util", + "chrono", "serde", "serde_json", - "shellexpand", - "specta", - "thiserror 2.0.18", - "tokio", ] [[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "mac_address2", - "machine-uid", - "sysinfo", -] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] -name = "hostname" -version = "0.4.2" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "cfg-if", - "libc", - "windows-link 0.2.1", + "autocfg", + "hashbrown 0.12.3", + "serde", ] [[package]] -name = "hound" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - -[[package]] -name = "hstr" -version = "3.0.4" +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa57007c3c9dab34df2fa4c1fb52fe9c34ec5a27ed9d8edea53254b50cd7887" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ - "hashbrown 0.14.5", - "new_debug_unreachable", - "once_cell", - "rustc-hash 2.1.2", + "equivalent", + "hashbrown 0.17.0", "serde", - "triomphe", + "serde_core", ] [[package]] -name = "htmd" -version = "0.5.4" +name = "indoc" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eee9b00ee2e599b4f86507157e3db786e7a3319fc225f0e9584151dbea2291d" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ - "html5ever 0.38.0", - "markup5ever_rcdom", - "phf 0.13.1", + "rustversion", ] [[package]] -name = "html5ever" -version = "0.29.1" +name = "infer" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ - "log", - "mac 0.1.1", - "markup5ever 0.14.1", - "match_token", + "cfb", ] [[package]] -name = "html5ever" -version = "0.38.0" +name = "inotify" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "log", - "markup5ever 0.38.0", + "bitflags 2.11.1", + "inotify-sys", + "libc", ] [[package]] -name = "htmlescape" -version = "0.3.1" +name = "inotify-sys" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] [[package]] -name = "http" -version = "0.2.12" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "bytes", - "fnv", - "itoa", + "block-padding", + "generic-array", ] [[package]] -name = "http" -version = "1.4.0" +name = "insta" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ - "bytes", - "itoa", + "console", + "once_cell", + "pest", + "pest_derive", + "serde", + "similar", + "tempfile", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", + "cfg-if", ] [[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +name = "intercept" +version = "0.1.0" dependencies = [ - "bytes", - "http 1.4.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "io-close" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" dependencies = [ - "bytes", - "futures-core", - "http 1.4.0", - "http-body 1.0.1", - "pin-project-lite", + "libc", + "winapi", ] [[package]] -name = "http-range" -version = "0.1.5" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "http-range-header" -version = "0.3.1" +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] -name = "http-range-header" -version = "0.4.2" +name = "is-docker" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] [[package]] -name = "httparse" -version = "1.10.1" +name = "is-macro" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "is-wsl" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] [[package]] -name = "humansize" -version = "2.1.3" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "hybrid-array" -version = "0.4.10" +name = "isolang" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "fe50d48c77760c55188549098b9a7f6e37ae980c586a24693d6b01c3b2010c3c" dependencies = [ - "typenum", + "phf 0.11.3", ] [[package]] -name = "hyper" -version = "0.14.32" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", + "either", ] [[package]] -name = "hyper" -version = "1.9.0" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec 1.15.1", - "tokio", - "want", + "either", ] [[package]] -name = "hyper-named-pipe" -version = "0.1.0" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "hex", - "hyper 1.9.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", - "winapi", + "either", ] [[package]] -name = "hyper-rustls" -version = "0.24.2" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", ] [[package]] -name = "hyper-rustls" -version = "0.25.0" +name = "javascriptcore-rs-sys" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.22.4", - "rustls-native-certs 0.7.3", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "webpki-roots 0.26.11", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "hyper-rustls" -version = "0.27.9" +name = "jiff" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ - "http 1.4.0", - "hyper 1.9.0", - "hyper-util", + "jiff-static", + "jiff-tzdb-platform", "log", - "rustls 0.23.38", - "rustls-native-certs 0.8.3", - "tokio", - "tokio-rustls 0.26.4", - "tower-service", - "webpki-roots 1.0.7", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" +name = "jiff-static" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ - "hyper 0.14.32", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "hyper-timeout" -version = "0.5.2" +name = "jiff-tzdb" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper 1.9.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "jiff-tzdb-platform" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ - "bytes", - "http-body-util", - "hyper 1.9.0", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "jiff-tzdb", ] [[package]] -name = "hyper-util" -version = "0.1.20" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.9.0", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2 0.6.3", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry 0.6.1", + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "hyperlocal" -version = "0.9.1" +name = "jni-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" dependencies = [ - "hex", - "http-body-util", - "hyper 1.9.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", + "jni-sys 0.4.1", ] [[package]] -name = "hyperloglogplus" +name = "jni-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "serde", + "jni-sys-macros", ] [[package]] -name = "hypher" -version = "0.1.7" +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef68590049bab63a464eee1a1158ac04c6f6613a546d8d90f78636b8b94f171" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] -name = "hypr-http-utils" -version = "0.1.0" +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "js-sys" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", + "cfg-if", + "futures-util", + "once_cell", "wasm-bindgen", - "windows-core 0.62.2", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "json-patch" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ - "cc", + "jsonptr 0.6.3", + "serde", + "serde_json", + "thiserror 1.0.69", ] [[package]] -name = "ico" -version = "0.5.0" +name = "json-patch" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" dependencies = [ - "byteorder", - "png 0.17.16", + "jsonptr 0.7.1", + "serde", + "serde_json", + "thiserror 1.0.69", ] [[package]] -name = "icu_collections" -version = "1.5.0" +name = "jsonptr" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" dependencies = [ - "displaydoc", "serde", - "yoke 0.7.5", - "zerofrom", - "zerovec 0.10.4", + "serde_json", ] [[package]] -name = "icu_collections" -version = "2.2.0" +name = "jsonptr" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke 0.8.2", - "zerofrom", - "zerovec 0.11.6", + "serde", + "serde_json", ] [[package]] -name = "icu_locale_core" -version = "2.2.0" +name = "jsonschema" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "84695c6689b01384700a3d93acecbd07231ee6fff1bf22ae980b4c307e6ddfd5" dependencies = [ - "displaydoc", - "litemap 0.8.2", - "tinystr 0.8.3", - "writeable 0.6.3", - "zerovec 0.11.6", + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex 0.17.0", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "regex-syntax", + "reqwest 0.13.2", + "rustls 0.23.38", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "jsonwebtoken" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ - "displaydoc", - "litemap 0.7.5", - "tinystr 0.7.6", - "writeable 0.5.5", - "zerovec 0.10.4", + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256 0.13.2", + "p384", + "pem", + "rand 0.8.6", + "rsa", + "serde", + "serde_json", + "sha2", + "signature 2.2.0", + "simple_asn1", ] [[package]] -name = "icu_locid_transform" -version = "1.5.0" +name = "kamadak-exif" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider 1.5.0", - "tinystr 0.7.6", - "zerovec 0.10.4", + "mutate_once", ] [[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - -[[package]] -name = "icu_normalizer" -version = "2.2.0" +name = "keyboard-types" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "icu_collections 2.2.0", - "icu_normalizer_data", - "icu_properties 2.2.0", - "icu_provider 2.2.0", - "smallvec 1.15.1", - "zerovec 0.11.6", + "bitflags 2.11.1", + "serde", + "unicode-segmentation", ] [[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +name = "knf-rs" +version = "0.3.2" +source = "git+https://github.com/thewh1teagle/pyannote-rs?rev=e23bd29#e23bd292298750ca8441039ae588afd5e4351c49" +dependencies = [ + "eyre", + "knf-rs-sys", + "ndarray", +] [[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +name = "knf-rs-sys" +version = "0.3.2" +source = "git+https://github.com/thewh1teagle/pyannote-rs?rev=e23bd29#e23bd292298750ca8441039ae588afd5e4351c49" dependencies = [ - "displaydoc", - "icu_collections 1.5.0", - "icu_locid_transform", - "icu_properties_data 1.5.1", - "icu_provider 1.5.0", - "serde", - "tinystr 0.7.6", - "zerovec 0.10.4", + "bindgen 0.69.5", + "cmake", ] [[package]] -name = "icu_properties" -version = "2.2.0" +name = "kqueue" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ - "icu_collections 2.2.0", - "icu_locale_core", - "icu_properties_data 2.2.0", - "icu_provider 2.2.0", - "zerotrie 0.2.4", - "zerovec 0.11.6", + "kqueue-sys", + "libc", ] [[package]] -name = "icu_properties_data" -version = "1.5.1" +name = "kqueue-sys" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] [[package]] -name = "icu_properties_data" -version = "2.2.0" +name = "krilla" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "a0ddfec86fec13d068075e14f22a7e217c281f3ed69ddcb427bf3f5d504fd674" +dependencies = [ + "base64 0.22.1", + "bumpalo", + "comemo", + "flate2", + "float-cmp 0.10.0", + "gif 0.13.3", + "hayro-write", + "image-webp", + "imagesize 0.14.0", + "indexmap 2.14.0", + "once_cell", + "pdf-writer", + "png 0.17.16", + "rayon", + "rustc-hash 2.1.2", + "rustybuzz", + "siphasher 1.0.2", + "skrifa", + "smallvec 1.15.1", + "subsetter", + "tiny-skia-path", + "xmp-writer", + "yoke 0.8.2", + "zune-jpeg 0.5.15", +] [[package]] -name = "icu_provider" -version = "1.5.0" +name = "krilla-svg" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "f485e1a850201a01dcd8d73e7cf09f2cd4c4cc85c2cd296359094d49336d8ef7" dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "postcard", - "serde", - "stable_deref_trait", - "tinystr 0.7.6", - "writeable 0.5.5", - "yoke 0.7.5", - "zerofrom", - "zerovec 0.10.4", + "flate2", + "fontdb", + "krilla", + "png 0.17.16", + "resvg", + "tiny-skia", + "usvg", ] [[package]] -name = "icu_provider" -version = "2.2.0" +name = "kstring" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable 0.6.3", - "yoke 0.8.2", - "zerofrom", - "zerotrie 0.2.4", - "zerovec 0.11.6", + "static_assertions", ] [[package]] -name = "icu_provider_adapters" -version = "1.5.0" +name = "kuchikiki" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ - "icu_locid", - "icu_locid_transform", - "icu_provider 1.5.0", - "tinystr 0.7.6", - "zerovec 0.10.4", + "cssparser 0.29.6", + "html5ever 0.29.1", + "indexmap 2.14.0", + "selectors 0.24.0", ] [[package]] -name = "icu_provider_blob" -version = "1.5.0" +name = "kurbo" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ - "icu_provider 1.5.0", - "postcard", - "serde", - "writeable 0.5.5", - "zerotrie 0.1.3", - "zerovec 0.10.4", + "arrayvec", + "euclid", + "smallvec 1.15.1", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "kurbo" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "arrayvec", + "euclid", + "smallvec 1.15.1", ] [[package]] -name = "icu_segmenter" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" +name = "language" +version = "0.1.0" dependencies = [ - "core_maths", - "displaydoc", - "icu_collections 1.5.0", - "icu_locid", - "icu_provider 1.5.0", - "icu_segmenter_data", + "codes-iso-639", + "schemars 1.2.1", "serde", - "utf8_iter", - "zerovec 0.10.4", + "serde_json", + "specta", + "thiserror 2.0.18", + "whichlang", + "whisper", ] [[package]] -name = "icu_segmenter_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb" - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" +name = "language-tags" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec 1.15.1", - "utf8_iter", -] +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] -name = "idna_adapter" -version = "1.2.1" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "icu_normalizer", - "icu_properties 2.2.0", + "spin", ] [[package]] -name = "ignore" -version = "0.4.25" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] -name = "image" -version = "0.25.10" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "gif 0.14.2", - "image-webp", - "moxcms 0.8.1", - "num-traits", - "png 0.18.1", - "tiff", - "zune-core 0.5.1", - "zune-jpeg 0.5.15", -] +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +name = "legacy-db-core" +version = "0.1.0" dependencies = [ - "byteorder-lite", - "quick-error", + "libsql", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", ] [[package]] -name = "imagesize" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" - -[[package]] -name = "imagesize" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" - -[[package]] -name = "imap-proto" -version = "0.16.6" +name = "levenshtein_automata" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1f9b30846c3d04371159ef3a0413ce7c1ae0a8c619cd255c60b3d902553f22" -dependencies = [ - "nom 7.1.3", -] +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] -name = "imara-diff" -version = "0.1.8" +name = "libappindicator" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" dependencies = [ - "hashbrown 0.15.5", + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", ] [[package]] -name = "impl-more" -version = "0.1.9" +name = "libappindicator-sys" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - -[[package]] -name = "importer-core" -version = "0.1.0" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ - "chrono", - "serde", - "serde_json", + "gtk-sys", + "libloading 0.7.4", + "once_cell", ] [[package]] -name = "indenter" -version = "0.3.4" +name = "libc" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] -name = "indexmap" -version = "1.9.3" +name = "libloading" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", + "cfg-if", + "winapi", ] [[package]] -name = "indexmap" -version = "2.14.0" +name = "libloading" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", + "cfg-if", + "windows-link 0.2.1", ] [[package]] -name = "indicatif" -version = "0.18.4" +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" -dependencies = [ - "console", - "portable-atomic", - "unicode-width 0.2.2", - "unit-prefix", - "web-time", -] +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] -name = "indoc" -version = "2.0.7" +name = "libpulse-binding" +version = "2.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397" dependencies = [ - "rustversion", + "bitflags 2.11.1", + "libc", + "libpulse-sys", + "num-derive", + "num-traits", + "winapi", ] [[package]] -name = "infer" -version = "0.19.0" +name = "libpulse-sys" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "d74371848b22e989f829cc1621d2ebd74960711557d8b45cfe740f60d0a05e61" dependencies = [ - "cfb", + "libc", + "num-derive", + "num-traits", + "pkg-config", + "winapi", ] [[package]] -name = "inotify" -version = "0.11.1" +name = "libredox" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags 2.11.1", - "inotify-sys", "libc", + "plain", + "redox_syscall 0.7.4", ] [[package]] -name = "inotify-sys" -version = "0.1.5" +name = "libspa" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ + "bitflags 2.11.1", + "cc", + "convert_case 0.8.0", + "cookie-factory", "libc", + "libspa-sys", + "nix 0.30.1", + "nom 8.0.0", + "system-deps 7.0.8", ] [[package]] -name = "inout" -version = "0.1.4" +name = "libspa-sys" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" dependencies = [ - "block-padding", - "generic-array", + "bindgen 0.72.1", + "cc", + "system-deps 7.0.8", ] [[package]] -name = "insta" -version = "1.47.2" +name = "libsql" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +checksum = "30fe980ac5693ed1f3db490559fb578885e913a018df64af8a1a46e1959a78df" dependencies = [ - "console", - "once_cell", - "pest", - "pest_derive", - "serde", - "similar", - "tempfile", + "anyhow", + "async-stream", + "async-trait", + "base64 0.21.7", + "bincode", + "bitflags 2.11.1", + "bytes", + "chrono", + "crc32fast", + "fallible-iterator 0.3.0", + "futures", + "http 0.2.12", + "hyper 0.14.32", + "hyper-rustls 0.25.0", + "libsql-hrana", + "libsql-sqlite3-parser", + "libsql-sys", + "libsql_replication", + "parking_lot", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tonic-web", + "tower 0.4.13", + "tower-http 0.4.4", + "tracing", + "uuid", + "zerocopy 0.7.35", ] [[package]] -name = "instability" -version = "0.3.12" +name = "libsql-ffi" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +checksum = "0be1da6f123ceb2cd23f469883415cab9ee963286a85d61e22afb8b12e15e681" dependencies = [ - "darling 0.23.0", - "indoc", - "proc-macro2", - "quote", - "syn 2.0.117", + "bindgen 0.66.1", + "cc", + "cmake", + "glob", ] [[package]] -name = "instant" -version = "0.1.13" +name = "libsql-hrana" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "intercept" -version = "0.1.0" +checksum = "d3358538b52cfcf9af4fe7aeb57d6843aafed2e8af80807bd636fd1448e94ea7" dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", + "base64 0.21.7", + "bytes", + "prost", + "serde", ] [[package]] -name = "io-close" -version = "0.3.7" +name = "libsql-rusqlite" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +checksum = "b646f94fc1d266e481c38a2d44d6d9d1be3ad04b56b90457acfb310dc450030e" dependencies = [ - "libc", - "winapi", + "bitflags 2.11.1", + "fallible-iterator 0.2.0", + "fallible-streaming-iterator", + "hashlink 0.8.4", + "libsql-ffi", + "smallvec 1.15.1", ] [[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" +name = "libsql-sqlite3-parser" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" dependencies = [ + "bitflags 2.11.1", + "cc", + "fallible-iterator 0.3.0", + "indexmap 2.14.0", + "log", "memchr", - "serde", + "phf 0.11.3", + "phf_codegen 0.11.3", + "phf_shared 0.11.3", + "uncased", ] [[package]] -name = "is-docker" -version = "0.2.0" +name = "libsql-sys" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +checksum = "90725458cc4461bc82f8f7983e80b002ea4f64b5184e1462f252d0dd74b122f5" dependencies = [ + "bytes", + "libsql-ffi", + "libsql-rusqlite", "once_cell", + "tracing", + "zerocopy 0.7.35", ] [[package]] -name = "is-macro" -version = "0.3.7" +name = "libsql_replication" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +checksum = "3bba5c9b3a26aca06d70f6a3646ba341cf574a548355353fe135af524b1b77cc" dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "aes", + "async-stream", + "async-trait", + "bytes", + "cbc", + "libsql-rusqlite", + "libsql-sys", + "parking_lot", + "prost", + "serde", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "uuid", + "zerocopy 0.7.35", ] [[package]] -name = "is-terminal" -version = "0.4.17" +name = "libsqlite3-sys" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", + "bindgen 0.72.1", + "cc", + "pkg-config", + "vcpkg", ] [[package]] -name = "is-wsl" -version = "0.4.0" +name = "libtest-mimic" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" dependencies = [ - "is-docker", - "once_cell", + "anstream", + "anstyle", + "clap", + "escape8259", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] -name = "isolang" -version = "2.4.0" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe50d48c77760c55188549098b9a7f6e37ae980c586a24693d6b01c3b2010c3c" -dependencies = [ - "phf 0.11.3", -] +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "itertools" +name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] -name = "itertools" -version = "0.14.0" +name = "lipsum" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064" dependencies = [ - "either", + "rand 0.8.6", + "rand_chacha 0.3.1", ] [[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +name = "listener-core" +version = "0.1.0" dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", + "audio", + "audio-utils", + "bytes", + "data", + "device-monitor", + "futures-util", + "host", + "hound", + "language", + "mp3", + "owhisper-client", + "owhisper-interface", + "ractor", + "sentry", + "serde", + "serde_json", + "specta", + "storage", + "supervisor", + "tauri-specta", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", + "transcribe-soniqo", + "transcript", + "uuid", + "vad-masking", + "vorbis_rs", ] [[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +name = "listener2-core" +version = "0.1.0" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", + "aspasia", + "audio-utils", + "bytes", + "denoise", + "futures-util", + "host", + "hound", + "language", + "owhisper-client", + "owhisper-interface", + "ractor", + "serde", + "serde_json", + "specta", + "strum 0.28.0", + "tauri-specta", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "transcribe-soniqo", ] [[package]] -name = "jiff" -version = "0.2.23" +name = "litemap" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" dependencies = [ - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-sys 0.61.2", + "serde", ] [[package]] -name = "jiff-static" -version = "0.2.23" +name = "litemap" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "llm-cactus" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "axum 0.8.9", + "cactus", + "cactus-model", + "futures-util", + "insta", + "llm-types", + "model-manager", + "pico-args", + "reqwest 0.13.2", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower 0.5.3", + "url", + "uuid", ] [[package]] -name = "jiff-tzdb" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" +name = "llm-types" +version = "0.1.0" +dependencies = [ + "async-openai", + "nom 8.0.0", + "rand 0.9.4", + "serde", + "serde_json", + "thiserror 2.0.18", +] [[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +name = "lmstudio" +version = "0.1.0" dependencies = [ - "jiff-tzdb", + "dirs 6.0.0", + "serde_json", + "thiserror 2.0.18", ] [[package]] -name = "jina" +name = "local-llm-core" version = "0.1.0" dependencies = [ - "reqwest 0.13.2", - "schemars 1.2.1", + "axum 0.8.9", + "dirs 6.0.0", + "file", + "gguf", + "llm-cactus", + "lmstudio", + "local-model", "serde", "serde_json", "specta", "thiserror 2.0.18", "tokio", - "url", + "tower-http 0.6.8", + "tracing", ] [[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +name = "local-model" +version = "0.1.0" dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "am", + "cactus-model", + "file", + "model-downloader", + "serde", + "specta", + "transcribe-soniqo", + "whisper-local-model", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "local-waker" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "jni-sys 0.4.1", + "scopeguard", ] [[package]] -name = "jni-sys" -version = "0.4.1" +name = "log" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "jni-sys-macros", + "hashbrown 0.15.5", ] [[package]] -name = "jni-sys-macros" -version = "0.4.1" +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" dependencies = [ - "quote", - "syn 2.0.117", + "byteorder", + "crc", ] [[package]] -name = "jobserver" -version = "0.1.34" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ - "getrandom 0.3.4", + "cc", "libc", + "pkg-config", ] [[package]] -name = "js-sys" -version = "0.3.95" +name = "mac" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] -name = "json-patch" -version = "3.0.1" +name = "mac_address2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "dc5e04aa39a4dc96076bca67bf60b63201ff06f9568e62f7904293d39de8cb0b" dependencies = [ - "jsonptr 0.6.3", - "serde", - "serde_json", + "nix 0.28.0", "thiserror 1.0.69", + "widestring", + "windows-sys 0.52.0", ] [[package]] -name = "json-patch" -version = "4.1.0" +name = "mach2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ - "jsonptr 0.7.1", - "serde", - "serde_json", - "thiserror 1.0.69", + "libc", ] [[package]] -name = "json5" -version = "0.4.1" +name = "mach2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" dependencies = [ - "pest", - "pest_derive", - "serde", + "libc", ] [[package]] -name = "jsonptr" -version = "0.6.3" +name = "machine-uid" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "7d7217d573cdb141d6da43113b098172e057d39915d79c4bdedbc3aacd46bd96" dependencies = [ - "serde", - "serde_json", + "libc", + "windows-registry 0.6.1", + "windows-sys 0.61.2", ] [[package]] -name = "jsonptr" -version = "0.7.1" +name = "macos-accessibility-client" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb" dependencies = [ - "serde", - "serde_json", + "core-foundation 0.9.4", + "core-foundation-sys", ] [[package]] -name = "jsonschema" -version = "0.46.0" +name = "markdown" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84695c6689b01384700a3d93acecbd07231ee6fff1bf22ae980b4c307e6ddfd5" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" dependencies = [ - "ahash", - "bytecount", - "data-encoding", - "email_address", - "fancy-regex 0.17.0", - "fraction", - "getrandom 0.3.4", - "idna", - "itoa", - "num-cmp", - "num-traits", - "percent-encoding", - "referencing", - "regex", - "regex-syntax", - "reqwest 0.13.2", - "rustls 0.23.38", - "serde", - "serde_json", - "unicode-general-category", - "uuid-simd", + "unicode-id", ] [[package]] -name = "jsonwebtoken" -version = "10.3.0" +name = "markup5ever" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ - "base64 0.22.1", - "ed25519-dalek", - "getrandom 0.2.17", - "hmac 0.12.1", - "js-sys", - "p256 0.13.2", - "p384", - "pem", - "rand 0.8.6", - "rsa", - "serde", - "serde_json", - "sha2 0.10.9", - "signature 2.2.0", - "simple_asn1", + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", ] [[package]] -name = "kamadak-exif" -version = "0.6.1" +name = "markup5ever" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ - "mutate_once", + "log", + "tendril 0.5.0", + "web_atoms", ] [[package]] -name = "kasuari" -version = "0.4.12" +name = "markup5ever_rcdom" +version = "0.38.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +checksum = "333171ccdf66e915257740d44e38ea5b1b19ce7b45d33cc35cb6f118fbd981ff" dependencies = [ - "hashbrown 0.16.1", - "portable-atomic", - "thiserror 2.0.18", + "html5ever 0.38.0", + "markup5ever 0.38.0", + "tendril 0.5.0", + "xml5ever", ] [[package]] -name = "keyboard-types" -version = "0.7.0" +name = "match_token" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ - "bitflags 2.11.1", - "serde", - "unicode-segmentation", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "khronos-egl" -version = "6.0.0" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "libc", - "pkg-config", + "regex-automata", ] [[package]] -name = "khronos_api" -version = "3.1.0" +name = "matches" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "knf-rs" -version = "0.3.2" -source = "git+https://github.com/thewh1teagle/pyannote-rs?rev=e23bd29#e23bd292298750ca8441039ae588afd5e4351c49" -dependencies = [ - "eyre", - "knf-rs-sys", - "ndarray", -] +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] -name = "knf-rs-sys" -version = "0.3.2" -source = "git+https://github.com/thewh1teagle/pyannote-rs?rev=e23bd29#e23bd292298750ca8441039ae588afd5e4351c49" -dependencies = [ - "bindgen 0.69.5", - "cmake", -] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "kqueue" -version = "1.1.1" +name = "matchit" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" -dependencies = [ - "kqueue-sys", - "libc", -] +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "kqueue-sys" -version = "1.0.4" +name = "matrixmultiply" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ - "bitflags 1.3.2", - "libc", + "autocfg", + "rawpointer", ] [[package]] -name = "krilla" -version = "0.6.0" +name = "maybe-async" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ddfec86fec13d068075e14f22a7e217c281f3ed69ddcb427bf3f5d504fd674" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ - "base64 0.22.1", - "bumpalo", - "comemo", - "flate2", - "float-cmp 0.10.0", - "gif 0.13.3", - "hayro-write", - "image-webp", - "imagesize 0.14.0", - "indexmap 2.14.0", - "once_cell", - "pdf-writer", - "png 0.17.16", - "rayon", - "rustc-hash 2.1.2", - "rustybuzz", - "siphasher 1.0.2", - "skrifa", - "smallvec 1.15.1", - "subsetter", - "tiny-skia-path", - "xmp-writer", - "yoke 0.8.2", - "zune-jpeg 0.5.15", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "krilla-svg" -version = "0.3.0" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f485e1a850201a01dcd8d73e7cf09f2cd4c4cc85c2cd296359094d49336d8ef7" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "flate2", - "fontdb", - "krilla", - "png 0.17.16", - "resvg", - "tiny-skia", - "usvg", + "cfg-if", + "digest", ] [[package]] -name = "kstring" -version = "2.0.2" +name = "mdast_util_to_markdown" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +checksum = "f4847e9e76278c5ad9a101a017c2798226c6fe1094dfb8e6338efd97e9a5bc55" dependencies = [ - "static_assertions", + "markdown", + "regex", ] [[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" +name = "measure_time" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" dependencies = [ - "cssparser 0.29.6", - "html5ever 0.29.1", - "indexmap 2.14.0", - "selectors 0.24.0", + "log", ] [[package]] -name = "kurbo" -version = "0.11.3" +name = "memchr" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" -dependencies = [ - "arrayvec", - "euclid", - "smallvec 1.15.1", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] -name = "kurbo" -version = "0.12.0" +name = "memmap2" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ - "arrayvec", - "euclid", - "smallvec 1.15.1", + "libc", ] [[package]] -name = "lab" -version = "0.11.0" +name = "memo-map" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" [[package]] -name = "language" -version = "0.1.0" +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ - "codes-iso-639", - "schemars 1.2.1", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "whichlang", - "whisper", + "autocfg", ] [[package]] -name = "language-tags" -version = "0.3.2" +name = "micromap" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] -name = "lazy_static" -version = "1.5.0" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "lazycell" -version = "1.3.0" +name = "mime_guess" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "minidump-common" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "5c4d14bcca0fd3ed165a03000480aaa364c6860c34e900cb2dafdf3b95340e77" +dependencies = [ + "bitflags 2.11.1", + "debugid", + "num-derive", + "num-traits", + "range-map", + "scroll", + "smart-default", +] [[package]] -name = "legacy-db-core" -version = "0.1.0" +name = "minidump-writer" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abcd9c8a1e6e1e9d56ce3627851f39a17ea83e17c96bc510f29d7e43d78a7d" dependencies = [ - "libsql", - "serde", - "serde_json", - "thiserror 2.0.18", - "tracing", + "bitflags 2.11.1", + "byteorder", + "cfg-if", + "crash-context", + "goblin", + "libc", + "log", + "mach2 0.4.3", + "memmap2", + "memoffset", + "minidump-common", + "nix 0.28.0", + "procfs-core", + "scroll", + "tempfile", + "thiserror 1.0.69", ] [[package]] -name = "lettre" -version = "0.11.21" +name = "minidumper" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabda5859ee7c06b995b9d1165aa52c39110e079ef609db97178d86aeb051fa7" +checksum = "9b4ebc9d1f8847ec1d078f78b35ed598e0ebefa1f242d5f83cd8d7f03960a7d1" dependencies = [ - "base64 0.22.1", - "email-encoding", - "email_address", - "fastrand", - "httpdate", - "idna", - "mime", - "nom 8.0.0", - "percent-encoding", - "quoted_printable", - "rustls 0.23.38", - "socket2 0.6.3", - "tokio", - "url", - "webpki-roots 1.0.7", + "cfg-if", + "crash-context", + "libc", + "log", + "minidump-writer", + "parking_lot", + "polling", + "scroll", + "thiserror 1.0.69", + "uds", ] [[package]] -name = "levenshtein_automata" -version = "0.2.1" +name = "minidumper-child" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" +checksum = "d7c4f23f835dbe67e44ddf884d3802ff549ca5948bf60e9fd70e9a13c96324d1" +dependencies = [ + "crash-handler", + "minidumper", + "thiserror 1.0.69", + "uuid", +] [[package]] -name = "libappindicator" -version = "0.9.0" +name = "minijinja" +version = "2.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d" dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", + "indexmap 2.14.0", + "memo-map", + "serde", + "serde_json", ] [[package]] -name = "libappindicator-sys" -version = "0.9.0" +name = "minijinja-contrib" +version = "2.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +checksum = "45092d80391870622fcf3bd82f5d2af18f99533ea60debb4bc9db0c76f0e809a" dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", + "minijinja", + "serde", ] [[package]] -name = "libc" -version = "0.2.185" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "libloading" -version = "0.7.4" +name = "minisign-verify" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" [[package]] -name = "libloading" +name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "cfg-if", - "windows-link 0.2.1", + "adler2", + "simd-adler32", ] [[package]] -name = "libm" -version = "0.2.16" +name = "mio" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] [[package]] -name = "libpulse-binding" -version = "2.30.1" +name = "model-downloader" +version = "0.1.0" +dependencies = [ + "download-interface", + "file", + "serde", + "specta", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "wiremock", + "zip 2.4.2", +] + +[[package]] +name = "model-manager" +version = "0.1.0" +dependencies = [ + "cactus", + "thiserror 2.0.18", + "tokio", + "uuid", + "whisper-local", +] + +[[package]] +name = "moxcms" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ - "bitflags 2.11.1", - "libc", - "libpulse-sys", - "num-derive", "num-traits", - "winapi", + "pxfm", ] [[package]] -name = "libpulse-sys" -version = "1.23.0" +name = "moxcms" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74371848b22e989f829cc1621d2ebd74960711557d8b45cfe740f60d0a05e61" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ - "libc", - "num-derive", "num-traits", - "pkg-config", - "winapi", + "pxfm", ] [[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +name = "mp3" +version = "0.1.0" dependencies = [ - "bitflags 2.11.1", - "libc", - "plain", - "redox_syscall 0.7.4", + "audio-utils", + "hound", + "mp3lame-encoder", + "tempfile", + "thiserror 2.0.18", ] [[package]] -name = "libspa" -version = "0.9.2" +name = "mp3lame-encoder" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" +checksum = "4f4764fd362524f3b15808fe877c40d49a191f31f1b043ceabb4079c5d327e3b" dependencies = [ - "bitflags 2.11.1", - "cc", - "convert_case 0.8.0", - "cookie-factory", - "libc", - "libspa-sys", - "nix 0.30.1", - "nom 8.0.0", - "system-deps 7.0.8", + "mp3lame-sys", ] [[package]] -name = "libspa-sys" -version = "0.9.2" +name = "mp3lame-sys" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" +checksum = "54e3b1772db47828840702e5a2e05694527f731abadf9b931355d54035f019d8" dependencies = [ - "bindgen 0.72.1", + "autotools", "cc", - "system-deps 7.0.8", + "libc", ] [[package]] -name = "libsql" -version = "0.9.30" +name = "muda" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30fe980ac5693ed1f3db490559fb578885e913a018df64af8a1a46e1959a78df" +checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "base64 0.21.7", - "bincode", - "bitflags 2.11.1", - "bytes", - "chrono", - "crc32fast", - "fallible-iterator 0.3.0", - "futures", - "http 0.2.12", - "hyper 0.14.32", - "hyper-rustls 0.25.0", - "libsql-hrana", - "libsql-sqlite3-parser", - "libsql-sys", - "libsql_replication", - "parking_lot", + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.17.16", "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tokio-util", - "tonic 0.11.0", - "tonic-web", - "tower 0.4.13", - "tower-http 0.4.4", - "tracing", - "uuid", - "zerocopy 0.7.35", -] - -[[package]] -name = "libsql-ffi" -version = "0.9.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be1da6f123ceb2cd23f469883415cab9ee963286a85d61e22afb8b12e15e681" -dependencies = [ - "bindgen 0.66.1", - "cc", - "cmake", - "glob", + "thiserror 2.0.18", + "windows-sys 0.60.2", ] [[package]] -name = "libsql-hrana" -version = "0.9.30" +name = "murmurhash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3358538b52cfcf9af4fe7aeb57d6843aafed2e8af80807bd636fd1448e94ea7" -dependencies = [ - "base64 0.21.7", - "bytes", - "prost 0.12.6", - "serde", -] +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] -name = "libsql-rusqlite" -version = "0.9.30" +name = "mutate_once" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646f94fc1d266e481c38a2d44d6d9d1be3ad04b56b90457acfb310dc450030e" -dependencies = [ - "bitflags 2.11.1", - "fallible-iterator 0.2.0", - "fallible-streaming-iterator", - "hashlink 0.8.4", - "libsql-ffi", - "smallvec 1.15.1", -] +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] -name = "libsql-sqlite3-parser" -version = "0.13.0" +name = "native-tls" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ - "bitflags 2.11.1", - "cc", - "fallible-iterator 0.3.0", - "indexmap 2.14.0", + "libc", "log", - "memchr", - "phf 0.11.3", - "phf_codegen 0.11.3", - "phf_shared 0.11.3", - "uncased", + "openssl", + "openssl-probe 0.2.1", + "openssl-sys", + "schannel", + "security-framework 3.7.0", + "security-framework-sys", + "tempfile", ] [[package]] -name = "libsql-sys" -version = "0.9.30" +name = "ndarray" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90725458cc4461bc82f8f7983e80b002ea4f64b5184e1462f252d0dd74b122f5" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" dependencies = [ - "bytes", - "libsql-ffi", - "libsql-rusqlite", - "once_cell", - "tracing", - "zerocopy 0.7.35", + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", ] [[package]] -name = "libsql_replication" -version = "0.9.30" +name = "ndk" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bba5c9b3a26aca06d70f6a3646ba341cf574a548355353fe135af524b1b77cc" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "aes", - "async-stream", - "async-trait", - "bytes", - "cbc", - "libsql-rusqlite", - "libsql-sys", - "parking_lot", - "prost 0.12.6", - "serde", + "bitflags 2.11.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tokio-util", - "tonic 0.11.0", - "tracing", - "uuid", - "zerocopy 0.7.35", ] [[package]] -name = "libsqlite3-sys" -version = "0.35.0" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" -dependencies = [ - "bindgen 0.72.1", - "cc", - "pkg-config", - "vcpkg", -] +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "libtest-mimic" -version = "0.8.2" +name = "ndk-sys" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "anstream", - "anstyle", - "clap", - "escape8259", + "jni-sys 0.3.1", ] [[package]] -name = "libwayshot-xcap" -version = "0.3.2" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558a3a7ca16a17a14adf8f051b3adcd7766d397532f5f6d6a48034db11e54c22" -dependencies = [ - "drm", - "gbm", - "gl", - "image", - "khronos-egl", - "memmap2", - "rustix 1.1.4", - "thiserror 2.0.18", - "tracing", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-wlr", -] +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] -name = "line-clipping" -version = "0.3.7" +name = "nix" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.11.1", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", + "memoffset", ] [[package]] -name = "linear" -version = "0.1.0" +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "hypr-http-utils", - "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tokio", - "tracing", - "utoipa", + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", ] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "nodrop" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] [[package]] -name = "linux-raw-sys" -version = "0.9.4" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "normalize-line-endings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "lipsum" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064" +name = "notification" +version = "0.1.0" dependencies = [ - "rand 0.8.6", - "rand_chacha 0.3.1", + "notification-interface", + "notification-linux", + "notification-macos", + "serde", + "tracing", ] [[package]] -name = "listener-core" +name = "notification-interface" version = "0.1.0" dependencies = [ - "audio", - "audio-utils", - "bytes", - "data", - "device-monitor", - "futures-util", - "host", - "hound", - "language", - "mp3", - "owhisper-client", - "owhisper-interface", - "ractor", - "sentry", "serde", - "serde_json", "specta", - "storage", - "supervisor", - "tauri-specta", - "tempfile", - "thiserror 2.0.18", +] + +[[package]] +name = "notification-linux" +version = "0.1.0" +dependencies = [ + "gdk", + "glib", + "gtk", + "indexmap 2.14.0", + "notification-interface", + "open", + "pango", "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "tracing-subscriber", - "transcript", "uuid", - "vad-masking", - "vorbis_rs", ] [[package]] -name = "listener2-core" +name = "notification-macos" version = "0.1.0" dependencies = [ - "aspasia", - "audio-utils", - "bytes", - "denoise", - "futures-util", - "host", - "hound", - "language", - "owhisper-client", - "owhisper-interface", - "ractor", + "notification-interface", + "objc2", + "objc2-app-kit", + "objc2-foundation", "serde", "serde_json", - "specta", - "strum 0.28.0", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", + "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", ] [[package]] -name = "litemap" -version = "0.7.5" +name = "notify" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "serde", + "bitflags 2.11.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", ] [[package]] -name = "litemap" -version = "0.8.2" +name = "notify-debouncer-full" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] [[package]] -name = "litrs" -version = "1.0.0" +name = "notify-types" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "llm-cactus" -version = "0.1.0" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "axum 0.8.9", - "cactus", - "cactus-model", - "futures-util", - "insta", - "llm-types", - "model-manager", - "pico-args", - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tower 0.5.3", - "url", - "uuid", + "bitflags 2.11.1", ] [[package]] -name = "llm-proxy" -version = "0.1.0" +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ - "analytics", - "api-env", - "async-stream", - "axum 0.8.9", - "backon", - "bytes", - "futures-util", - "observability", - "openrouter", - "reqwest 0.13.2", - "sentry", - "serde", - "serde_json", - "strum 0.28.0", - "thiserror 2.0.18", - "tokio", - "tower 0.5.3", - "tracing", - "tracing-subscriber", - "utoipa", - "wiremock", + "winapi", ] [[package]] -name = "llm-types" -version = "0.1.0" +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "async-openai", - "nom 8.0.0", - "rand 0.9.4", - "serde", - "serde_json", - "thiserror 2.0.18", + "windows-sys 0.61.2", ] [[package]] -name = "lmstudio" -version = "0.1.0" +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "dirs 6.0.0", - "serde_json", - "thiserror 2.0.18", + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", ] [[package]] -name = "local-llm-core" -version = "0.1.0" +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "axum 0.8.9", - "dirs 6.0.0", - "file", - "gguf", - "llm-cactus", - "lmstudio", - "local-model", + "num-integer", + "num-traits", "serde", - "serde_json", - "specta", - "thiserror 2.0.18", - "tokio", - "tower-http 0.6.8", - "tracing", ] [[package]] -name = "local-model" -version = "0.1.0" +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "am", - "cactus-model", - "file", - "model-downloader", - "serde", - "specta", - "whisper-local-model", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec 1.15.1", + "zeroize", ] [[package]] -name = "local-stt-core" +name = "num-cmp" version = "0.1.0" -dependencies = [ - "local-model", - "serde", - "specta", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" [[package]] -name = "local-stt-server" -version = "0.1.0" +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ - "axum 0.8.9", - "serde", - "specta", - "tauri-specta", - "tokio", - "tower-http 0.6.8", - "tracing", - "transcribe-cactus", - "transcribe-whisper-local", + "num-traits", ] [[package]] -name = "local-waker" -version = "0.1.4" +name = "num-conv" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] -name = "lock_api" -version = "0.4.14" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "scopeguard", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "log" -version = "0.4.29" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "loops" -version = "0.1.0" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "url", + "num-traits", ] [[package]] -name = "lru" -version = "0.12.5" +name = "num-iter" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "hashbrown 0.15.5", + "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "lru" -version = "0.16.4" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "hashbrown 0.16.1", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "lz4_flex" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" - -[[package]] -name = "lzma-rs" -version = "0.3.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "byteorder", - "crc", + "autocfg", + "libm", ] [[package]] -name = "lzma-sys" -version = "0.1.20" +name = "num_cpus" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "cc", + "hermit-abi", "libc", - "pkg-config", ] [[package]] -name = "mac" -version = "0.1.0" +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ - "libc", - "objc2-core-graphics", - "strum 0.28.0", + "num_enum_derive", + "rustversion", ] [[package]] -name = "mac" -version = "0.1.1" +name = "num_enum_derive" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "mac_address" -version = "1.1.8" +name = "num_threads" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ - "nix 0.29.0", - "winapi", + "libc", ] [[package]] -name = "mac_address2" -version = "2.0.2" +name = "objc2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e04aa39a4dc96076bca67bf60b63201ff06f9568e62f7904293d39de8cb0b" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "nix 0.28.0", - "thiserror 1.0.69", - "widestring", - "windows-sys 0.52.0", + "objc2-encode", + "objc2-exception-helper", ] [[package]] -name = "mach2" -version = "0.4.3" +name = "objc2-app-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ + "bitflags 2.11.1", + "block2", "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", ] [[package]] -name = "mach2" -version = "0.5.0" +name = "objc2-application-services" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +checksum = "69282c2b5bc58fba07cb9de2113619532eb551e98efe3d8d695509ef45fbd53b" dependencies = [ + "bitflags 2.11.1", "libc", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-services", + "objc2-foundation", ] [[package]] -name = "machine-uid" -version = "0.5.4" +name = "objc2-audio-toolbox" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d7217d573cdb141d6da43113b098172e057d39915d79c4bdedbc3aacd46bd96" +checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" dependencies = [ + "bitflags 2.11.1", "libc", - "windows-registry 0.6.1", - "windows-sys 0.61.2", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] -name = "macos-accessibility-client" -version = "0.0.1" +name = "objc2-av-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb" +checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" dependencies = [ - "core-foundation 0.9.4", - "core-foundation-sys", + "bitflags 2.11.1", + "block2", + "dispatch2", + "objc2", + "objc2-avf-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-video", + "objc2-foundation", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core", ] [[package]] -name = "mail-parser" -version = "0.11.2" +name = "objc2-avf-audio" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82a3d6522697593ba4c683e0a6ee5a40fee93bc1a525e3cc6eeb3da11fd8897" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" dependencies = [ - "hashify", + "objc2", + "objc2-foundation", ] [[package]] -name = "markdown" -version = "1.0.0" +name = "objc2-cloud-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "unicode-id", + "bitflags 2.11.1", + "objc2", + "objc2-foundation", ] [[package]] -name = "markup5ever" -version = "0.14.1" +name = "objc2-contacts" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +checksum = "b034b578389f89a85c055eacc8d8b368be5f04a6c1b07f672bf3aec21d0ef621" dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache 0.8.9", - "string_cache_codegen 0.5.4", - "tendril 0.4.3", + "block2", + "objc2", + "objc2-foundation", ] [[package]] -name = "markup5ever" -version = "0.38.0" +name = "objc2-core-audio" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" dependencies = [ - "log", - "tendril 0.5.0", - "web_atoms", + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] -name = "markup5ever_rcdom" -version = "0.38.0+unofficial" +name = "objc2-core-audio-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333171ccdf66e915257740d44e38ea5b1b19ce7b45d33cc35cb6f118fbd981ff" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" dependencies = [ - "html5ever 0.38.0", - "markup5ever 0.38.0", - "tendril 0.5.0", - "xml5ever", + "bitflags 2.11.1", + "objc2", ] [[package]] -name = "match_token" -version = "0.1.0" +name = "objc2-core-data" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bitflags 2.11.1", + "objc2", + "objc2-foundation", ] [[package]] -name = "matchers" -version = "0.2.0" +name = "objc2-core-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "regex-automata", + "bitflags 2.11.1", + "block2", + "dispatch2", + "libc", + "objc2", ] [[package]] -name = "matches" -version = "0.1.10" +name = "objc2-core-graphics" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.1", + "block2", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", + "objc2-metal", +] [[package]] -name = "matrixmultiply" -version = "0.3.10" +name = "objc2-core-image" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "autocfg", - "rawpointer", + "objc2", + "objc2-foundation", ] [[package]] -name = "maybe-async" -version = "0.2.10" +name = "objc2-core-location" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "block2", + "dispatch2", + "objc2", + "objc2-contacts", + "objc2-foundation", ] [[package]] -name = "mcp" -version = "0.1.0" +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" dependencies = [ - "api-auth", - "askama 0.15.6", - "axum 0.8.9", - "rmcp", + "bitflags 2.11.1", + "dispatch2", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", ] [[package]] -name = "md-5" -version = "0.10.6" +name = "objc2-core-services" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" dependencies = [ - "cfg-if", - "digest 0.10.7", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-security", ] [[package]] -name = "mdast_util_to_markdown" -version = "0.0.2" +name = "objc2-core-text" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4847e9e76278c5ad9a101a017c2798226c6fe1094dfb8e6338efd97e9a5bc55" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "markdown", - "regex", + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", ] [[package]] -name = "measure_time" -version = "0.9.0" +name = "objc2-core-video" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "log", + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", ] [[package]] -name = "memchr" -version = "2.8.0" +name = "objc2-encode" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] -name = "memmap2" -version = "0.9.10" +name = "objc2-event-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "51bc9ad7325642172110196bacd6af64027ec5549ded7fc6589ea03e0f792bf8" dependencies = [ - "libc", + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-graphics", + "objc2-core-location", + "objc2-foundation", + "objc2-map-kit", ] [[package]] -name = "memmem" +name = "objc2-exception-helper" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] [[package]] -name = "memo-map" -version = "0.3.3" +name = "objc2-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] [[package]] -name = "memoffset" -version = "0.9.1" +name = "objc2-image-io" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" dependencies = [ - "autocfg", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", ] [[package]] -name = "memory" -version = "0.1.0" +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] [[package]] -name = "micromap" -version = "0.3.0" +name = "objc2-io-surface" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", +] [[package]] -name = "mime" -version = "0.3.17" +name = "objc2-map-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "579edede1c244621cd8229b70ea6e20e6ec3bab5a74afdfd494b446b681e1e64" +dependencies = [ + "objc2", + "objc2-foundation", +] [[package]] -name = "mime_guess" -version = "2.0.5" +name = "objc2-media-toolbox" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" dependencies = [ - "mime", - "unicase", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-media", ] [[package]] -name = "mini-internal" -version = "0.1.45" +name = "objc2-metal" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8df435db5df1dd82a74f77e3c3addf6ab7665079c31e222a64f34f7475d87e" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bitflags 2.11.1", + "objc2", + "objc2-foundation", ] [[package]] -name = "minidump-common" -version = "0.21.2" +name = "objc2-osa-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4d14bcca0fd3ed165a03000480aaa364c6860c34e900cb2dafdf3b95340e77" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ "bitflags 2.11.1", - "debugid", - "num-derive", - "num-traits", - "range-map", - "scroll", - "smart-default", + "objc2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] -name = "minidump-writer" -version = "0.8.9" +name = "objc2-quartz-core" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abcd9c8a1e6e1e9d56ce3627851f39a17ea83e17c96bc510f29d7e43d78a7d" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.11.1", - "byteorder", - "cfg-if", - "crash-context", - "goblin", - "libc", - "log", - "mach2 0.4.3", - "memmap2", - "memoffset", - "minidump-common", - "nix 0.28.0", - "procfs-core", - "scroll", - "tempfile", - "thiserror 1.0.69", + "objc2", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] -name = "minidumper" -version = "0.8.3" +name = "objc2-security" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4ebc9d1f8847ec1d078f78b35ed598e0ebefa1f242d5f83cd8d7f03960a7d1" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ - "cfg-if", - "crash-context", - "libc", - "log", - "minidump-writer", - "parking_lot", - "polling", - "scroll", - "thiserror 1.0.69", - "uds", + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "minidumper-child" -version = "0.2.2" +name = "objc2-ui-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4f23f835dbe67e44ddf884d3802ff549ca5948bf60e9fd70e9a13c96324d1" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "crash-handler", - "minidumper", - "thiserror 1.0.69", - "uuid", + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", ] [[package]] -name = "minijinja" -version = "2.19.0" +name = "objc2-user-notifications" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" dependencies = [ - "indexmap 2.14.0", - "memo-map", - "serde", - "serde_json", + "objc2", + "objc2-foundation", ] [[package]] -name = "minijinja-contrib" -version = "2.19.0" +name = "objc2-web-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45092d80391870622fcf3bd82f5d2af18f99533ea60debb4bc9db0c76f0e809a" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "minijinja", - "serde", + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] [[package]] -name = "miniserde" -version = "0.1.45" +name = "observability" +version = "0.1.0" +dependencies = [ + "http 1.4.0", + "opentelemetry", + "opentelemetry_sdk", + "reqwest 0.13.2", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "ogg_next_sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c48e83ec09ab8a51d4e6be46608bf2a1293a79e2f7ea60246a2ce50eaef44ba" +checksum = "4c990730c2782b922815753a62af535e4205267df190dfbd8aa5d74e11d7dcc3" dependencies = [ - "itoa", - "mini-internal", - "zmij", + "cc", ] [[package]] -name = "minisign-verify" -version = "0.2.5" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oneshot" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" + +[[package]] +name = "onnx" +version = "0.1.0" dependencies = [ - "adler2", - "simd-adler32", + "ndarray", + "ort", + "thiserror 2.0.18", ] [[package]] -name = "mio" -version = "1.2.0" +name = "oorandom" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ + "dunce", + "is-wsl", "libc", - "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", + "pathdiff", ] [[package]] -name = "mobile-bridge" +name = "openai-transcription" version = "0.1.0" dependencies = [ - "db-app", - "db-core", - "db-execute", - "db-migrate", - "db-reactive", + "serde", "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", - "uniffi 0.31.1", + "strum 0.28.0", ] [[package]] -name = "model-downloader" +name = "openapi31to30" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89ba9a74ae43f97c114a68bfded49346e06414730bb62400b71ec524fcca1f9" +dependencies = [ + "color-eyre", + "serde", + "serde_yaml", +] + +[[package]] +name = "openapiv3" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_json", +] + +[[package]] +name = "opencode" version = "0.1.0" dependencies = [ - "download-interface", - "file", + "cli-process", + "dirs 6.0.0", + "futures-util", "serde", - "specta", + "serde_json", "tempfile", "thiserror 2.0.18", "tokio", + "tokio-stream", "tokio-util", - "tracing", - "wiremock", - "zip 2.4.2", + "url", ] [[package]] -name = "model-manager" +name = "openrouter" version = "0.1.0" dependencies = [ - "cactus", + "async-stream", + "base64 0.22.1", + "bytes", + "futures-util", + "reqwest 0.13.2", + "serde", + "serde_json", "thiserror 2.0.18", "tokio", - "uuid", - "whisper-local", ] [[package]] -name = "moxcms" -version = "0.7.11" +name = "openssl" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ - "num-traits", - "pxfm", + "bitflags 2.11.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "moxcms" -version = "0.8.1" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "num-traits", - "pxfm", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "mp3" -version = "0.1.0" -dependencies = [ - "audio-utils", - "hound", - "mp3lame-encoder", - "tempfile", - "thiserror 2.0.18", -] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "mp3lame-encoder" -version = "0.2.3" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4764fd362524f3b15808fe877c40d49a191f31f1b043ceabb4079c5d327e3b" -dependencies = [ - "mp3lame-sys", -] +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "mp3lame-sys" -version = "0.1.11" +name = "openssl-sys" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e3b1772db47828840702e5a2e05694527f731abadf9b931355d54035f019d8" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ - "autotools", "cc", "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "muda" -version = "0.17.2" +name = "opentelemetry" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" +checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.17.16", - "serde", + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", "thiserror 2.0.18", - "windows-sys 0.60.2", + "tracing", ] [[package]] -name = "multimap" -version = "0.10.1" +name = "opentelemetry_sdk" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.4", + "serde_json", + "thiserror 2.0.18", +] [[package]] -name = "murmurhash32" -version = "0.3.1" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "mutate_once" -version = "0.1.2" +name = "ordered-multimap" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" - -[[package]] -name = "nango" -version = "0.1.0" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ - "hex", - "hmac 0.13.0", - "hypr-http-utils", - "reqwest 0.13.2", - "schemars 1.2.1", - "serde", - "serde_json", - "sha2 0.11.0", - "specta", - "strum 0.28.0", - "thiserror 2.0.18", - "tokio", - "url", + "dlv-list", + "hashbrown 0.14.5", ] [[package]] -name = "nanohtml2text" -version = "0.2.1" +name = "ordered-stream" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec1bc47d34ae756616f387c11fd0595f86f2cc7e6473bde9e3ded30cb902a1" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] -name = "native-tls" -version = "0.2.18" +name = "ort" +version = "2.0.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe 0.2.1", - "openssl-sys", - "schannel", - "security-framework 3.7.0", - "security-framework-sys", - "tempfile", + "ndarray", + "ort-sys", + "smallvec 2.0.0-alpha.10", + "tracing", ] [[package]] -name = "ndarray" -version = "0.16.1" +name = "ort-sys" +version = "2.0.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890" dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", + "flate2", + "pkg-config", + "sha2", + "tar", + "ureq", ] [[package]] -name = "ndk" -version = "0.9.0" +name = "os_info" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", + "android_system_properties", "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", + "nix 0.30.1", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", ] [[package]] -name = "ndk-context" -version = "0.1.1" +name = "os_pipe" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] [[package]] -name = "ndk-sys" -version = "0.6.0+11769913" +name = "osakit" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" dependencies = [ - "jni-sys 0.3.1", + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", ] [[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +name = "outlook-calendar" +version = "0.1.0" +dependencies = [ + "chrono", + "hypr-http-utils", + "serde", + "serde_json", + "specta", + "thiserror 2.0.18", + "tokio", + "urlencoding", + "utoipa", +] [[package]] -name = "nix" -version = "0.28.0" +name = "outref" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owhisper-client" +version = "0.0.1" dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", - "memoffset", + "am", + "audio-utils", + "backon", + "base64 0.22.1", + "bytes", + "cactus-model", + "data", + "deepgram", + "futures-util", + "language", + "observability", + "openai-transcription", + "owhisper-interface", + "reqwest 0.13.2", + "reqwest-middleware", + "reqwest-tracing", + "rodio", + "serde", + "serde_json", + "soniox", + "strum 0.28.0", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", + "url", + "ws-client", ] [[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +name = "owhisper-interface" +version = "0.1.0" dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset", + "chrono", + "codes-iso-639", + "deepgram", + "language", + "schemars 1.2.1", + "serde", + "serde_bytes", + "serde_json", + "specta", + "utoipa", + "uuid", ] [[package]] -name = "nix" -version = "0.30.1" +name = "ownedbytes" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", + "stable_deref_trait", ] [[package]] -name = "nodrop" -version = "0.1.14" +name = "owo-colors" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] -name = "nom" -version = "7.1.3" +name = "p256" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "memchr", - "minimal-lexical", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2", ] [[package]] -name = "nom" -version = "8.0.0" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "memchr", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", ] [[package]] -name = "nonzero_ext" -version = "0.3.0" +name = "p384" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", +] [[package]] -name = "normalize-line-endings" -version = "0.3.0" +name = "page_size" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] [[package]] -name = "notification" -version = "0.1.0" +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ - "notification-interface", - "notification-linux", - "notification-macos", - "serde", - "tracing", + "approx", + "fast-srgb8", + "libm", + "palette_derive", ] [[package]] -name = "notification-interface" -version = "0.1.0" +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ - "serde", - "specta", + "by_address", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "notification-linux" -version = "0.1.0" +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" dependencies = [ - "gdk", + "gio", "glib", - "gtk", - "indexmap 2.14.0", - "notification-interface", - "open", - "pango", - "tokio", - "uuid", + "libc", + "once_cell", + "pango-sys", ] [[package]] -name = "notification-macos" -version = "0.1.0" +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ - "notification-interface", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "serde", - "serde_json", - "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", ] [[package]] -name = "notification-macos2" -version = "0.1.0" -dependencies = [ - "block2", - "env_logger", - "log", - "notification-interface", - "objc2", - "objc2-foundation", - "objc2-user-notifications", - "uuid", -] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] -name = "notification-worker" -version = "0.1.0" +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "apalis", - "apalis-cron", - "chrono", - "cron 0.16.0", - "serde", - "serde_json", - "specta", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tracing", + "lock_api", + "parking_lot_core", ] [[package]] -name = "notify" -version = "8.2.0" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "bitflags 2.11.1", - "fsevent-sys", - "inotify", - "kqueue", + "cfg-if", "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", + "redox_syscall 0.5.18", + "smallvec 1.15.1", + "windows-link 0.2.1", ] [[package]] -name = "notify-debouncer-full" -version = "0.5.0" +name = "parse-display" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" dependencies = [ - "file-id", - "log", - "notify", - "notify-types", - "walkdir", + "parse-display-derive", + "regex", + "regex-syntax", ] [[package]] -name = "notify-types" -version = "2.1.0" +name = "parse-display-derive" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" dependencies = [ - "bitflags 2.11.1", + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.117", ] [[package]] -name = "ntapi" -version = "0.4.3" +name = "parse-zoneinfo" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ - "winapi", + "regex", ] [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "num" -version = "0.4.3" +name = "pastey" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] -name = "num-bigint" -version = "0.4.6" +name = "pathdiff" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", - "serde", -] +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] -name = "num-bigint-dig" -version = "0.8.6" +name = "pbkdf2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.6", - "smallvec 1.15.1", - "zeroize", + "digest", + "hmac", ] [[package]] -name = "num-cmp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" - -[[package]] -name = "num-complex" -version = "0.4.6" +name = "pdf-writer" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "92a79477295a713c2ed425aa82a8b5d20cec3fdee203706cbe6f3854880c1c81" dependencies = [ - "num-traits", + "bitflags 2.11.1", + "itoa", + "memchr", + "ryu", ] [[package]] -name = "num-conv" -version = "0.2.1" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] -name = "num-derive" -version = "0.4.2" +name = "pem" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "base64 0.22.1", + "serde_core", ] [[package]] -name = "num-integer" -version = "0.1.46" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "num-traits", + "base64ct", ] [[package]] -name = "num-iter" -version = "0.1.45" +name = "pem-rfc7468" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "base64ct", ] [[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "num_cpus" -version = "1.17.0" +name = "pest" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ - "hermit-abi", - "libc", + "memchr", + "ucd-trie", ] [[package]] -name = "num_enum" -version = "0.7.6" +name = "pest_derive" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ - "num_enum_derive", - "rustversion", + "pest", + "pest_generator", ] [[package]] -name = "num_enum_derive" -version = "0.7.6" +name = "pest_generator" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ - "proc-macro-crate 3.5.0", + "pest", + "pest_meta", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] -name = "num_threads" -version = "0.1.7" +name = "pest_meta" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ - "libc", + "pest", + "sha2", ] [[package]] -name = "objc2" -version = "0.6.4" +name = "petgraph" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "objc2-encode", - "objc2-exception-helper", + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", ] [[package]] -name = "objc2-app-kit" -version = "0.3.2" +name = "phf" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation", - "objc2-quartz-core", + "phf_shared 0.8.0", ] [[package]] -name = "objc2-application-services" -version = "0.3.2" +name = "phf" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69282c2b5bc58fba07cb9de2113619532eb551e98efe3d8d695509ef45fbd53b" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "bitflags 2.11.1", - "libc", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-services", - "objc2-foundation", + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] -name = "objc2-audio-toolbox" -version = "0.3.2" +name = "phf" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "bitflags 2.11.1", - "libc", - "objc2", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", + "phf_macros 0.11.3", + "phf_shared 0.11.3", ] [[package]] -name = "objc2-av-foundation" -version = "0.3.2" +name = "phf" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "bitflags 2.11.1", - "block2", - "dispatch2", - "objc2", - "objc2-avf-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-video", - "objc2-foundation", - "objc2-image-io", - "objc2-media-toolbox", - "objc2-quartz-core", + "phf_shared 0.12.1", ] [[package]] -name = "objc2-avf-audio" -version = "0.3.2" +name = "phf" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "objc2", - "objc2-foundation", + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", ] [[package]] -name = "objc2-cloud-kit" -version = "0.3.2" +name = "phf_codegen" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-foundation", + "phf_generator 0.8.0", + "phf_shared 0.8.0", ] [[package]] -name = "objc2-contacts" -version = "0.3.2" +name = "phf_codegen" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b034b578389f89a85c055eacc8d8b368be5f04a6c1b07f672bf3aec21d0ef621" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] -name = "objc2-core-audio" -version = "0.3.2" +name = "phf_codegen" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "dispatch2", - "objc2", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] -name = "objc2-core-audio-types" -version = "0.3.2" +name = "phf_generator" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" dependencies = [ - "bitflags 2.11.1", - "objc2", + "phf_shared 0.8.0", + "rand 0.7.3", ] [[package]] -name = "objc2-core-data" -version = "0.3.2" +name = "phf_generator" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-foundation", + "phf_shared 0.10.0", + "rand 0.8.6", ] [[package]] -name = "objc2-core-foundation" -version = "0.3.2" +name = "phf_generator" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "bitflags 2.11.1", - "block2", - "dispatch2", - "libc", - "objc2", + "phf_shared 0.11.3", + "rand 0.8.6", ] [[package]] -name = "objc2-core-graphics" -version = "0.3.2" +name = "phf_generator" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "bitflags 2.11.1", - "block2", - "dispatch2", - "libc", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", - "objc2-metal", + "fastrand", + "phf_shared 0.13.1", ] [[package]] -name = "objc2-core-image" -version = "0.3.2" +name = "phf_macros" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "objc2", - "objc2-foundation", + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "objc2-core-location" -version = "0.3.2" +name = "phf_macros" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "block2", - "dispatch2", - "objc2", - "objc2-contacts", - "objc2-foundation", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "objc2-core-media" -version = "0.3.2" +name = "phf_macros" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "bitflags 2.11.1", - "block2", - "dispatch2", - "objc2", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-core-video", + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "objc2-core-services" -version = "0.3.2" +name = "phf_shared" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-security", + "siphasher 0.3.11", ] [[package]] -name = "objc2-core-text" -version = "0.3.2" +name = "phf_shared" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", + "siphasher 0.3.11", ] [[package]] -name = "objc2-core-video" -version = "0.3.2" +name = "phf_shared" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", - "objc2-metal", + "siphasher 1.0.2", + "uncased", ] [[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-event-kit" -version = "0.3.2" +name = "phf_shared" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bc9ad7325642172110196bacd6af64027ec5549ded7fc6589ea03e0f792bf8" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-graphics", - "objc2-core-location", - "objc2-foundation", - "objc2-map-kit", + "siphasher 1.0.2", ] [[package]] -name = "objc2-exception-helper" -version = "0.1.1" +name = "phf_shared" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "cc", + "siphasher 1.0.2", ] [[package]] -name = "objc2-foundation" -version = "0.3.2" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] -name = "objc2-image-io" -version = "0.3.2" +name = "pin-project" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", + "pin-project-internal", ] [[package]] -name = "objc2-io-kit" -version = "0.3.2" +name = "pin-project-internal" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ - "libc", - "objc2-core-foundation", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "objc2-map-kit" -version = "0.3.2" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579edede1c244621cd8229b70ea6e20e6ec3bab5a74afdfd494b446b681e1e64" -dependencies = [ - "objc2", - "objc2-foundation", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "objc2-media-toolbox" -version = "0.3.2" +name = "piper" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ - "objc2", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-core-media", + "atomic-waker", + "fastrand", + "futures-io", ] [[package]] -name = "objc2-metal" -version = "0.3.2" +name = "pipewire" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" dependencies = [ + "anyhow", "bitflags 2.11.1", - "objc2", - "objc2-foundation", + "libc", + "libspa", + "libspa-sys", + "nix 0.30.1", + "once_cell", + "pipewire-sys", + "thiserror 2.0.18", ] [[package]] -name = "objc2-osa-kit" -version = "0.3.2" +name = "pipewire-sys" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "bindgen 0.72.1", + "libspa-sys", + "system-deps 7.0.8", ] [[package]] -name = "objc2-quartz-core" -version = "0.3.2" +name = "pkcs1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] -name = "objc2-security" -version = "0.3.2" +name = "pkcs8" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", + "der 0.6.1", + "spki 0.6.0", ] [[package]] -name = "objc2-ui-kit" -version = "0.3.2" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", + "der 0.7.10", + "spki 0.7.3", ] [[package]] -name = "objc2-user-notifications" -version = "0.3.2" +name = "pkg-config" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] -name = "objc2-web-kit" -version = "0.3.2" +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", -] +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] -name = "object" -version = "0.37.3" +name = "plist" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ - "memchr", + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml 0.38.4", + "serde", + "time", ] [[package]] -name = "observability" -version = "0.1.0" +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ - "http 1.4.0", - "opentelemetry", - "opentelemetry_sdk", - "reqwest 0.13.2", - "tracing", - "tracing-opentelemetry", + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "octocrab" -version = "0.49.7" +name = "plotters-backend" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f6687a23731011d0117f9f4c3cdabaa7b5e42ca671f42b5cc0657c492540e3" -dependencies = [ - "arc-swap", - "async-trait", - "base64 0.22.1", - "bytes", - "cargo_metadata 0.23.1", - "cfg-if", - "chrono", - "either", - "futures", - "futures-util", - "getrandom 0.2.17", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.9", - "hyper-timeout 0.5.2", - "hyper-util", - "jsonwebtoken", - "once_cell", - "percent-encoding", - "pin-project", - "secrecy", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "snafu", - "tokio", - "tower 0.5.3", - "tower-http 0.6.8", - "tracing", - "url", - "web-time", -] +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] -name = "ogg_next_sys" -version = "0.1.4" +name = "plotters-svg" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c990730c2782b922815753a62af535e4205267df190dfbd8aa5d74e11d7dcc3" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ - "cc", + "plotters-backend", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "png" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] -name = "oneshot" -version = "0.1.13" +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" - -[[package]] -name = "onnx" -version = "0.1.0" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "ndarray", - "ort", - "thiserror 2.0.18", + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", ] [[package]] -name = "oorandom" -version = "11.1.5" +name = "port-killer" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +checksum = "4f8f8abaf06419461fc718f5a0929c18d06dd825f26c066e376f365f2d8c2ec2" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "port_check" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "4c2749dcd0984ec1be3c01001bb1d83623a58c3c0049a99b9afec61464fa98e7" [[package]] -name = "open" -version = "5.3.3" +name = "portable-atomic" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openai-transcription" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "strum 0.28.0", -] +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] -name = "openapi31to30" -version = "0.0.1" +name = "portable-atomic-util" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89ba9a74ae43f97c114a68bfded49346e06414730bb62400b71ec524fcca1f9" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ - "color-eyre", - "serde", - "serde_yaml", + "portable-atomic", ] [[package]] -name = "openapiv3" -version = "2.2.0" +name = "postcard" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ - "indexmap 2.14.0", + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", "serde", - "serde_json", ] [[package]] -name = "opencode" -version = "0.1.0" +name = "posthog-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3862eb754386a1273612a885e961b19be0bb08e6d99e483557af41236d0dac12" dependencies = [ - "cli-process", - "dirs 6.0.0", - "futures-util", + "chrono", + "derive_builder", + "regex", + "reqwest 0.13.2", + "semver", "serde", "serde_json", - "tempfile", - "thiserror 2.0.18", + "sha1", "tokio", - "tokio-stream", - "tokio-util", - "url", + "tracing", + "uuid", ] [[package]] -name = "openrouter" -version = "0.1.0" +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "async-stream", - "base64 0.22.1", - "bytes", - "futures-util", - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", + "zerovec 0.11.6", ] [[package]] -name = "openssl" -version = "0.10.77" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "zerocopy 0.8.48", ] [[package]] -name = "openssl-probe" -version = "0.1.6" +name = "precomputed-hash" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "openssl-probe" -version = "0.2.1" +name = "predicates" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.113" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "anstyle", + "difflib", + "float-cmp 0.10.0", + "normalize-line-endings", + "predicates-core", + "regex", ] [[package]] -name = "openstatus" -version = "0.1.0" -dependencies = [ - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "url", -] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] -name = "opentelemetry" -version = "0.30.0" +name = "predicates-tree" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.18", - "tracing", + "predicates-core", + "termtree", ] [[package]] -name = "opentelemetry-http" -version = "0.30.0" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "async-trait", - "bytes", - "http 1.4.0", - "opentelemetry", - "reqwest 0.12.28", + "proc-macro2", + "syn 2.0.117", ] [[package]] -name = "opentelemetry-otlp" -version = "0.30.0" +name = "primal-check" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" dependencies = [ - "http 1.4.0", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost 0.13.5", - "reqwest 0.12.28", - "thiserror 2.0.18", - "tokio", - "tonic 0.13.1", - "tracing", + "num-integer", ] [[package]] -name = "opentelemetry-proto" -version = "0.30.0" +name = "primeorder" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost 0.13.5", - "tonic 0.13.1", + "elliptic-curve 0.13.8", ] [[package]] -name = "opentelemetry_sdk" -version = "0.30.0" +name = "proc-macro-crate" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "futures-channel", - "futures-executor", - "futures-util", - "opentelemetry", - "percent-encoding", - "rand 0.9.4", - "serde_json", - "thiserror 2.0.18", + "once_cell", + "toml_edit 0.19.15", ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "proc-macro-crate" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] [[package]] -name = "ordered-float" -version = "4.6.0" +name = "proc-macro-crate" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "num-traits", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] -name = "ordered-multimap" -version = "0.7.3" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "dlv-list", - "hashbrown 0.14.5", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", ] [[package]] -name = "ordered-stream" -version = "0.2.0" +name = "proc-macro-error-attr" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "futures-core", - "pin-project-lite", + "proc-macro2", + "quote", + "version_check", ] [[package]] -name = "ort" -version = "2.0.0-rc.10" +name = "proc-macro-hack" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "ndarray", - "ort-sys", - "smallvec 2.0.0-alpha.10", - "tracing", + "unicode-ident", ] [[package]] -name = "ort-sys" -version = "2.0.0-rc.10" +name = "procfs-core" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "flate2", - "pkg-config", - "sha2 0.10.9", - "tar", - "ureq", + "bitflags 2.11.1", + "hex", ] [[package]] -name = "os_info" -version = "3.14.0" +name = "prodash" +version = "29.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" dependencies = [ - "android_system_properties", "log", - "nix 0.30.1", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", + "parking_lot", ] [[package]] -name = "os_pipe" -version = "1.2.3" +name = "prodash" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" dependencies = [ - "libc", - "windows-sys 0.61.2", + "parking_lot", ] [[package]] -name = "osakit" -version = "0.3.1" +name = "progenitor" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +checksum = "bdc8cf6196a0139ab7b833b500f7f1acd005c91be0fe27a9e20112bf83dc9535" dependencies = [ - "objc2", - "objc2-foundation", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.18", + "progenitor-client 0.12.0", + "progenitor-impl", + "progenitor-macro", ] [[package]] -name = "outlook-calendar" -version = "0.1.0" +name = "progenitor-client" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffab7b358944dba033a7b324e7558e66e6bcb1fb4705cf57f26fd5092bcae630" dependencies = [ - "chrono", - "hypr-http-utils", + "bytes", + "futures-core", + "percent-encoding", + "reqwest 0.13.2", "serde", "serde_json", - "specta", - "thiserror 2.0.18", - "tokio", - "urlencoding", - "utoipa", + "serde_urlencoded", ] [[package]] -name = "outref" -version = "0.5.2" +name = "progenitor-client" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - -[[package]] -name = "owhisper-client" -version = "0.0.1" +checksum = "3999c302f5f2a42b7ca1cc39ad9e612c74cf2910ef6e58f869e45f3068b9659f" dependencies = [ - "am", - "audio-utils", - "backon", - "base64 0.22.1", "bytes", - "cactus-model", - "data", - "deepgram", - "futures-util", - "language", - "observability", - "openai-transcription", - "owhisper-interface", + "futures-core", + "percent-encoding", "reqwest 0.13.2", - "reqwest-middleware", - "reqwest-tracing", - "rodio", "serde", "serde_json", - "soniox", - "strum 0.28.0", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "tracing-subscriber", - "url", - "ws-client", + "serde_urlencoded", ] [[package]] -name = "owhisper-config" -version = "0.0.1" +name = "progenitor-impl" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea21f106f8345d4417f3a39c90e4ff826ea777cb51579d72165d380a4d6f685d" dependencies = [ - "config", - "dirs 6.0.0", - "schemars 1.2.1", - "serde", + "heck 0.5.0", + "http 1.4.0", + "indexmap 2.14.0", + "openapiv3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "syn 2.0.117", "thiserror 2.0.18", + "typify", + "unicode-ident", ] [[package]] -name = "owhisper-interface" -version = "0.1.0" +name = "progenitor-macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16f9cab95e07c5e6995db8d69d86e7472688b3deedb23e52631e398fddc2470" dependencies = [ - "chrono", - "codes-iso-639", - "deepgram", - "language", - "schemars 1.2.1", + "openapiv3", + "proc-macro2", + "progenitor-impl", + "quote", + "schemars 0.8.22", "serde", - "serde_bytes", "serde_json", - "specta", - "utoipa", - "uuid", + "serde_tokenstream", + "serde_yaml", + "syn 2.0.117", ] [[package]] -name = "ownedbytes" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" +name = "progenitor-utils" +version = "0.1.0" dependencies = [ - "stable_deref_trait", + "openapi31to30", + "openapiv3", + "prettyplease", + "progenitor", + "serde_json", + "serde_yaml", + "syn 2.0.117", ] [[package]] -name = "owo-colors" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" - -[[package]] -name = "p256" -version = "0.11.1" +name = "prost" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.9", + "bytes", + "prost-derive", ] [[package]] -name = "p256" -version = "0.13.2" +name = "prost-derive" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", - "sha2 0.10.9", + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "p384" -version = "0.13.1" +name = "psl-types" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", - "sha2 0.10.9", -] +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] -name = "page_size" -version = "0.6.0" +name = "psm" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" dependencies = [ - "libc", - "winapi", + "ar_archive_writer", + "cc", ] [[package]] -name = "palette" -version = "0.7.6" +name = "publicsuffix" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "approx", - "fast-srgb8", - "libm", - "palette_derive", + "idna", + "psl-types", ] [[package]] -name = "palette_derive" -version = "0.7.6" +name = "pulldown-cmark" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "by_address", - "proc-macro2", - "quote", - "syn 2.0.117", + "bitflags 2.11.1", + "memchr", + "unicase", ] [[package]] -name = "pango" -version = "0.18.3" +name = "pxfm" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "pyannote-local" +version = "0.1.0" dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", + "approx", + "dasp", + "data", + "knf-rs", + "onnx", + "rodio", + "serde", + "simsimd", + "specta", + "thiserror 2.0.18", ] [[package]] -name = "pango-sys" -version = "0.18.0" +name = "qcms" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] +checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa" [[package]] -name = "parking" -version = "2.2.1" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] -name = "parking_lot" -version = "0.12.5" +name = "quick-xml" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ - "lock_api", - "parking_lot_core", + "memchr", + "serde", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "quick-xml" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec 1.15.1", - "windows-link 0.2.1", + "memchr", ] [[package]] -name = "parse-display" -version = "0.9.1" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ - "parse-display-derive", - "regex", - "regex-syntax", + "bytes", + "cfg_aliases 0.2.1", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.2", + "rustls 0.23.38", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", ] [[package]] -name = "parse-display-derive" -version = "0.9.1" +name = "quinn-proto" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ - "proc-macro2", - "quote", - "regex", - "regex-syntax", - "structmeta", - "syn 2.0.117", + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash 2.1.2", + "rustls 0.23.38", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "parse-zoneinfo" -version = "0.3.1" +name = "quinn-udp" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ - "regex", + "cfg_aliases 0.2.1", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", ] [[package]] -name = "paste" -version = "1.0.15" +name = "quote" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + [[package]] -name = "pastey" -version = "0.2.1" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "pathdiff" -version = "0.2.3" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "ractor" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "4a64ac8ba2e8d71b25c55ab7acafc481ae4c9175f3ee8f7c36b66c4cad369bb5" dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", + "async-trait", + "bon 2.3.0", + "dashmap", + "futures", + "js-sys", + "once_cell", + "strum 0.26.3", + "tokio", + "tokio_with_wasm", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", ] [[package]] -name = "pdf-writer" -version = "0.14.0" +name = "rand" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a79477295a713c2ed425aa82a8b5d20cec3fdee203706cbe6f3854880c1c81" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "bitflags 2.11.1", - "itoa", - "memchr", - "ryu", + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", ] [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "rand" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] [[package]] -name = "pem" -version = "3.0.6" +name = "rand" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "base64 0.22.1", - "serde_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "rand" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "base64ct", + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] -name = "pem-rfc7468" -version = "1.0.0" +name = "rand_chacha" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "base64ct", + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] [[package]] -name = "pest" -version = "2.8.6" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "memchr", - "ucd-trie", + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] -name = "pest_derive" -version = "2.8.6" +name = "rand_core" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "pest", - "pest_generator", + "getrandom 0.1.16", ] [[package]] -name = "pest_generator" -version = "2.8.6" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.117", + "getrandom 0.2.17", ] [[package]] -name = "pest_meta" -version = "2.8.6" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "pest", - "sha2 0.10.9", + "getrandom 0.3.4", ] [[package]] -name = "petgraph" -version = "0.7.1" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset 0.5.7", - "indexmap 2.14.0", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] -name = "petgraph" -version = "0.8.3" +name = "rand_distr" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ - "fixedbitset 0.5.7", - "hashbrown 0.15.5", - "indexmap 2.14.0", + "num-traits", + "rand 0.8.6", ] [[package]] -name = "phf" -version = "0.8.0" +name = "rand_distr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" dependencies = [ - "phf_shared 0.8.0", + "num-traits", + "rand 0.10.1", ] [[package]] -name = "phf" -version = "0.10.1" +name = "rand_hc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", + "rand_core 0.5.1", ] [[package]] -name = "phf" -version = "0.11.3" +name = "rand_pcg" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", + "rand_core 0.5.1", ] [[package]] -name = "phf" -version = "0.12.1" +name = "range-map" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" dependencies = [ - "phf_shared 0.12.1", + "num-traits", ] [[package]] -name = "phf" -version = "0.13.1" +name = "raw-window-handle" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros 0.13.1", - "phf_shared 0.13.1", - "serde", -] +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] -name = "phf_codegen" -version = "0.8.0" +name = "rawpointer" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] -name = "phf_codegen" -version = "0.11.3" +name = "rayon" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "either", + "rayon-core", ] [[package]] -name = "phf_codegen" -version = "0.13.1" +name = "rayon-core" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "phf_generator" -version = "0.8.0" +name = "read-fonts" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", + "bytemuck", + "font-types", ] [[package]] -name = "phf_generator" -version = "0.10.0" +name = "realfft" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.6", + "rustfft", ] [[package]] -name = "phf_generator" -version = "0.11.3" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", + "bitflags 1.3.2", ] [[package]] -name = "phf_generator" -version = "0.13.1" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "fastrand", - "phf_shared 0.13.1", + "bitflags 2.11.1", ] [[package]] -name = "phf_macros" -version = "0.10.0" +name = "redox_syscall" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", + "bitflags 2.11.1", ] [[package]] -name = "phf_macros" -version = "0.11.3" +name = "redox_users" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", ] [[package]] -name = "phf_macros" -version = "0.13.1" +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", - "syn 2.0.117", + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", ] [[package]] -name = "phf_shared" -version = "0.8.0" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ - "siphasher 0.3.11", + "ref-cast-impl", ] [[package]] -name = "phf_shared" -version = "0.10.0" +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "siphasher 0.3.11", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "phf_shared" -version = "0.11.3" +name = "referencing" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "a2d5554bf79f4acf770dc3193b44b2d63b348f5f7b7448a0ea1191b37b620728" dependencies = [ - "siphasher 1.0.2", - "uncased", + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", ] [[package]] -name = "phf_shared" -version = "0.12.1" +name = "regex" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ - "siphasher 1.0.2", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "phf_shared" -version = "0.13.1" +name = "regex-automata" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ - "siphasher 1.0.2", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "pico-args" -version = "0.5.0" +name = "regex-lite" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] -name = "pin-project" -version = "1.1.11" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" -dependencies = [ - "pin-project-internal", -] +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "pin-project-internal" -version = "1.1.11" +name = "regress" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "hashbrown 0.16.1", + "memchr", ] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "relative-path" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0" +dependencies = [ + "serde", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "reqwest" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-rustls 0.27.9", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.38", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower 0.5.3", + "tower-http 0.6.8", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", + "webpki-roots 1.0.7", +] [[package]] -name = "piper" -version = "0.2.5" +name = "reqwest" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-rustls 0.27.9", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.38", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.4", + "tokio-util", + "tower 0.5.3", + "tower-http 0.6.8", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", ] [[package]] -name = "pipewire" -version = "0.9.2" +name = "reqwest-eventsource" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" dependencies = [ - "anyhow", - "bitflags 2.11.1", - "libc", - "libspa", - "libspa-sys", - "nix 0.30.1", - "once_cell", - "pipewire-sys", - "thiserror 2.0.18", + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom 7.1.3", + "pin-project-lite", + "reqwest 0.12.28", + "thiserror 1.0.69", ] [[package]] -name = "pipewire-sys" -version = "0.9.2" +name = "reqwest-middleware" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" +checksum = "199dda04a536b532d0cc04d7979e39b1c763ea749bf91507017069c00b96056f" dependencies = [ - "bindgen 0.72.1", - "libspa-sys", - "system-deps 7.0.8", + "anyhow", + "async-trait", + "http 1.4.0", + "reqwest 0.13.2", + "serde", + "thiserror 2.0.18", + "tower-service", ] [[package]] -name = "pkcs1" -version = "0.7.5" +name = "reqwest-tracing" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "3bce5a3624f12d0f1eb94d352abdef80902e29a612c40d26147f0e236d049352" dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", + "anyhow", + "async-trait", + "getrandom 0.2.17", + "http 1.4.0", + "matchit 0.8.4", + "reqwest 0.13.2", + "reqwest-middleware", + "tracing", ] [[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +name = "resampler" +version = "0.0.1" dependencies = [ - "der 0.6.1", - "spki 0.6.0", + "audio-interface", + "audioadapter-buffers", + "dasp", + "data", + "futures-util", + "hound", + "pin-project", + "rodio", + "rubato", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "resvg" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" dependencies = [ - "der 0.7.10", - "spki 0.7.3", + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg 0.4.21", ] [[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plain" -version = "0.2.3" +name = "rfc6979" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] [[package]] -name = "plist" -version = "1.8.0" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml 0.38.4", - "serde", - "time", + "hmac", + "subtle", ] [[package]] -name = "plotters" -version = "0.3.7" +name = "rfd" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", + "windows-sys 0.60.2", ] [[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" +name = "rgb" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ - "plotters-backend", + "bytemuck", ] [[package]] -name = "png" -version = "0.17.16" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "png" -version = "0.18.1" +name = "ringbuf" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" dependencies = [ - "bitflags 2.11.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "crossbeam-utils", + "portable-atomic", + "portable-atomic-util", ] [[package]] -name = "polling" -version = "3.11.0" +name = "rodio" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", + "cpal", + "dasp_sample", + "num-rational", + "rand 0.10.1", + "rand_distr 0.6.0", + "rtrb", + "symphonia", + "thiserror 2.0.18", ] [[package]] -name = "poly1305" -version = "0.8.0" +name = "roman-numerals-rs" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "porkbun" -version = "0.1.0" -dependencies = [ - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "url", -] +checksum = "c85cd47a33a4510b1424fe796498e174c6a9cf94e606460ef022a19f3e4ff85e" [[package]] -name = "port-killer" -version = "0.1.0" +name = "roxmltree" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8f8abaf06419461fc718f5a0929c18d06dd825f26c066e376f365f2d8c2ec2" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] -name = "port_check" -version = "0.3.0" +name = "rquickjs" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2749dcd0984ec1be3c01001bb1d83623a58c3c0049a99b9afec61464fa98e7" +checksum = "a135375fbac5ba723bb6a48f432a72f81539cedde422f0121a86c7c4e96d8e0d" +dependencies = [ + "rquickjs-core 0.10.0", +] [[package]] -name = "portable-atomic" -version = "1.13.1" +name = "rquickjs" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "c50dc6d6c587c339edb4769cf705867497a2baf0eca8b4645fa6ecd22f02c77a" +dependencies = [ + "either", + "indexmap 2.14.0", + "rquickjs-core 0.11.0", + "rquickjs-macro", +] [[package]] -name = "portable-atomic-util" -version = "0.2.7" +name = "rquickjs-core" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "bccb7121a123865c8ace4dea42e7ed84d78b90cbaf4ca32c59849d8d210c9672" dependencies = [ - "portable-atomic", + "hashbrown 0.16.1", + "relative-path", + "rquickjs-sys 0.10.0", ] [[package]] -name = "portable-pty" -version = "0.9.0" +name = "rquickjs-core" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1" dependencies = [ - "anyhow", - "bitflags 1.3.2", - "downcast-rs 1.2.1", - "filedescriptor", - "lazy_static", - "libc", - "log", - "nix 0.28.0", - "serial2", - "shared_library", - "shell-words", - "winapi", - "winreg 0.10.1", + "async-lock", + "chrono", + "dlopen2", + "either", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "phf 0.13.1", + "relative-path", + "rquickjs-sys 0.11.0", ] [[package]] -name = "postcard" -version = "1.1.3" +name = "rquickjs-macro" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +checksum = "7106215ff41a5677b104906a13e1a440b880f4b6362b5dc4f3978c267fad2b80" dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", + "convert_case 0.10.0", + "fnv", + "ident_case", + "indexmap 2.14.0", + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "rquickjs-core 0.11.0", + "syn 2.0.117", ] [[package]] -name = "posthog-rs" -version = "0.5.1" +name = "rquickjs-sys" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3862eb754386a1273612a885e961b19be0bb08e6d99e483557af41236d0dac12" +checksum = "57b1b6528590d4d65dc86b5159eae2d0219709546644c66408b2441696d1d725" dependencies = [ - "chrono", - "derive_builder", - "regex", - "reqwest 0.13.2", - "semver", - "serde", - "serde_json", - "sha1", - "tokio", - "tracing", - "uuid", + "cc", ] [[package]] -name = "potential_utf" -version = "0.1.5" +name = "rquickjs-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "27344601ef27460e82d6a4e1ecb9e7e99f518122095f3c51296da8e9be2b9d83" dependencies = [ - "zerovec 0.11.6", + "cc", ] [[package]] -name = "power" -version = "0.1.0" +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "cidre", - "thiserror 2.0.18", - "windows 0.62.2", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "rtrb" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "7204ed6420f698836b76d4d5c2ec5dec7585fd5c3a788fd1cde855d1de598239" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "rubato" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "90173154a8a14e6adb109ea641743bc95ec81c093d94e70c6763565f7108ebeb" dependencies = [ - "zerocopy 0.8.48", + "audioadapter", + "audioadapter-buffers", + "num-complex", + "num-integer", + "num-traits", + "realfft", + "visibility", + "windowfunctions", ] [[package]] -name = "precomputed-hash" -version = "0.1.1" +name = "rust-ini" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] [[package]] -name = "predicates" -version = "3.1.4" +name = "rust-stemmers" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" dependencies = [ - "anstyle", - "difflib", - "float-cmp 0.10.0", - "normalize-line-endings", - "predicates-core", - "regex", + "serde", + "serde_derive", ] [[package]] -name = "predicates-core" -version = "1.0.10" +name = "rust_decimal" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +dependencies = [ + "arrayvec", + "num-traits", +] [[package]] -name = "predicates-tree" -version = "1.0.13" +name = "rustc-demangle" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] -name = "prettyplease" -version = "0.2.37" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "proc-macro2", - "syn 2.0.117", + "semver", ] [[package]] -name = "primal-check" -version = "0.3.4" +name = "rustfft" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" dependencies = [ + "num-complex", "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", ] [[package]] -name = "primeorder" -version = "0.13.6" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "elliptic-curve 0.13.8", + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] -name = "proc-macro-crate" -version = "2.0.2" +name = "rustls" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "rustls" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "rustls" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.12", + "subtle", + "zeroize", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "rustls-native-certs" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "openssl-probe 0.1.6", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", ] [[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" +name = "rustls-native-certs" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "rustls-pemfile" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "unicode-ident", + "rustls-pki-types", ] [[package]] -name = "procfs-core" -version = "0.16.0" +name = "rustls-pki-types" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "bitflags 2.11.1", - "hex", + "web-time", + "zeroize", ] [[package]] -name = "prodash" -version = "29.0.2" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", "log", - "parking_lot", + "once_cell", + "rustls 0.23.38", + "rustls-native-certs 0.8.3", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.12", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] [[package]] -name = "prodash" -version = "30.0.1" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" -dependencies = [ - "parking_lot", -] +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] -name = "progenitor" -version = "0.12.0" +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc8cf6196a0139ab7b833b500f7f1acd005c91be0fe27a9e20112bf83dc9535" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "progenitor-client 0.12.0", - "progenitor-impl", - "progenitor-macro", + "ring", + "untrusted", ] [[package]] -name = "progenitor-client" -version = "0.12.0" +name = "rustls-webpki" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffab7b358944dba033a7b324e7558e66e6bcb1fb4705cf57f26fd5092bcae630" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "bytes", - "futures-core", - "percent-encoding", - "reqwest 0.13.2", - "serde", - "serde_json", - "serde_urlencoded", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "progenitor-client" -version = "0.13.0" +name = "rustls-webpki" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3999c302f5f2a42b7ca1cc39ad9e612c74cf2910ef6e58f869e45f3068b9659f" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ - "bytes", - "futures-core", - "percent-encoding", - "reqwest 0.13.2", - "serde", - "serde_json", - "serde_urlencoded", + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "progenitor-impl" -version = "0.12.0" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea21f106f8345d4417f3a39c90e4ff826ea777cb51579d72165d380a4d6f685d" -dependencies = [ - "heck 0.5.0", - "http 1.4.0", - "indexmap 2.14.0", - "openapiv3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "serde", - "serde_json", - "syn 2.0.117", - "thiserror 2.0.18", - "typify", - "unicode-ident", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "progenitor-macro" -version = "0.12.0" +name = "rustybuzz" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16f9cab95e07c5e6995db8d69d86e7472688b3deedb23e52631e398fddc2470" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "openapiv3", - "proc-macro2", - "progenitor-impl", - "quote", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_tokenstream", - "serde_yaml", - "syn 2.0.117", + "bitflags 2.11.1", + "bytemuck", + "core_maths", + "log", + "smallvec 1.15.1", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", ] [[package]] -name = "progenitor-utils" -version = "0.1.0" -dependencies = [ - "openapi31to30", - "openapiv3", - "prettyplease", - "progenitor", - "serde_json", - "serde_yaml", - "syn 2.0.117", -] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "prometheus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +name = "s3" +version = "0.1.0" dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "memchr", - "parking_lot", + "aws-config", + "aws-credential-types", + "aws-sdk-s3", + "testcontainers-modules", "thiserror 2.0.18", + "tokio", ] [[package]] -name = "prost" -version = "0.12.6" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "bytes", - "prost-derive 0.12.6", + "winapi-util", ] [[package]] -name = "prost" -version = "0.13.5" +name = "sanitize-filename" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" dependencies = [ - "bytes", - "prost-derive 0.13.5", + "regex", ] [[package]] -name = "prost-build" -version = "0.13.5" +name = "scc" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ - "heck 0.5.0", - "itertools 0.14.0", - "log", - "multimap", - "once_cell", - "petgraph 0.7.1", - "prettyplease", - "prost 0.13.5", - "prost-types", - "regex", - "syn 2.0.117", - "tempfile", + "sdd", ] [[package]] -name = "prost-derive" -version = "0.12.6" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.117", + "windows-sys 0.61.2", ] [[package]] -name = "prost-derive" -version = "0.13.5" +name = "schemars" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "chrono", + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive 0.8.22", + "serde", + "serde_json", + "url", + "uuid", ] [[package]] -name = "prost-types" -version = "0.13.5" +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "prost 0.13.5", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] -name = "psl-types" -version = "2.0.11" +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "psm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "ar_archive_writer", - "cc", + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive 1.2.1", + "serde", + "serde_json", ] [[package]] -name = "publicsuffix" -version = "2.3.0" +name = "schemars_derive" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ - "idna", - "psl-types", + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", ] [[package]] -name = "pulldown-cmark" -version = "0.13.3" +name = "schemars_derive" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ - "bitflags 2.11.1", - "memchr", - "unicase", + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", ] [[package]] -name = "pxfm" -version = "0.1.29" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "pyannote-cloud" -version = "0.1.0" -dependencies = [ - "chrono", - "progenitor-client 0.13.0", - "progenitor-utils", - "regress", - "reqwest 0.13.2", - "serde", - "serde_json", - "tokio", - "utoipa", - "wiremock", -] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "pyannote-local" -version = "0.1.0" +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ - "approx", - "dasp", - "data", - "knf-rs", - "onnx", - "rodio", - "serde", - "simsimd", - "specta", - "thiserror 2.0.18", + "scroll_derive", ] [[package]] -name = "qcms" -version = "0.3.0" +name = "scroll_derive" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "quanta" -version = "0.12.6" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.1+wasi-snapshot-preview1", - "web-sys", - "winapi", + "ring", + "untrusted", ] [[package]] -name = "quick-error" -version = "2.0.1" +name = "sdd" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] -name = "quick-xml" -version = "0.30.0" +name = "sec1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "memchr", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", ] [[package]] -name = "quick-xml" -version = "0.38.4" +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "memchr", - "serde", + "base16ct 0.2.0", + "der 0.7.10", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", ] [[package]] -name = "quick-xml" -version = "0.39.2" +name = "secrecy" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ - "memchr", + "serde", + "zeroize", ] [[package]] -name = "quickcheck" -version = "1.1.0" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c589f335db0f6aaa168a7cd27b1fc6920f5e1470c804f814d9cd6e62a0f70b" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "env_logger", - "log", - "rand 0.10.1", + "bitflags 2.11.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "quickcheck_macros" -version = "1.2.0" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a28b8493dd664c8b171dd944da82d933f7d456b829bfb236738e1fe06c5ba4" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "quinn" -version = "0.11.9" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ - "bytes", - "cfg_aliases 0.2.1", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.2", - "rustls 0.23.38", - "socket2 0.6.3", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", + "core-foundation-sys", + "libc", ] [[package]] -name = "quinn-proto" -version = "0.11.14" +name = "selectors" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.4", - "ring", - "rustc-hash 2.1.2", - "rustls 0.23.38", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec 1.15.1", ] [[package]] -name = "quinn-udp" -version = "0.5.14" +name = "selectors" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "cfg_aliases 0.2.1", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", + "bitflags 2.11.1", + "cssparser 0.36.0", + "derive_more 2.1.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash 2.1.2", + "servo_arc 0.4.3", + "smallvec 1.15.1", ] [[package]] -name = "quote" -version = "1.0.45" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ - "proc-macro2", + "serde", + "serde_core", ] [[package]] -name = "quoted_printable" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" - -[[package]] -name = "r-efi" -version = "5.3.0" +name = "sentry" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "ractor" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a64ac8ba2e8d71b25c55ab7acafc481ae4c9175f3ee8f7c36b66c4cad369bb5" +checksum = "989425268ab5c011e06400187eed6c298272f8ef913e49fcadc3fda788b45030" dependencies = [ - "async-trait", - "bon 2.3.0", - "dashmap", - "futures", - "js-sys", - "once_cell", - "strum 0.26.3", + "httpdate", + "native-tls", + "reqwest 0.12.28", + "sentry-actix", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", "tokio", - "tokio_with_wasm", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-time", + "ureq", ] [[package]] -name = "rand" -version = "0.7.3" +name = "sentry-actix" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "a5c675bdf6118764a8e265c3395c311b4d905d12866c92df52870c0223d2ffc1" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", + "actix-http", + "actix-web", + "bytes", + "futures-util", + "sentry-core", ] [[package]] -name = "rand" -version = "0.8.6" +name = "sentry-backtrace" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "68e299dd3f7bcf676875eee852c9941e1d08278a743c32ca528e2debf846a653" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "backtrace", + "regex", + "sentry-core", ] [[package]] -name = "rand" -version = "0.9.4" +name = "sentry-contexts" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "fac0c5d6892cd4c414492fc957477b620026fb3411fca9fa12774831da561c88" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", ] [[package]] -name = "rand" -version = "0.10.1" +name = "sentry-core" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc" dependencies = [ - "chacha20 0.10.0", - "getrandom 0.4.2", - "rand_core 0.10.1", + "rand 0.9.4", + "sentry-types", + "serde", + "serde_json", + "url", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "sentry-debug-images" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "00950648aa0d371c7f57057434ad5671bd4c106390df7e7284739330786a01b6" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "findshlibs", + "sentry-core", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "sentry-panic" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "2b7a23b13c004873de3ce7db86eb0f59fe4adfc655a31f7bbc17fd10bacc9bfe" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "sentry-backtrace", + "sentry-core", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "sentry-rust-minidump" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "63964525bf74b16233dbcfb307e11485ebd8ff8f87f6ae212b07ca7937cd2db1" dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "minidumper-child", + "sentry", + "thiserror 2.0.18", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "sentry-tracing" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" dependencies = [ - "getrandom 0.1.16", + "bitflags 2.11.1", + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "sentry-types" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412" dependencies = [ - "getrandom 0.2.17", + "debugid", + "hex", + "rand 0.9.4", + "serde", + "serde_json", + "thiserror 2.0.18", + "time", + "url", + "uuid", ] [[package]] -name = "rand_core" -version = "0.9.5" +name = "seq-macro" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] -name = "rand_core" -version = "0.10.1" +name = "sequential-macro" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +checksum = "eb5facc5f409a55d25bf271c853402a00e1187097d326757043f5dd711944d07" [[package]] -name = "rand_distr" -version = "0.4.3" +name = "sequential-test" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "f0d9c0d773bc7e7733264f460e5dfa00b2510421ddd6284db0749eef8dfb79e9" dependencies = [ - "num-traits", - "rand 0.8.6", + "sequential-macro", ] [[package]] -name = "rand_distr" -version = "0.6.0" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "num-traits", - "rand 0.10.1", + "serde_core", + "serde_derive", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "serde-untagged" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" dependencies = [ - "rand_core 0.5.1", + "erased-serde", + "serde", + "serde_core", + "typeid", ] [[package]] -name = "rand_pcg" -version = "0.2.1" +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ - "rand_core 0.5.1", + "serde", + "serde_core", ] [[package]] -name = "range-map" -version = "0.2.0" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "num-traits", + "serde_derive", ] [[package]] -name = "ratatui" -version = "0.30.0" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "instability", - "ratatui-core", - "ratatui-crossterm", - "ratatui-macros", - "ratatui-termwiz", - "ratatui-widgets", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "ratatui-core" -version = "0.1.0" +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "bitflags 2.11.1", - "compact_str", - "hashbrown 0.16.1", - "indoc", - "itertools 0.14.0", - "kasuari", - "lru 0.16.4", - "strum 0.27.2", - "thiserror 2.0.18", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.2", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "ratatui-crossterm" -version = "0.1.0" +name = "serde_html_form" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" dependencies = [ - "cfg-if", - "crossterm", - "instability", - "ratatui-core", + "form_urlencoded", + "indexmap 2.14.0", + "itoa", + "ryu", + "serde_core", ] [[package]] -name = "ratatui-macros" -version = "0.7.0" +name = "serde_html_form" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" dependencies = [ - "ratatui-core", - "ratatui-widgets", + "form_urlencoded", + "indexmap 2.14.0", + "itoa", + "ryu", + "serde_core", ] [[package]] -name = "ratatui-termwiz" -version = "0.1.0" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "ratatui-core", - "termwiz", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] -name = "ratatui-widgets" -version = "0.3.0" +name = "serde_path_to_error" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.16.1", - "indoc", - "instability", - "itertools 0.14.0", - "line-clipping", - "ratatui-core", - "strum 0.27.2", - "time", - "unicode-segmentation", - "unicode-width 0.2.2", + "itoa", + "serde", + "serde_core", ] [[package]] -name = "raw-cpuid" -version = "11.6.0" +name = "serde_qs" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240" dependencies = [ - "bitflags 2.11.1", + "itoa", + "percent-encoding", + "ryu", + "serde", ] [[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.12.0" +name = "serde_repr" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ - "either", - "rayon-core", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "rayon-core" -version = "1.13.0" +name = "serde_spanned" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "serde", ] [[package]] -name = "read-fonts" -version = "0.35.0" +name = "serde_spanned" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "bytemuck", - "font-types", + "serde_core", ] [[package]] -name = "realfft" -version = "3.5.0" +name = "serde_tokenstream" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +checksum = "d7c49585c52c01f13c5c2ebb333f14f6885d76daa768d8a037d28017ec538c69" dependencies = [ - "rustfft", + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", ] [[package]] -name = "recall" -version = "0.1.0" +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "reqwest 0.13.2", + "form_urlencoded", + "itoa", + "ryu", "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "serde_with" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ - "bitflags 1.3.2", + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "serde_with_macros" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "bitflags 2.11.1", + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "redox_syscall" -version = "0.7.4" +name = "serde_yaml" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "bitflags 2.11.1", + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] -name = "redox_users" -version = "0.4.6" +name = "serial_test" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", ] [[package]] -name = "redox_users" -version = "0.5.2" +name = "serial_test_derive" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "ref-cast" -version = "1.0.25" +name = "serialize-to-javascript" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" dependencies = [ - "ref-cast-impl", + "serde", + "serde_json", + "serialize-to-javascript-impl", ] [[package]] -name = "ref-cast-impl" -version = "1.0.25" +name = "serialize-to-javascript-impl" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", @@ -15264,1006 +14326,841 @@ dependencies = [ ] [[package]] -name = "referencing" -version = "0.46.0" +name = "servo_arc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5554bf79f4acf770dc3193b44b2d63b348f5f7b7448a0ea1191b37b620728" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ - "ahash", - "fluent-uri", - "getrandom 0.3.4", - "hashbrown 0.16.1", - "itoa", - "micromap", - "parking_lot", - "percent-encoding", - "serde_json", + "nodrop", + "stable_deref_trait", ] [[package]] -name = "regex" -version = "1.12.3" +name = "servo_arc" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "stable_deref_trait", ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "cfg-if", + "cpufeatures 0.2.17", + "digest", ] [[package]] -name = "regex-lite" -version = "0.1.9" +name = "sha1-checked" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] [[package]] -name = "regex-syntax" -version = "0.8.10" +name = "sha1_smol" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] -name = "regress" -version = "0.10.5" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "hashbrown 0.16.1", - "memchr", + "cfg-if", + "cpufeatures 0.2.17", + "digest", ] [[package]] -name = "relative-path" -version = "2.0.1" +name = "sha256" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" dependencies = [ - "serde", + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", ] [[package]] -name = "reqwest" -version = "0.12.28" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "base64 0.22.1", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.9", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "mime_guess", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.38", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tokio-native-tls", - "tokio-rustls 0.26.4", - "tokio-util", - "tower 0.5.3", - "tower-http 0.6.8", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", - "webpki-roots 1.0.7", + "lazy_static", ] [[package]] -name = "reqwest" -version = "0.13.2" +name = "shared_child" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.9", - "hyper-util", - "js-sys", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.38", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tokio-rustls 0.26.4", - "tokio-util", - "tower 0.5.3", - "tower-http 0.6.8", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.5.0", - "web-sys", + "libc", + "sigchld", + "windows-sys 0.60.2", ] [[package]] -name = "reqwest-eventsource" -version = "0.6.0" +name = "shell-words" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" -dependencies = [ - "eventsource-stream", - "futures-core", - "futures-timer", - "mime", - "nom 7.1.3", - "pin-project-lite", - "reqwest 0.12.28", - "thiserror 1.0.69", -] +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] -name = "reqwest-middleware" -version = "0.5.1" +name = "shellexpand" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199dda04a536b532d0cc04d7979e39b1c763ea749bf91507017069c00b96056f" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "anyhow", - "async-trait", - "http 1.4.0", - "reqwest 0.13.2", - "serde", - "thiserror 2.0.18", - "tower-service", + "dirs 6.0.0", ] [[package]] -name = "reqwest-tracing" -version = "0.6.0" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bce5a3624f12d0f1eb94d352abdef80902e29a612c40d26147f0e236d049352" -dependencies = [ - "anyhow", - "async-trait", - "getrandom 0.2.17", - "http 1.4.0", - "matchit 0.8.4", - "reqwest 0.13.2", - "reqwest-middleware", - "tracing", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "resampler" -version = "0.0.1" +name = "shortcut-macos" +version = "0.1.0" dependencies = [ - "audio-interface", - "audioadapter-buffers", - "dasp", - "data", - "futures-util", - "hound", - "pin-project", - "rodio", - "rubato", + "serde", + "specta", "thiserror 2.0.18", - "tokio", + "tracing", ] [[package]] -name = "resvg" -version = "0.45.1" +name = "sigchld" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ - "gif 0.13.3", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", - "zune-jpeg 0.4.21", + "libc", + "os_pipe", + "signal-hook 0.3.18", ] [[package]] -name = "rfc6979" -version = "0.3.1" +name = "signal-hook" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ - "crypto-bigint 0.4.9", - "hmac 0.12.1", - "zeroize", + "libc", + "signal-hook-registry", ] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "signal-hook" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" dependencies = [ - "hmac 0.12.1", - "subtle", + "libc", + "signal-hook-registry", ] [[package]] -name = "rfd" -version = "0.16.0" +name = "signal-hook-mio" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ - "block2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.60.2", + "libc", + "mio", + "signal-hook 0.3.18", ] [[package]] -name = "rgb" -version = "0.8.53" +name = "signal-hook-registry" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "bytemuck", + "errno", + "libc", ] [[package]] -name = "ring" -version = "0.17.14" +name = "signature" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", + "digest", + "rand_core 0.6.4", ] [[package]] -name = "ringbuf" -version = "0.4.8" +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "crossbeam-utils", - "portable-atomic", - "portable-atomic-util", + "digest", + "rand_core 0.6.4", ] [[package]] -name = "rmcp" -version = "1.5.0" +name = "simd-adler32" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67d69668de0b0ccd9cc435f700f3b39a7861863cf37a15e1f304ea78688a4826" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bytes", - "chrono", - "futures", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "pastey", - "pin-project-lite", - "rand 0.10.1", - "rmcp-macros", - "schemars 1.2.1", - "serde", - "serde_json", - "sse-stream", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "tower-service", - "tracing", - "url", - "uuid", -] +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "rmcp-macros" -version = "1.5.0" +name = "similar" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fdc01c81097b0aed18633e676e269fefa3a78ec1df56b4fe597c1241b92025" -dependencies = [ - "darling 0.23.0", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.117", -] +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] -name = "rodio" -version = "0.22.2" +name = "simple_asn1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ - "cpal", - "dasp_sample", - "num-rational", - "rand 0.10.1", - "rand_distr 0.6.0", - "rtrb", - "symphonia", + "num-bigint", + "num-traits", "thiserror 2.0.18", + "time", ] [[package]] -name = "roman-numerals-rs" -version = "3.1.0" +name = "simplecss" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85cd47a33a4510b1424fe796498e174c6a9cf94e606460ef022a19f3e4ff85e" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] [[package]] -name = "ron" -version = "0.12.1" +name = "simsimd" +version = "6.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" +checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" dependencies = [ - "bitflags 2.11.1", - "indexmap 2.14.0", - "once_cell", - "serde", - "serde_derive", - "typeid", - "unicode-ident", + "cc", ] [[package]] -name = "roxmltree" -version = "0.20.0" +name = "siphasher" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] -name = "rquickjs" -version = "0.10.0" +name = "siphasher" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a135375fbac5ba723bb6a48f432a72f81539cedde422f0121a86c7c4e96d8e0d" -dependencies = [ - "rquickjs-core 0.10.0", -] +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] -name = "rquickjs" -version = "0.11.0" +name = "sketches-ddsketch" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50dc6d6c587c339edb4769cf705867497a2baf0eca8b4645fa6ecd22f02c77a" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" dependencies = [ - "either", - "indexmap 2.14.0", - "rquickjs-core 0.11.0", - "rquickjs-macro", + "serde", ] [[package]] -name = "rquickjs-core" -version = "0.10.0" +name = "skrifa" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bccb7121a123865c8ace4dea42e7ed84d78b90cbaf4ca32c59849d8d210c9672" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" dependencies = [ - "hashbrown 0.16.1", - "relative-path", - "rquickjs-sys 0.10.0", + "bytemuck", + "read-fonts", ] [[package]] -name = "rquickjs-core" -version = "0.11.0" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1" -dependencies = [ - "async-lock", - "chrono", - "dlopen2", - "either", - "hashbrown 0.16.1", - "indexmap 2.14.0", - "phf 0.13.1", - "relative-path", - "rquickjs-sys 0.11.0", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "rquickjs-macro" -version = "0.11.0" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7106215ff41a5677b104906a13e1a440b880f4b6362b5dc4f3978c267fad2b80" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ - "convert_case 0.10.0", - "fnv", - "ident_case", - "indexmap 2.14.0", - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "rquickjs-core 0.11.0", - "syn 2.0.117", + "version_check", ] [[package]] -name = "rquickjs-sys" -version = "0.10.0" +name = "slug" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b1b6528590d4d65dc86b5159eae2d0219709546644c66408b2441696d1d725" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ - "cc", + "deunicode", + "wasm-bindgen", ] [[package]] -name = "rquickjs-sys" -version = "0.11.0" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27344601ef27460e82d6a4e1ecb9e7e99f518122095f3c51296da8e9be2b9d83" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ - "cc", + "serde", ] [[package]] -name = "rsa" -version = "0.9.10" +name = "smallvec" +version = "2.0.0-alpha.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] +checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b" [[package]] -name = "rtrb" -version = "0.3.3" +name = "smart-default" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7204ed6420f698836b76d4d5c2ec5dec7585fd5c3a788fd1cde855d1de598239" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "rubato" +name = "smartstring" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90173154a8a14e6adb109ea641743bc95ec81c093d94e70c6763565f7108ebeb" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ - "audioadapter", - "audioadapter-buffers", - "num-complex", - "num-integer", - "num-traits", - "realfft", - "visibility", - "windowfunctions", + "autocfg", + "static_assertions", + "version_check", ] [[package]] -name = "rumqttc" -version = "0.25.1" +name = "socket2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0feff8d882bff0b2fddaf99355a10336d43dd3ed44204f85ece28cf9626ab519" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ - "bytes", - "fixedbitset 0.5.7", - "flume", - "futures-util", - "log", - "rustls-native-certs 0.8.3", - "rustls-pemfile", - "rustls-webpki 0.102.8", - "thiserror 2.0.18", - "tokio", - "tokio-rustls 0.26.4", - "tokio-stream", - "tokio-util", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "rusqlite" -version = "0.37.0" +name = "socket2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ - "bitflags 2.11.1", - "fallible-iterator 0.3.0", - "fallible-streaming-iterator", - "hashlink 0.10.0", - "libsqlite3-sys", - "smallvec 1.15.1", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "rust-ini" -version = "0.21.3" +name = "socks" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" dependencies = [ - "cfg-if", - "ordered-multimap", + "byteorder", + "libc", + "winapi", ] [[package]] -name = "rust-stemmers" -version = "1.2.0" +name = "softbuffer" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "rust_decimal" -version = "1.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" -dependencies = [ - "arrayvec", - "num-traits", + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.18", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", ] [[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +name = "soniox" +version = "0.1.0" dependencies = [ - "semver", + "observability", + "reqwest 0.13.2", + "serde", + "serde_json", + "tokio", ] [[package]] -name = "rustfft" -version = "6.4.1" +name = "soup3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" dependencies = [ - "num-complex", - "num-integer", - "num-traits", - "primal-check", - "strength_reduce", - "transpose", + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", ] [[package]] -name = "rustix" -version = "0.38.44" +name = "soup3-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" dependencies = [ - "bitflags 2.11.1", - "errno", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "system-deps 6.2.2", ] [[package]] -name = "rustix" -version = "1.1.4" +name = "specta" +version = "2.0.0-rc.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971" dependencies = [ - "bitflags 2.11.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "chrono", + "paste", + "serde_json", + "specta-macros", + "thiserror 1.0.69", ] [[package]] -name = "rustls" -version = "0.21.12" +name = "specta-macros" +version = "2.0.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517" dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "rustls" -version = "0.22.4" +name = "specta-serde" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63" dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", + "specta", + "thiserror 1.0.69", ] [[package]] -name = "rustls" -version = "0.23.38" +name = "specta-typescript" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d" dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki 0.103.12", - "subtle", - "zeroize", + "specta", + "specta-serde", + "thiserror 1.0.69", ] [[package]] -name = "rustls-native-certs" -version = "0.7.3" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", + "lock_api", ] [[package]] -name = "rustls-native-certs" -version = "0.8.3" +name = "spki" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ - "openssl-probe 0.2.1", - "rustls-pki-types", - "schannel", - "security-framework 3.7.0", + "base64ct", + "der 0.6.1", ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "rustls-pki-types", + "base64ct", + "der 0.7.10", ] [[package]] -name = "rustls-pki-types" -version = "1.14.0" +name = "sqlx" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "decccfa5f2f3eac95eb68085cfe69a0172fa9711666c3a634cfc806d4fb74a47" dependencies = [ - "web-time", - "zeroize", + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] -name = "rustls-platform-verifier" -version = "0.6.2" +name = "sqlx-core" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "86854e8c6aba0dafcf1c04b4836b0b7fa3a20c560e3554567afefe1258fa4e60" dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", + "base64 0.22.1", + "bytes", + "cfg-if", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.16.1", + "hashlink 0.10.0", + "indexmap 2.14.0", "log", - "once_cell", - "rustls 0.23.38", - "rustls-native-certs 0.8.3", - "rustls-platform-verifier-android", - "rustls-webpki 0.103.12", - "security-framework 3.7.0", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", + "memchr", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec 1.15.1", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", ] [[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "sqlx-macros" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "d7aab9442ed1568e3aed6c368737226ee4e0e8d1deb0e0887fa6bf15282ace44" dependencies = [ - "ring", - "untrusted", + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", ] [[package]] -name = "rustls-webpki" -version = "0.102.8" +name = "sqlx-macros-core" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "34eb4976b8f02ac57ee98d4ce40cd1aad7ab31d9792977bc3171f787ba6ba2fb" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "cfg-if", + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-sqlite", + "syn 2.0.117", + "thiserror 2.0.18", + "tokio", + "url", ] [[package]] -name = "rustls-webpki" -version = "0.103.12" +name = "sqlx-mysql" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "6fef16f3d52a3710a672b48175b713e86476e2df85576a753c8b37ad11a483c0" dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", + "atoi", + "base64 0.22.1", + "bitflags 2.11.1", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "percent-encoding", + "rand 0.8.6", + "rsa", + "sha1", + "sha2", + "smallvec 1.15.1", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "sqlx-postgres" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "f053cf36ecb2793a9d9bb02d01bbad1ef66481d5db6ff5ab2dfb7b070cc0d13c" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.11.1", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "rand 0.8.6", + "serde", + "serde_json", + "sha2", + "smallvec 1.15.1", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] [[package]] -name = "rustybuzz" -version = "0.20.1" +name = "sqlx-sqlite" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +checksum = "fe2cd6cee87120b1e1dd31356b5589911995c777707e49f2750eec7c7fe43eef" dependencies = [ - "bitflags 2.11.1", - "bytemuck", - "core_maths", + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", "log", - "smallvec 1.15.1", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", ] [[package]] -name = "ryu" -version = "1.0.23" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "s3" -version = "0.1.0" +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" dependencies = [ - "aws-config", - "aws-credential-types", - "aws-sdk-s3", - "testcontainers-modules", - "thiserror 2.0.18", - "tokio", + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", ] [[package]] -name = "same-file" -version = "1.0.6" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "storage" +version = "0.1.0" dependencies = [ - "winapi-util", + "dirs 6.0.0", + "serde", + "serde_json", + "shellexpand", + "specta", + "tempfile", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "sanitize-filename" -version = "0.6.0" +name = "str_inflector" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +checksum = "ec0b848d5a7695b33ad1be00f84a3c079fe85c9278a325ff9159e6c99cef4ef7" dependencies = [ + "lazy_static", "regex", ] [[package]] -name = "scc" -version = "2.4.0" +name = "strength_reduce" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" [[package]] -name = "schannel" -version = "0.1.29" +name = "strict-num" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ - "windows-sys 0.61.2", + "float-cmp 0.9.0", ] [[package]] -name = "schemars" -version = "0.8.22" +name = "string_cache" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ - "chrono", - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive 0.8.22", + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", "serde", - "serde_json", - "url", - "uuid", ] [[package]] -name = "schemars" +name = "string_cache" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ - "dyn-clone", - "ref-cast", + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", "serde", - "serde_json", ] [[package]] -name = "schemars" -version = "1.2.1" +name = "string_cache_codegen" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ - "chrono", - "dyn-clone", - "ref-cast", - "schemars_derive 1.2.1", - "serde", - "serde_json", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", ] [[package]] -name = "schemars_derive" -version = "0.8.22" +name = "string_cache_codegen" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "serde_derive_internals", - "syn 2.0.117", ] [[package]] -name = "schemars_derive" -version = "1.2.1" +name = "string_enum" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e" dependencies = [ - "proc-macro2", "quote", - "serde_derive_internals", + "swc_macros_common", "syn 2.0.117", ] [[package]] -name = "scoped-tls" -version = "1.0.1" +name = "stringprep" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "screen-core" -version = "0.1.0" -dependencies = [ - "image", - "thiserror 2.0.18", - "xcap", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "scroll" -version = "0.12.0" +name = "structmeta" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" dependencies = [ - "scroll_derive", + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.117", ] [[package]] -name = "scroll_derive" -version = "0.12.1" +name = "structmeta-derive" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", @@ -16271,2924 +15168,1288 @@ dependencies = [ ] [[package]] -name = "sct" -version = "0.7.1" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "ring", - "untrusted", + "strum_macros 0.26.4", ] [[package]] -name = "sdd" -version = "3.0.10" +name = "strum" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] [[package]] -name = "sec1" -version = "0.3.0" +name = "strum" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", + "strum_macros 0.28.0", ] [[package]] -name = "sec1" -version = "0.7.3" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "base16ct 0.2.0", - "der 0.7.10", - "generic-array", - "pkcs8 0.10.2", - "subtle", - "zeroize", + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", ] [[package]] -name = "secrecy" -version = "0.10.3" +name = "strum_macros" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "serde", - "zeroize", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "strum_macros" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "security-framework" -version = "3.7.0" +name = "subsetter" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +checksum = "cb6895a12ac5599bb6057362f00e8a3cf1daab4df33f553a55690a44e4fed8d0" dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", + "kurbo 0.12.0", + "rustc-hash 2.1.2", + "skrifa", + "write-fonts", ] [[package]] -name = "security-framework-sys" -version = "2.17.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "segmentation" +name = "supabase-auth" version = "0.1.0" dependencies = [ - "approx", - "criterion", - "data", - "hound", - "onnx", + "axum 0.8.9", + "backon", + "base64 0.22.1", + "chrono", + "jsonwebtoken", + "reqwest 0.13.2", "serde", "serde_json", + "specta", + "storage", "thiserror 2.0.18", + "tokio", + "wiremock", ] [[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +name = "supervisor" +version = "0.1.0" dependencies = [ - "bitflags 1.3.2", - "cssparser 0.29.6", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.2.0", - "smallvec 1.15.1", + "ractor", + "thiserror 2.0.18", + "tokio", + "tracing", ] [[package]] -name = "selectors" -version = "0.36.1" +name = "svgtypes" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ - "bitflags 2.11.1", - "cssparser 0.36.0", - "derive_more 2.1.1", - "log", - "new_debug_unreachable", - "phf 0.13.1", - "phf_codegen 0.13.1", - "precomputed-hash", - "rustc-hash 2.1.2", - "servo_arc 0.4.3", - "smallvec 1.15.1", + "kurbo 0.11.3", + "siphasher 1.0.2", ] [[package]] -name = "self_cell" -version = "1.2.2" +name = "swc_atoms" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" +checksum = "b40c2b43a19b5d0706aca8669ae5b77b92bd141f7f8ce5e980e0e52430f54b20" +dependencies = [ + "hstr", + "once_cell", + "serde", +] [[package]] -name = "semver" -version = "1.0.28" +name = "swc_common" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "09e51fecd32bb0989543f0a64f4103cbd728e375838be83d768ce6989f5ea631" dependencies = [ + "anyhow", + "ast_node", + "better_scoped_tls", + "bytes-str", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash 2.1.2", "serde", - "serde_core", + "siphasher 0.3.11", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width 0.1.14", + "url", ] [[package]] -name = "sentry" -version = "0.42.0" +name = "swc_ecma_ast" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989425268ab5c011e06400187eed6c298272f8ef913e49fcadc3fda788b45030" +checksum = "7da8bb0e5aaa6e077f178a28d29bc7da4a8ddaf012b3c21c043cb5f72a0b9779" dependencies = [ - "httpdate", - "native-tls", - "reqwest 0.12.28", - "sentry-actix", - "sentry-backtrace", - "sentry-contexts", - "sentry-core", - "sentry-debug-images", - "sentry-panic", - "sentry-tower", - "sentry-tracing", - "tokio", - "ureq", + "bitflags 2.11.1", + "is-macro", + "num-bigint", + "once_cell", + "phf 0.11.3", + "rustc-hash 2.1.2", + "string_enum", + "swc_atoms", + "swc_common", + "swc_visit", + "unicode-id-start", ] [[package]] -name = "sentry-actix" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c675bdf6118764a8e265c3395c311b4d905d12866c92df52870c0223d2ffc1" +name = "swc_ecma_parser" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac3281dd9eef03b877fe9cef75a4c8951ce6df0c5f381868f302ee3c58fa6e2" dependencies = [ - "actix-http", - "actix-web", - "bytes", - "futures-util", - "sentry-core", + "bitflags 2.11.1", + "either", + "num-bigint", + "phf 0.11.3", + "rustc-hash 2.1.2", + "seq-macro", + "serde", + "smartstring", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", ] [[package]] -name = "sentry-backtrace" -version = "0.42.0" +name = "swc_eq_ignore_macros" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e299dd3f7bcf676875eee852c9941e1d08278a743c32ca528e2debf846a653" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" dependencies = [ - "backtrace", - "regex", - "sentry-core", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "sentry-contexts" -version = "0.42.0" +name = "swc_macros_common" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac0c5d6892cd4c414492fc957477b620026fb3411fca9fa12774831da561c88" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" dependencies = [ - "hostname", - "libc", - "os_info", - "rustc_version", - "sentry-core", - "uname", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "sentry-core" -version = "0.42.0" +name = "swc_visit" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" dependencies = [ - "rand 0.9.4", - "sentry-types", - "serde", - "serde_json", - "url", + "either", + "new_debug_unreachable", ] [[package]] -name = "sentry-debug-images" -version = "0.42.0" +name = "swift-rs" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00950648aa0d371c7f57057434ad5671bd4c106390df7e7284739330786a01b6" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" dependencies = [ - "findshlibs", - "sentry-core", + "base64 0.21.7", + "serde", + "serde_json", ] [[package]] -name = "sentry-panic" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7a23b13c004873de3ce7db86eb0f59fe4adfc655a31f7bbc17fd10bacc9bfe" +name = "swift-rs" +version = "1.0.7" +source = "git+https://github.com/yujonglee/swift-rs?rev=41a1605#41a160524c737c061f5633e4371952544425aa01" dependencies = [ - "sentry-backtrace", - "sentry-core", + "base64 0.21.7", + "serde", + "serde_json", ] [[package]] -name = "sentry-rust-minidump" -version = "0.13.0" +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63964525bf74b16233dbcfb307e11485ebd8ff8f87f6ae212b07ca7937cd2db1" -dependencies = [ - "minidumper-child", - "sentry", - "thiserror 2.0.18", -] +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] -name = "sentry-tower" -version = "0.42.0" +name = "symphonia" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a303d0127d95ae928a937dcc0886931d28b4186e7338eea7d5786827b69b002" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" dependencies = [ - "axum 0.8.9", - "http 1.4.0", - "pin-project", - "sentry-core", - "tower-layer", - "tower-service", - "url", + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-caf", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", ] [[package]] -name = "sentry-tracing" -version = "0.42.0" +name = "symphonia-bundle-flac" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" dependencies = [ - "bitflags 2.11.1", - "sentry-backtrace", - "sentry-core", - "tracing-core", - "tracing-subscriber", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] -name = "sentry-types" -version = "0.42.0" +name = "symphonia-bundle-mp3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" dependencies = [ - "debugid", - "hex", - "rand 0.9.4", - "serde", - "serde_json", - "thiserror 2.0.18", - "time", - "url", - "uuid", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", ] [[package]] -name = "seq-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" - -[[package]] -name = "sequential-macro" -version = "0.1.4" +name = "symphonia-codec-aac" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5facc5f409a55d25bf271c853402a00e1187097d326757043f5dd711944d07" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] [[package]] -name = "sequential-test" -version = "0.2.4" +name = "symphonia-codec-adpcm" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d9c0d773bc7e7733264f460e5dfa00b2510421ddd6284db0749eef8dfb79e9" +checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" dependencies = [ - "sequential-macro", + "log", + "symphonia-core", ] [[package]] -name = "serde" -version = "1.0.228" +name = "symphonia-codec-alac" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" dependencies = [ - "serde_core", - "serde_derive", + "log", + "symphonia-core", ] [[package]] -name = "serde-untagged" -version = "0.1.9" +name = "symphonia-codec-pcm" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", + "log", + "symphonia-core", ] [[package]] -name = "serde_bytes" -version = "0.11.19" +name = "symphonia-codec-vorbis" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" dependencies = [ - "serde", - "serde_core", + "log", + "symphonia-core", + "symphonia-utils-xiph", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "symphonia-core" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" dependencies = [ - "serde_derive", + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "symphonia-format-caf" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "log", + "symphonia-core", + "symphonia-metadata", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "symphonia-format-isomp4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] -name = "serde_html_form" -version = "0.2.8" +name = "symphonia-format-mkv" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" dependencies = [ - "form_urlencoded", - "indexmap 2.14.0", - "itoa", - "ryu", - "serde_core", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] -name = "serde_html_form" -version = "0.4.0" +name = "symphonia-format-ogg" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" +checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" dependencies = [ - "form_urlencoded", - "indexmap 2.14.0", - "itoa", - "ryu", - "serde_core", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "symphonia-format-riff" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" dependencies = [ - "indexmap 2.14.0", - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "extended", + "log", + "symphonia-core", + "symphonia-metadata", ] [[package]] -name = "serde_path_to_error" -version = "0.1.20" +name = "symphonia-metadata" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" dependencies = [ - "itoa", - "serde", - "serde_core", + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", ] [[package]] -name = "serde_qs" -version = "1.1.1" +name = "symphonia-utils-xiph" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" dependencies = [ - "itoa", - "percent-encoding", - "ryu", - "serde", + "symphonia-core", + "symphonia-metadata", ] [[package]] -name = "serde_repr" -version = "0.1.20" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", + "unicode-ident", ] [[package]] -name = "serde_tokenstream" -version = "0.2.3" +name = "syn" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c49585c52c01f13c5c2ebb333f14f6885d76daa768d8a037d28017ec538c69" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", - "serde", - "syn 2.0.117", + "unicode-ident", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "sync_wrapper" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "serde_with" -version = "3.18.0" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", - "serde_json", - "serde_with_macros", - "time", + "futures-core", ] [[package]] -name = "serde_with_macros" -version = "3.18.0" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "syntect" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" dependencies = [ - "indexmap 2.14.0", - "itoa", - "ryu", + "bincode", + "fancy-regex 0.16.2", + "flate2", + "fnv", + "once_cell", + "plist", + "regex-syntax", "serde", - "unsafe-libyaml", + "serde_derive", + "serde_json", + "thiserror 2.0.18", + "walkdir", + "yaml-rust", ] [[package]] -name = "serial2" -version = "0.2.36" +name = "sys-locale" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdbc46aa3882ec3d48ec2b5abcb4f0d863a13d7599265f3faa6d851f23c12f3" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" dependencies = [ - "cfg-if", "libc", - "winapi", -] - -[[package]] -name = "serial_test" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" -dependencies = [ - "futures-executor", - "futures-util", - "log", - "once_cell", - "parking_lot", - "scc", - "serial_test_derive", ] [[package]] -name = "serial_test_derive" -version = "3.4.0" +name = "sysinfo" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", ] [[package]] -name = "serialize-to-javascript" -version = "0.1.2" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", ] [[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "core-foundation-sys", + "libc", ] [[package]] -name = "servo_arc" -version = "0.2.0" +name = "system-deps" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "nodrop", - "stable_deref_trait", + "cfg-expr 0.15.8", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", ] [[package]] -name = "servo_arc" -version = "0.4.3" +name = "system-deps" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ - "stable_deref_trait", + "cfg-expr 0.20.7", + "heck 0.5.0", + "pkg-config", + "toml 1.1.2+spec-1.1.0", + "version-compare", ] [[package]] -name = "sha1" -version = "0.10.6" +name = "tantivy" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "502915c7381c5cb2d2781503962610cb880ad8f1a0ca95df1bae645d5ebf2545" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha1-checked" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" -dependencies = [ - "digest 0.10.7", - "sha1", + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "bon 3.9.1", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs 2.0.2", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "hyperloglogplus", + "itertools 0.14.0", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash 2.1.2", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec 1.15.1", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror 2.0.18", + "time", + "uuid", + "winapi", ] [[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sha2" -version = "0.10.9" +name = "tantivy-bitpacker" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "c3b04eed5108d8283607da6710fe17a7663523440eaf7ea5a1a440d19a1448b6" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "bitpacking", ] [[package]] -name = "sha2" -version = "0.11.0" +name = "tantivy-columnar" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +checksum = "8b628488ae936c83e92b5c4056833054ca56f76c0e616aee8339e24ac89119cd" dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.2", + "downcast-rs 2.0.2", + "fastdivide", + "itertools 0.14.0", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", ] [[package]] -name = "sha256" -version = "1.6.0" +name = "tantivy-common" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +checksum = "f880aa7cab0c063a47b62596d10991cdd0b6e0e0575d9c5eeb298b307a25de55" dependencies = [ "async-trait", - "bytes", - "hex", - "sha2 0.10.9", - "tokio", + "byteorder", + "ownedbytes", + "serde", + "time", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "tantivy-fst" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" dependencies = [ - "lazy_static", + "byteorder", + "regex-syntax", + "utf8-ranges", ] [[package]] -name = "shared_child" -version = "1.1.1" +name = "tantivy-query-grammar" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +checksum = "768fccdc84d60d86235d42d7e4c33acf43c418258ff5952abf07bd7837fcd26b" dependencies = [ - "libc", - "sigchld", - "windows-sys 0.60.2", + "nom 7.1.3", + "serde", + "serde_json", ] [[package]] -name = "shared_library" -version = "0.1.9" +name = "tantivy-sstable" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +checksum = "f8292095d1a8a2c2b36380ec455f910ab52dde516af36321af332c93f20ab7d5" dependencies = [ - "lazy_static", - "libc", + "futures-util", + "itertools 0.14.0", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", ] [[package]] -name = "shell-escape" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" - -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shellexpand" -version = "3.1.2" +name = "tantivy-stacker" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +checksum = "23d38a379411169f0b3002c9cba61cdfe315f757e9d4f239c00c282497a0749d" dependencies = [ - "dirs 6.0.0", + "murmurhash32", + "rand_distr 0.4.3", + "tantivy-common", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "tantivy-tokenizer-api" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shortcut-macos" -version = "0.1.0" +checksum = "23024f6aeb25ceb1a0e27740c84bdb0fae52626737b7e9a9de6ad5aa25c7b038" dependencies = [ "serde", - "specta", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "sigchld" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" -dependencies = [ - "libc", - "os_pipe", - "signal-hook 0.3.18", ] [[package]] -name = "signal-hook" -version = "0.3.18" +name = "tao" +version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ + "bitflags 2.11.1", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", "libc", - "signal-hook-registry", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", ] [[package]] -name = "signal-hook" -version = "0.4.4" +name = "tao-macros" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ - "libc", - "signal-hook-registry", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "signal-hook-mio" -version = "0.2.5" +name = "tar" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ + "filetime", "libc", - "mio", - "signal-hook 0.3.18", + "xattr", ] [[package]] -name = "signal-hook-registry" -version = "1.4.8" +name = "target-lexicon" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] -name = "signature" -version = "1.6.4" +name = "target-lexicon" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] -name = "signature" -version = "2.2.0" +name = "tauri" +version = "2.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http 1.4.0", + "http-range", + "image", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.2", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "specta", + "swift-rs 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils 2.8.3", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.61.3", +] [[package]] -name = "similar" -version = "2.7.0" +name = "tauri-build" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch 3.0.1", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils 2.8.3", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] [[package]] -name = "simple_asn1" -version = "0.6.4" +name = "tauri-codegen" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" dependencies = [ - "num-bigint", - "num-traits", + "base64 0.22.1", + "brotli", + "ico", + "json-patch 3.0.1", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils 2.8.3", "thiserror 2.0.18", "time", + "url", + "uuid", + "walkdir", ] [[package]] -name = "simplecss" -version = "0.2.2" +name = "tauri-macros" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" dependencies = [ - "log", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils 2.8.3", ] [[package]] -name = "simsimd" -version = "6.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" +name = "tauri-nspanel" +version = "2.1.0" +source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2.1#a3122e894383aa068ec5365a42994e3ac94ba1b6" dependencies = [ - "cc", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "pastey", + "tauri", ] [[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "sketches-ddsketch" -version = "0.3.1" +name = "tauri-plugin" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", "serde", + "serde_json", + "tauri-utils 2.8.3", + "toml 0.9.12+spec-1.1.0", + "walkdir", ] [[package]] -name = "skrifa" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +name = "tauri-plugin-agent" +version = "0.1.0" dependencies = [ - "bytemuck", - "read-fonts", + "agent-core", + "serde", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-specta", + "thiserror 2.0.18", ] [[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +name = "tauri-plugin-analytics" +version = "0.1.0" +dependencies = [ + "analytics", + "host", + "serde", + "serde_json", + "specta", + "specta-typescript", + "strum 0.28.0", + "tauri", + "tauri-plugin", + "tauri-plugin-misc", + "tauri-plugin-store2", + "tauri-specta", + "thiserror 2.0.18", + "tokio", +] [[package]] -name = "slack-web" +name = "tauri-plugin-audio-priority" version = "0.1.0" dependencies = [ - "hypr-http-utils", + "audio-device", "serde", "serde_json", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-plugin-settings", + "tauri-specta", "thiserror 2.0.18", + "tokio", + "tracing", ] [[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +name = "tauri-plugin-auth" +version = "0.1.0" dependencies = [ - "version_check", + "serde", + "serde_json", + "specta", + "specta-typescript", + "storage", + "strum 0.28.0", + "supabase-auth", + "tauri", + "tauri-plugin", + "tauri-plugin-settings", + "tauri-specta", + "tempfile", + "template-support", + "thiserror 2.0.18", + "tracing", ] [[package]] -name = "slug" -version = "0.1.6" +name = "tauri-plugin-automation" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +checksum = "9183fa70494162bb1571998a9f578eeda96dd92d3431082165f38bff647f3311" dependencies = [ - "deunicode", - "wasm-bindgen", + "libloading 0.8.9", + "objc2-app-kit", + "serde_json", + "tauri", + "tauri-plugin", + "tokio", + "tracing", ] [[package]] -name = "smallvec" -version = "1.15.1" +name = "tauri-plugin-autostart" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "459383cebc193cdd03d1ba4acc40f2c408a7abce419d64bdcd2d745bc2886f70" dependencies = [ + "auto-launch", "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", ] [[package]] -name = "smallvec" -version = "2.0.0-alpha.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b" - -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +name = "tauri-plugin-bedrock" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "aws-config", + "aws-sdk-bedrock", + "serde", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-specta", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +name = "tauri-plugin-calendar" +version = "0.1.0" dependencies = [ - "autocfg", - "static_assertions", - "version_check", + "calendar", + "calendar-interface", + "serde", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-plugin-auth", + "tauri-plugin-permissions", + "tauri-specta", + "thiserror 2.0.18", ] [[package]] -name = "smawk" -version = "0.3.2" +name = "tauri-plugin-clipboard-manager" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf" +dependencies = [ + "arboard", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] [[package]] -name = "smol_str" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +name = "tauri-plugin-db" +version = "0.1.0" dependencies = [ - "borsh", + "anyhow", + "db-app", + "db-core", + "db-execute", + "db-migrate", + "db-reactive", "serde", + "serde_json", + "specta", + "specta-typescript", + "sqlx", + "storage", + "tauri", + "tauri-plugin", + "tauri-specta", + "tauri-utils 0.1.0", + "tempfile", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "snafu" -version = "0.8.9" +name = "tauri-plugin-deep-link" +version = "2.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b" dependencies = [ - "snafu-derive", + "dunce", + "plist", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils 2.8.3", + "thiserror 2.0.18", + "tracing", + "url", + "windows-registry 0.5.3", + "windows-result 0.3.4", ] [[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +name = "tauri-plugin-deeplink2" +version = "0.1.0" dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "askama", + "axum 0.8.9", + "docs", + "open", + "serde", + "serde_json", + "serde_qs", + "serde_yaml", + "specta", + "specta-typescript", + "strum 0.28.0", + "tauri", + "tauri-plugin", + "tauri-plugin-deep-link", + "tauri-specta", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", ] [[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +name = "tauri-plugin-detect" +version = "0.1.0" dependencies = [ - "libc", - "windows-sys 0.52.0", + "detect", + "host", + "notification-interface", + "serde", + "serde_json", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-plugin-detect", + "tauri-plugin-settings", + "tauri-plugin-windows", + "tauri-specta", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "uuid", ] [[package]] -name = "socket2" -version = "0.6.3" +name = "tauri-plugin-dialog" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809" dependencies = [ - "libc", - "windows-sys 0.61.2", + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", ] [[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +name = "tauri-plugin-dictation" +version = "0.1.0" dependencies = [ - "byteorder", - "libc", - "winapi", + "dictation-ui-macos", + "serde", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-plugin-shortcut", + "tauri-specta", + "thiserror 2.0.18", + "tracing", ] [[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +name = "tauri-plugin-dock" +version = "0.1.0" dependencies = [ - "bytemuck", - "js-sys", - "ndk", "objc2", - "objc2-core-foundation", - "objc2-core-graphics", + "objc2-app-kit", "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall 0.5.18", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", + "tauri", + "tauri-plugin", + "tauri-plugin-windows", ] [[package]] -name = "soniox" +name = "tauri-plugin-export" version = "0.1.0" dependencies = [ - "observability", - "reqwest 0.13.2", + "export-core", "serde", - "serde_json", + "specta", + "specta-typescript", + "tauri", + "tauri-plugin", + "tauri-specta", + "thiserror 2.0.18", "tokio", ] [[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +name = "tauri-plugin-flag" +version = "0.1.0" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", + "analytics", + "host", + "serde", + "specta", + "specta-typescript", + "strum 0.28.0", + "tauri", + "tauri-plugin", + "tauri-specta", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "specta" -version = "2.0.0-rc.22" +name = "tauri-plugin-fs" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971" -dependencies = [ - "chrono", - "paste", - "serde_json", - "specta-macros", - "thiserror 1.0.69", -] - -[[package]] -name = "specta-macros" -version = "2.0.0-rc.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "specta-serde" -version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63" -dependencies = [ - "specta", - "thiserror 1.0.69", -] - -[[package]] -name = "specta-typescript" -version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d" -dependencies = [ - "specta", - "specta-serde", - "thiserror 1.0.69", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spinning_top" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.10", -] - -[[package]] -name = "sqlx" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decccfa5f2f3eac95eb68085cfe69a0172fa9711666c3a634cfc806d4fb74a47" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86854e8c6aba0dafcf1c04b4836b0b7fa3a20c560e3554567afefe1258fa4e60" -dependencies = [ - "base64 0.22.1", - "bytes", - "cfg-if", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.4.1", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.16.1", - "hashlink 0.10.0", - "indexmap 2.14.0", - "log", - "memchr", - "percent-encoding", - "rustls 0.23.38", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec 1.15.1", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7aab9442ed1568e3aed6c368737226ee4e0e8d1deb0e0887fa6bf15282ace44" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.117", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34eb4976b8f02ac57ee98d4ce40cd1aad7ab31d9792977bc3171f787ba6ba2fb" -dependencies = [ - "cfg-if", - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.117", - "thiserror 2.0.18", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fef16f3d52a3710a672b48175b713e86476e2df85576a753c8b37ad11a483c0" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.1", - "byteorder", - "bytes", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac 0.12.1", - "itoa", - "log", - "md-5", - "memchr", - "percent-encoding", - "rand 0.8.6", - "rsa", - "serde", - "sha1", - "sha2 0.10.9", - "smallvec 1.15.1", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053cf36ecb2793a9d9bb02d01bbad1ef66481d5db6ff5ab2dfb7b070cc0d13c" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.1", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac 0.12.1", - "home", - "itoa", - "log", - "md-5", - "memchr", - "rand 0.8.6", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec 1.15.1", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.9.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe2cd6cee87120b1e1dd31356b5589911995c777707e49f2750eec7c7fe43eef" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", -] - -[[package]] -name = "sse-stream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5e6deb40826033bd7b11c7ef25ef71193fabd71f680f40dd16538a2704d2f4" -dependencies = [ - "bytes", - "futures-util", - "http-body 1.0.1", - "http-body-util", - "pin-project-lite", -] - -[[package]] -name = "ssh-cipher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" -dependencies = [ - "cipher", - "ssh-encoding", -] - -[[package]] -name = "ssh-encoding" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" -dependencies = [ - "base64ct", - "pem-rfc7468 0.7.0", - "sha2 0.10.9", -] - -[[package]] -name = "ssh-key" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" -dependencies = [ - "ed25519-dalek", - "rand_core 0.6.4", - "sha2 0.10.9", - "signature 2.2.0", - "ssh-cipher", - "ssh-encoding", - "subtle", - "zeroize", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "stacker" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "stop-token" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af91f480ee899ab2d9f8435bfdfc14d08a5754bd9d3fef1f1a1c23336aad6c8b" -dependencies = [ - "async-channel 1.9.0", - "cfg-if", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "storage" -version = "0.1.0" -dependencies = [ - "dirs 6.0.0", - "serde", - "serde_json", - "shellexpand", - "specta", - "tempfile", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "str_inflector" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b848d5a7695b33ad1be00f84a3c079fe85c9278a325ff9159e6c99cef4ef7" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "strength_reduce" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp 0.9.0", -] - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.13.1", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", -] - -[[package]] -name = "string_enum" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e" -dependencies = [ - "quote", - "swc_macros_common", - "syn 2.0.117", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "structmeta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn 2.0.117", -] - -[[package]] -name = "structmeta-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros 0.28.0", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.117", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "subsetter" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6895a12ac5599bb6057362f00e8a3cf1daab4df33f553a55690a44e4fed8d0" -dependencies = [ - "kurbo 0.12.0", - "rustc-hash 2.1.2", - "skrifa", - "write-fonts", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "supabase-auth" -version = "0.1.0" -dependencies = [ - "axum 0.8.9", - "backon", - "base64 0.22.1", - "chrono", - "jsonwebtoken", - "reqwest 0.13.2", - "serde", - "serde_json", - "specta", - "storage", - "thiserror 2.0.18", - "tokio", - "wiremock", -] - -[[package]] -name = "supabase-storage" -version = "0.1.0" -dependencies = [ - "observability", - "reqwest 0.13.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "supervisor" -version = "0.1.0" -dependencies = [ - "ractor", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "svgtypes" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" -dependencies = [ - "kurbo 0.11.3", - "siphasher 1.0.2", -] - -[[package]] -name = "swc_atoms" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40c2b43a19b5d0706aca8669ae5b77b92bd141f7f8ce5e980e0e52430f54b20" -dependencies = [ - "hstr", - "once_cell", - "serde", -] - -[[package]] -name = "swc_common" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e51fecd32bb0989543f0a64f4103cbd728e375838be83d768ce6989f5ea631" -dependencies = [ - "anyhow", - "ast_node", - "better_scoped_tls", - "bytes-str", - "either", - "from_variant", - "new_debug_unreachable", - "num-bigint", - "once_cell", - "rustc-hash 2.1.2", - "serde", - "siphasher 0.3.11", - "swc_atoms", - "swc_eq_ignore_macros", - "swc_visit", - "tracing", - "unicode-width 0.1.14", - "url", -] - -[[package]] -name = "swc_ecma_ast" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8bb0e5aaa6e077f178a28d29bc7da4a8ddaf012b3c21c043cb5f72a0b9779" -dependencies = [ - "bitflags 2.11.1", - "is-macro", - "num-bigint", - "once_cell", - "phf 0.11.3", - "rustc-hash 2.1.2", - "string_enum", - "swc_atoms", - "swc_common", - "swc_visit", - "unicode-id-start", -] - -[[package]] -name = "swc_ecma_parser" -version = "26.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac3281dd9eef03b877fe9cef75a4c8951ce6df0c5f381868f302ee3c58fa6e2" -dependencies = [ - "bitflags 2.11.1", - "either", - "num-bigint", - "phf 0.11.3", - "rustc-hash 2.1.2", - "seq-macro", - "serde", - "smartstring", - "stacker", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "tracing", -] - -[[package]] -name = "swc_eq_ignore_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "swc_macros_common" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "swc_visit" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" -dependencies = [ - "either", - "new_debug_unreachable", -] - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "git+https://github.com/yujonglee/swift-rs?rev=41a1605#41a160524c737c061f5633e4371952544425aa01" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "symlink" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" - -[[package]] -name = "symphonia" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" -dependencies = [ - "lazy_static", - "symphonia-bundle-flac", - "symphonia-bundle-mp3", - "symphonia-codec-aac", - "symphonia-codec-adpcm", - "symphonia-codec-alac", - "symphonia-codec-pcm", - "symphonia-codec-vorbis", - "symphonia-core", - "symphonia-format-caf", - "symphonia-format-isomp4", - "symphonia-format-mkv", - "symphonia-format-ogg", - "symphonia-format-riff", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-bundle-flac" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-bundle-mp3" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" -dependencies = [ - "lazy_static", - "log", - "symphonia-core", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-codec-aac" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" -dependencies = [ - "lazy_static", - "log", - "symphonia-core", -] - -[[package]] -name = "symphonia-codec-adpcm" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" -dependencies = [ - "log", - "symphonia-core", -] - -[[package]] -name = "symphonia-codec-alac" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" -dependencies = [ - "log", - "symphonia-core", -] - -[[package]] -name = "symphonia-codec-pcm" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" -dependencies = [ - "log", - "symphonia-core", -] - -[[package]] -name = "symphonia-codec-vorbis" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" -dependencies = [ - "log", - "symphonia-core", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-core" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" -dependencies = [ - "arrayvec", - "bitflags 1.3.2", - "bytemuck", - "lazy_static", - "log", -] - -[[package]] -name = "symphonia-format-caf" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-format-isomp4" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" -dependencies = [ - "encoding_rs", - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-format-mkv" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" -dependencies = [ - "lazy_static", - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-format-ogg" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-format-riff" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" -dependencies = [ - "extended", - "log", - "symphonia-core", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-metadata" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" -dependencies = [ - "encoding_rs", - "lazy_static", - "log", - "symphonia-core", -] - -[[package]] -name = "symphonia-utils-xiph" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" -dependencies = [ - "symphonia-core", - "symphonia-metadata", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "syntect" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" -dependencies = [ - "bincode", - "fancy-regex 0.16.2", - "flate2", - "fnv", - "once_cell", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "thiserror 2.0.18", - "walkdir", - "yaml-rust", -] - -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", -] - -[[package]] -name = "sysinfo" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" -dependencies = [ - "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows 0.62.2", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr 0.15.8", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "system-deps" -version = "7.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" -dependencies = [ - "cfg-expr 0.20.7", - "heck 0.5.0", - "pkg-config", - "toml 1.1.2+spec-1.1.0", - "version-compare", -] - -[[package]] -name = "tantivy" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502915c7381c5cb2d2781503962610cb880ad8f1a0ca95df1bae645d5ebf2545" -dependencies = [ - "aho-corasick", - "arc-swap", - "base64 0.22.1", - "bitpacking", - "bon 3.9.1", - "byteorder", - "census", - "crc32fast", - "crossbeam-channel", - "downcast-rs 2.0.2", - "fastdivide", - "fnv", - "fs4", - "htmlescape", - "hyperloglogplus", - "itertools 0.14.0", - "levenshtein_automata", - "log", - "lru 0.12.5", - "lz4_flex", - "measure_time", - "memmap2", - "once_cell", - "oneshot", - "rayon", - "regex", - "rust-stemmers", - "rustc-hash 2.1.2", - "serde", - "serde_json", - "sketches-ddsketch", - "smallvec 1.15.1", - "tantivy-bitpacker", - "tantivy-columnar", - "tantivy-common", - "tantivy-fst", - "tantivy-query-grammar", - "tantivy-stacker", - "tantivy-tokenizer-api", - "tempfile", - "thiserror 2.0.18", - "time", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-bitpacker" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b04eed5108d8283607da6710fe17a7663523440eaf7ea5a1a440d19a1448b6" -dependencies = [ - "bitpacking", -] - -[[package]] -name = "tantivy-columnar" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b628488ae936c83e92b5c4056833054ca56f76c0e616aee8339e24ac89119cd" -dependencies = [ - "downcast-rs 2.0.2", - "fastdivide", - "itertools 0.14.0", - "serde", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-sstable", - "tantivy-stacker", -] - -[[package]] -name = "tantivy-common" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f880aa7cab0c063a47b62596d10991cdd0b6e0e0575d9c5eeb298b307a25de55" -dependencies = [ - "async-trait", - "byteorder", - "ownedbytes", - "serde", - "time", -] - -[[package]] -name = "tantivy-fst" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" -dependencies = [ - "byteorder", - "regex-syntax", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "768fccdc84d60d86235d42d7e4c33acf43c418258ff5952abf07bd7837fcd26b" -dependencies = [ - "nom 7.1.3", - "serde", - "serde_json", -] - -[[package]] -name = "tantivy-sstable" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8292095d1a8a2c2b36380ec455f910ab52dde516af36321af332c93f20ab7d5" -dependencies = [ - "futures-util", - "itertools 0.14.0", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-fst", - "zstd", -] - -[[package]] -name = "tantivy-stacker" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d38a379411169f0b3002c9cba61cdfe315f757e9d4f239c00c282497a0749d" -dependencies = [ - "murmurhash32", - "rand_distr 0.4.3", - "tantivy-common", -] - -[[package]] -name = "tantivy-tokenizer-api" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23024f6aeb25ceb1a0e27740c84bdb0fae52626737b7e9a9de6ad5aa25c7b038" -dependencies = [ - "serde", -] - -[[package]] -name = "tao" -version = "0.34.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" -dependencies = [ - "bitflags 2.11.1", - "block2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tar" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "target-lexicon" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" - -[[package]] -name = "tauri" -version = "2.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs 6.0.0", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http 1.4.0", - "http-range", - "image", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.2", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "specta", - "swift-rs 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils 2.8.3", - "thiserror 2.0.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs 6.0.0", - "glob", - "heck 0.5.0", - "json-patch 3.0.1", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils 2.8.3", - "tauri-winres", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch 3.0.1", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.117", - "tauri-utils 2.8.3", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "tauri-codegen", - "tauri-utils 2.8.3", -] - -[[package]] -name = "tauri-nspanel" -version = "2.1.0" -source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2.1#a3122e894383aa068ec5365a42994e3ac94ba1b6" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "pastey", - "tauri", -] - -[[package]] -name = "tauri-plugin" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils 2.8.3", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-plugin-activity-capture" -version = "0.1.0" -dependencies = [ - "activity-capture", - "futures-util", - "reqwest 0.13.2", - "screen-core", - "serde", - "serde_json", - "sha2 0.11.0", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-plugin-activity-capture", - "tauri-plugin-local-llm", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "tauri-plugin-agent" -version = "0.1.0" -dependencies = [ - "agent-core", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", -] - -[[package]] -name = "tauri-plugin-analytics" -version = "0.1.0" -dependencies = [ - "analytics", - "host", - "serde", - "serde_json", - "specta", - "specta-typescript", - "strum 0.28.0", - "tauri", - "tauri-plugin", - "tauri-plugin-misc", - "tauri-plugin-store2", - "tauri-specta", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-audio-priority" -version = "0.1.0" -dependencies = [ - "audio-device", - "serde", - "serde_json", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-plugin-settings", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "tauri-plugin-auth" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "specta", - "specta-typescript", - "storage", - "strum 0.28.0", - "supabase-auth", - "tauri", - "tauri-plugin", - "tauri-plugin-settings", - "tauri-specta", - "tempfile", - "template-support", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "tauri-plugin-automation" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9183fa70494162bb1571998a9f578eeda96dd92d3431082165f38bff647f3311" -dependencies = [ - "libloading 0.8.9", - "objc2-app-kit", - "serde_json", - "tauri", - "tauri-plugin", - "tokio", - "tracing", -] - -[[package]] -name = "tauri-plugin-autostart" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459383cebc193cdd03d1ba4acc40f2c408a7abce419d64bdcd2d745bc2886f70" -dependencies = [ - "auto-launch", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", -] - -[[package]] -name = "tauri-plugin-bedrock" -version = "0.1.0" -dependencies = [ - "aws-config", - "aws-sdk-bedrock", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-calendar" -version = "0.1.0" -dependencies = [ - "calendar", - "calendar-interface", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-plugin-auth", - "tauri-plugin-permissions", - "tauri-specta", - "thiserror 2.0.18", -] - -[[package]] -name = "tauri-plugin-clipboard-manager" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf" -dependencies = [ - "arboard", - "log", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", -] - -[[package]] -name = "tauri-plugin-db" -version = "0.1.0" -dependencies = [ - "anyhow", - "db-app", - "db-core", - "db-execute", - "db-migrate", - "db-reactive", - "serde", - "serde_json", - "specta", - "specta-typescript", - "sqlx", - "storage", - "tauri", - "tauri-plugin", - "tauri-specta", - "tauri-utils 0.1.0", - "tempfile", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-deep-link" -version = "2.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b" -dependencies = [ - "dunce", - "plist", - "rust-ini", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-utils 2.8.3", - "thiserror 2.0.18", - "tracing", - "url", - "windows-registry 0.5.3", - "windows-result 0.3.4", -] - -[[package]] -name = "tauri-plugin-deeplink2" -version = "0.1.0" -dependencies = [ - "askama 0.15.6", - "axum 0.8.9", - "docs", - "open", - "serde", - "serde_json", - "serde_qs", - "serde_yaml", - "specta", - "specta-typescript", - "strum 0.28.0", - "tauri", - "tauri-plugin", - "tauri-plugin-deep-link", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "tauri-plugin-detect" -version = "0.1.0" -dependencies = [ - "detect", - "host", - "notification-interface", - "serde", - "serde_json", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-plugin-detect", - "tauri-plugin-settings", - "tauri-plugin-windows", - "tauri-specta", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", - "uuid", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "tauri-plugin-dictation" -version = "0.1.0" -dependencies = [ - "dictation-ui-macos", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-plugin-shortcut", - "tauri-specta", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "tauri-plugin-dock" -version = "0.1.0" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "tauri", - "tauri-plugin", - "tauri-plugin-windows", -] - -[[package]] -name = "tauri-plugin-export" -version = "0.1.0" -dependencies = [ - "export-core", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-flag" -version = "0.1.0" -dependencies = [ - "analytics", - "host", - "serde", - "specta", - "specta-typescript", - "strum 0.28.0", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" +checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" dependencies = [ "anyhow", "dunce", @@ -19431,6 +16692,7 @@ dependencies = [ "tower-http 0.6.8", "tracing", "transcribe-cactus", + "transcribe-soniqo", "transcribe-whisper-local", "whisper-local", "whisper-local-model", @@ -19695,22 +16957,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tauri-plugin-screen" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "screen-core", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", - "tokio", -] - [[package]] name = "tauri-plugin-sentry" version = "0.5.0" @@ -19968,6 +17214,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "transcribe-soniqo", "transcript", "transcription-core", ] @@ -20045,23 +17292,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tauri-plugin-webhook" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "specta", - "specta-typescript", - "strum 0.28.0", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.18", - "utoipa", - "uuid", -] - [[package]] name = "tauri-plugin-window-state" version = "2.4.1" @@ -20199,7 +17429,7 @@ checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" dependencies = [ "anyhow", "brotli", - "cargo_metadata 0.19.2", + "cargo_metadata", "ctor", "dunce", "glob", @@ -20247,16 +17477,6 @@ dependencies = [ "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", ] -[[package]] -name = "teems" -version = "0.1.0" -dependencies = [ - "hypr-http-utils", - "serde", - "serde_json", - "thiserror 2.0.18", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -20274,7 +17494,7 @@ dependencies = [ name = "template-app" version = "0.1.0" dependencies = [ - "askama 0.15.6", + "askama", "askama-utils", "insta", "serde", @@ -20303,18 +17523,11 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "template-cli" -version = "0.1.0" -dependencies = [ - "askama 0.15.6", -] - [[package]] name = "template-eval" version = "0.1.0" dependencies = [ - "askama 0.15.6", + "askama", "askama-utils", "insta", "libtest-mimic", @@ -20331,7 +17544,7 @@ dependencies = [ name = "template-support" version = "0.1.0" dependencies = [ - "askama 0.15.6", + "askama", "serde", "specta", "utoipa", @@ -20344,7 +17557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", - "mac 0.1.1", + "mac", "utf-8", ] @@ -20380,84 +17593,11 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "terminal_size" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" -dependencies = [ - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom 7.1.3", - "phf 0.11.3", - "phf_codegen 0.11.3", -] - -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - [[package]] name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "termwiz" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" -dependencies = [ - "anyhow", - "base64 0.22.1", - "bitflags 2.11.1", - "fancy-regex 0.11.0", - "filedescriptor", - "finl_unicode", - "fixedbitset 0.4.2", - "hex", - "lazy_static", - "libc", - "log", - "memmem", - "nix 0.29.0", - "num-derive", - "num-traits", - "ordered-float", - "pest", - "pest_derive", - "phf 0.11.3", - "sha2 0.10.9", - "signal-hook 0.3.18", - "siphasher 1.0.2", - "terminfo", - "termios", - "thiserror 1.0.69", - "ucd-trie", - "unicode-segmentation", - "vtparse", - "wezterm-bidi", - "wezterm-blob-leases", - "wezterm-color-types", - "wezterm-dynamic", - "wezterm-input-types", - "winapi", -] +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "testcontainers" @@ -20497,18 +17637,6 @@ dependencies = [ "testcontainers", ] -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" -dependencies = [ - "smawk", - "terminal_size", - "unicode-linebreak", - "unicode-width 0.2.2", -] - [[package]] name = "thin-vec" version = "0.2.16" @@ -20798,18 +17926,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.18" @@ -20819,7 +17935,6 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", ] [[package]] @@ -20862,13 +17977,9 @@ dependencies = [ "futures-util", "log", "native-tls", - "rustls 0.23.38", - "rustls-pki-types", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.4", "tungstenite 0.29.0", - "webpki-roots 0.26.11", ] [[package]] @@ -20908,15 +18019,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.8.2" @@ -21019,7 +18121,6 @@ dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "toml_writer", "winnow 1.0.1", ] @@ -21053,10 +18154,10 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "hyper-timeout 0.4.1", + "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.6", + "prost", "tokio", "tokio-stream", "tower 0.4.13", @@ -21065,32 +18166,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bytes", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "hyper-timeout 0.5.2", - "hyper-util", - "percent-encoding", - "pin-project", - "prost 0.13.5", - "tokio", - "tokio-stream", - "tower 0.5.3", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tonic-web" version = "0.11.0" @@ -21104,7 +18179,7 @@ dependencies = [ "hyper 0.14.32", "pin-project", "tokio-stream", - "tonic 0.11.0", + "tonic", "tower-http 0.4.4", "tower-layer", "tower-service", @@ -21139,9 +18214,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.14.0", "pin-project-lite", - "slab", "sync_wrapper 1.0.2", "tokio", "tokio-util", @@ -21162,7 +18235,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "http-range-header 0.3.1", + "http-range-header", "pin-project-lite", "tower 0.4.13", "tower-layer", @@ -21184,12 +18257,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "http-range-header 0.4.2", - "httpdate", "iri-string", - "mime", - "mime_guess", - "percent-encoding", "pin-project-lite", "tokio", "tokio-util", @@ -21197,7 +18265,6 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid", ] [[package]] @@ -21297,16 +18364,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -21318,15 +18375,12 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", - "serde", - "serde_json", "sharded-slab", "smallvec 1.15.1", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] @@ -21386,49 +18440,17 @@ dependencies = [ ] [[package]] -name = "transcribe-proxy" +name = "transcribe-soniqo" version = "0.1.0" dependencies = [ - "analytics", - "api-auth", - "api-env", - "audio-mime", - "audio-utils", - "axum 0.8.9", - "backon", - "base64 0.22.1", - "bytes", - "codes-iso-639", - "data", - "futures-util", - "insta", - "itertools 0.14.0", "language", - "observability", - "owhisper-client", "owhisper-interface", - "quickcheck", - "quickcheck_macros", - "reqwest 0.13.2", - "reqwest-middleware", - "rodio", - "sentry", "serde", - "serde_html_form 0.4.0", "serde_json", - "supabase-storage", - "tempfile", + "specta", + "swift-rs 1.0.7 (git+https://github.com/yujonglee/swift-rs?rev=41a1605)", "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.29.0", - "tower 0.5.3", "tracing", - "tracing-subscriber", - "url", - "urlencoding", - "utoipa", - "uuid", ] [[package]] @@ -21526,7 +18548,7 @@ checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ "memchr", "nom 8.0.0", - "petgraph 0.8.3", + "petgraph", ] [[package]] @@ -21606,8 +18628,6 @@ dependencies = [ "log", "native-tls", "rand 0.9.4", - "rustls 0.23.38", - "rustls-pki-types", "sha1", "thiserror 2.0.18", ] @@ -21629,12 +18649,6 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - [[package]] name = "typeid" version = "1.0.3" @@ -22155,368 +19169,88 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" name = "unicode-ccc" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" - -[[package]] -name = "unicode-general-category" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" - -[[package]] -name = "unicode-id" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" - -[[package]] -name = "unicode-id-start" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-math-class" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d246cf599d5fae3c8d56e04b20eb519adb89a8af8d0b0fbcded369aa3647d65" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - -[[package]] -name = "unicode-segmentation" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" - -[[package]] -name = "unicode-truncate" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" -dependencies = [ - "itertools 0.14.0", - "unicode-segmentation", - "unicode-width 0.2.2", -] - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "uniffi" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" -dependencies = [ - "anyhow", - "camino", - "cargo_metadata 0.19.2", - "clap", - "uniffi_bindgen 0.29.5", - "uniffi_core 0.29.5", - "uniffi_macros 0.29.5", - "uniffi_pipeline 0.29.5", -] - -[[package]] -name = "uniffi" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5f2297ee5b893405bed1a6929faec4713a061df158ecf5198089f23910d470" -dependencies = [ - "anyhow", - "cargo_metadata 0.19.2", - "uniffi_bindgen 0.31.1", - "uniffi_core 0.31.1", - "uniffi_macros 0.31.1", - "uniffi_pipeline 0.31.1", -] - -[[package]] -name = "uniffi-bindgen" -version = "0.1.0" -dependencies = [ - "uniffi 0.29.5", -] - -[[package]] -name = "uniffi_bindgen" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" -dependencies = [ - "anyhow", - "askama 0.13.1", - "camino", - "cargo_metadata 0.19.2", - "fs-err", - "glob", - "goblin", - "heck 0.5.0", - "indexmap 2.14.0", - "once_cell", - "serde", - "tempfile", - "textwrap", - "toml 0.5.11", - "uniffi_internal_macros 0.29.5", - "uniffi_meta 0.29.5", - "uniffi_pipeline 0.29.5", - "uniffi_udl 0.29.5", -] - -[[package]] -name = "uniffi_bindgen" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc0c60a9607e7ab77a2ad47ec5530178015014839db25af7512447d2238016c" -dependencies = [ - "anyhow", - "askama 0.14.0", - "camino", - "cargo_metadata 0.19.2", - "fs-err", - "glob", - "goblin", - "heck 0.5.0", - "indexmap 2.14.0", - "once_cell", - "serde", - "tempfile", - "textwrap", - "toml 0.9.12+spec-1.1.0", - "uniffi_internal_macros 0.31.1", - "uniffi_meta 0.31.1", - "uniffi_pipeline 0.31.1", - "uniffi_udl 0.31.1", -] - -[[package]] -name = "uniffi_core" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" -dependencies = [ - "anyhow", - "bytes", - "once_cell", - "static_assertions", -] +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] -name = "uniffi_core" -version = "0.31.1" +name = "unicode-general-category" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77baf5d539fe2e1ad6805e942dbc5dbdeb2b83eb5f2b3a6535d422ca4b02a12f" -dependencies = [ - "anyhow", - "bytes", - "once_cell", - "static_assertions", -] +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] -name = "uniffi_internal_macros" -version = "0.29.5" +name = "unicode-id" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" [[package]] -name = "uniffi_internal_macros" -version = "0.31.1" +name = "unicode-id-start" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b42137524f4be6400fcaca9d02c1d4ecb6ad917e4013c0b93235526d8396e5" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" [[package]] -name = "uniffi_macros" -version = "0.29.5" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" -dependencies = [ - "camino", - "fs-err", - "once_cell", - "proc-macro2", - "quote", - "serde", - "syn 2.0.117", - "toml 0.5.11", - "uniffi_meta 0.29.5", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "uniffi_macros" -version = "0.31.1" +name = "unicode-math-class" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9273ec45330d8fe9a3701b7b983cea7a4e218503359831967cb95d26b873561" -dependencies = [ - "camino", - "fs-err", - "once_cell", - "proc-macro2", - "quote", - "serde", - "syn 2.0.117", - "toml 0.9.12+spec-1.1.0", - "uniffi_meta 0.31.1", -] +checksum = "7d246cf599d5fae3c8d56e04b20eb519adb89a8af8d0b0fbcded369aa3647d65" [[package]] -name = "uniffi_meta" -version = "0.29.5" +name = "unicode-normalization" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ - "anyhow", - "siphasher 0.3.11", - "uniffi_internal_macros 0.29.5", - "uniffi_pipeline 0.29.5", + "tinyvec", ] [[package]] -name = "uniffi_meta" -version = "0.31.1" +name = "unicode-properties" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431d2f443e7828a6c29d188de98b6771a6491ee98bba2d4372643bf93f988a18" -dependencies = [ - "anyhow", - "siphasher 1.0.2", - "uniffi_internal_macros 0.31.1", - "uniffi_pipeline 0.31.1", -] +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] -name = "uniffi_pipeline" -version = "0.29.5" +name = "unicode-script" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "tempfile", - "uniffi_internal_macros 0.29.5", -] +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] -name = "uniffi_pipeline" -version = "0.31.1" +name = "unicode-segmentation" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761ef74f6175e15603d0424cc5f98854c5baccfe7bf4ccb08e5816f9ab8af689" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "tempfile", - "uniffi_internal_macros 0.31.1", -] +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] -name = "uniffi_udl" -version = "0.29.5" +name = "unicode-vo" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" -dependencies = [ - "anyhow", - "textwrap", - "uniffi_meta 0.29.5", - "weedle2", -] +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] -name = "uniffi_udl" -version = "0.31.1" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68773ec0e1c067b6505a73bbf6a5782f31a7f9209333a0df97b87565c46bf370" -dependencies = [ - "anyhow", - "textwrap", - "uniffi_meta 0.31.1", - "weedle2", -] +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unit-prefix" -version = "0.5.2" +name = "unicode-width" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] -name = "universal-hash" -version = "0.5.1" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.6", - "subtle", -] +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" @@ -22536,24 +19270,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "updater-core" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "futures-util", - "http 1.4.0", - "minisign-verify", - "percent-encoding", - "reqwest 0.13.2", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", - "time", - "url", -] - [[package]] name = "ureq" version = "3.3.0" @@ -22702,7 +19418,6 @@ version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "atomic", "getrandom 0.4.2", "js-sys", "serde_core", @@ -22799,13 +19514,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "version" -version = "0.1.0" -dependencies = [ - "semver", -] - [[package]] name = "version-compare" version = "0.2.1" @@ -22869,48 +19577,6 @@ dependencies = [ "libc", ] -[[package]] -name = "vt100" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" -dependencies = [ - "itoa", - "log", - "unicode-width 0.1.14", - "vte", -] - -[[package]] -name = "vte" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" -dependencies = [ - "arrayvec", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "vtparse" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" -dependencies = [ - "utf8parse", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -23149,7 +19815,6 @@ dependencies = [ "cc", "downcast-rs 1.2.1", "rustix 1.1.4", - "scoped-tls", "smallvec 1.15.1", "wayland-sys", ] @@ -23202,29 +19867,12 @@ dependencies = [ "quote", ] -[[package]] -name = "wayland-server" -version = "0.31.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1846eb04c49182e04f4a099e2a830a2b745610bbc1d61246e206f29c7000a0" -dependencies = [ - "bitflags 2.11.1", - "downcast-rs 1.2.1", - "rustix 1.1.4", - "wayland-backend", - "wayland-scanner", -] - [[package]] name = "wayland-sys" version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ - "dlib", - "libc", - "log", - "memoffset", "pkg-config", ] @@ -23245,7 +19893,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", - "serde", "wasm-bindgen", ] @@ -23368,93 +20015,12 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "weedle2" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "weezl" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" -[[package]] -name = "wezterm-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" -dependencies = [ - "log", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-blob-leases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" -dependencies = [ - "getrandom 0.3.4", - "mac_address", - "sha2 0.10.9", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "wezterm-color-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" -dependencies = [ - "csscolorparser", - "deltae", - "lazy_static", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-dynamic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" -dependencies = [ - "log", - "ordered-float", - "strsim", - "thiserror 1.0.69", - "wezterm-dynamic-derive", -] - -[[package]] -name = "wezterm-dynamic-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "wezterm-input-types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" -dependencies = [ - "bitflags 1.3.2", - "euclid", - "lazy_static", - "serde", - "wezterm-dynamic", -] - [[package]] name = "which" version = "4.4.2" @@ -23467,15 +20033,6 @@ dependencies = [ "rustix 0.38.44", ] -[[package]] -name = "which" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" -dependencies = [ - "libc", -] - [[package]] name = "whichlang" version = "0.1.1" @@ -23817,22 +20374,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-link 0.2.1", ] [[package]] @@ -24102,15 +20644,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.15" @@ -24284,764 +20817,306 @@ dependencies = [ ] [[package]] -name = "write-fonts" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886614b5ce857341226aa091f3c285e450683894acaaa7887f366c361efef79d" -dependencies = [ - "font-types", - "indexmap 2.14.0", - "kurbo 0.12.0", - "log", - "read-fonts", -] - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wry" -version = "0.54.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs 6.0.0", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http 1.4.0", - "javascriptcore-rs", - "jni", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "ws-client" -version = "0.1.0" -dependencies = [ - "async-stream", - "backon", - "bytes", - "futures-util", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tracing", -] - -[[package]] -name = "ws-utils" -version = "0.1.0" -dependencies = [ - "audio-interface", - "audio-utils", - "axum 0.8.9", - "futures-util", - "owhisper-interface", - "pin-project", - "serde_json", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "gethostname", - "rustix 1.1.4", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix 1.1.4", -] - -[[package]] -name = "xcap" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d20dc4057d755e2b78da287f9057065a89c507af69e1a5eac19e868c21cd8a" -dependencies = [ - "dispatch2", - "image", - "libwayshot-xcap", - "log", - "objc2", - "objc2-app-kit", - "objc2-av-foundation", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-media", - "objc2-core-video", - "objc2-foundation", - "percent-encoding", - "pipewire", - "rand 0.9.4", - "scopeguard", - "serde", - "thiserror 2.0.18", - "url", - "widestring", - "windows 0.62.2", - "xcb", - "zbus", -] - -[[package]] -name = "xcb" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" -dependencies = [ - "bitflags 1.3.2", - "libc", - "quick-xml 0.30.0", -] - -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xml5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dc9559429edf0cd3f327cc0afd9d6b36fa8cec6d93107b7fbe64f806b5f2d9" -dependencies = [ - "log", - "markup5ever 0.38.0", -] - -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "xmp-writer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9e2f4a404d9ebffc0a9832cf4f50907220ba3d7fffa9099261a5cab52f2dd7" - -[[package]] -name = "xshell" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" -dependencies = [ - "xshell-macros", -] - -[[package]] -name = "xshell-macros" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" - -[[package]] -name = "xtask" -version = "0.1.0" -dependencies = [ - "anyhow", - "pathdiff", - "tempfile", - "toml 0.8.2", - "xshell", -] - -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" +name = "write-fonts" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "886614b5ce857341226aa091f3c285e450683894acaaa7887f366c361efef79d" dependencies = [ - "linked-hash-map", + "font-types", + "indexmap 2.14.0", + "kurbo 0.12.0", + "log", + "read-fonts", ] [[package]] -name = "yaml-rust2" -version = "0.10.4" +name = "writeable" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink 0.10.0", -] +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "yoke" -version = "0.7.5" +name = "writeable" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive 0.7.5", - "zerofrom", -] +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] -name = "yoke" -version = "0.8.2" +name = "wry" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" dependencies = [ - "stable_deref_trait", - "yoke-derive 0.8.2", - "zerofrom", + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http 1.4.0", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", ] [[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +name = "ws-client" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", + "async-stream", + "backon", + "bytes", + "futures-util", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "tracing", ] [[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +name = "ws-utils" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", + "audio-interface", + "audio-utils", + "axum 0.8.9", + "futures-util", + "owhisper-interface", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "zbus" -version = "5.14.0" +name = "x11" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener 5.4.1", - "futures-core", - "futures-lite", - "hex", "libc", - "ordered-stream", - "rustix 1.1.4", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 0.7.15", - "zbus_macros", - "zbus_names", - "zvariant", + "pkg-config", ] [[package]] -name = "zbus_macros" -version = "5.14.0" +name = "x11-dl" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "zbus_names", - "zvariant", - "zvariant_utils", + "libc", + "once_cell", + "pkg-config", ] [[package]] -name = "zbus_names" -version = "4.3.1" +name = "x11rb" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ - "serde", - "winnow 0.7.15", - "zvariant", + "gethostname", + "rustix 1.1.4", + "x11rb-protocol", ] [[package]] -name = "zeroclaw-api" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" -dependencies = [ - "anyhow", - "async-trait", - "futures-util", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", -] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] -name = "zeroclaw-channels" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ - "anyhow", - "async-imap", - "async-trait", - "axum 0.8.9", - "base64 0.22.1", - "chrono", - "directories", - "futures-util", - "hex", - "hmac 0.12.1", - "image", - "lettre", - "lru 0.16.4", - "mail-parser", - "nanohtml2text", - "parking_lot", - "portable-atomic", - "rand 0.10.1", - "regex", - "reqwest 0.12.28", - "rumqttc", - "rusqlite", - "rustls 0.23.38", - "rustls-pki-types", - "serde", - "serde_json", - "sha2 0.10.9", - "shellexpand", - "tokio", - "tokio-rustls 0.26.4", - "tokio-socks", - "tokio-tungstenite 0.29.0", - "tokio-util", - "toml 1.1.2+spec-1.1.0", - "tracing", - "urlencoding", - "uuid", - "webpki-roots 1.0.7", - "zeroclaw-api", - "zeroclaw-config", - "zeroclaw-infra", - "zeroclaw-memory", - "zeroclaw-providers", - "zeroclaw-runtime", - "zeroclaw-tools", + "libc", + "rustix 1.1.4", ] [[package]] -name = "zeroclaw-config" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" -dependencies = [ - "anyhow", - "chacha20poly1305", - "chrono", - "directories", - "hex", - "hostname", - "parking_lot", - "rand 0.10.1", - "regex", - "reqwest 0.12.28", - "rustls 0.23.38", - "rustls-pki-types", - "schemars 1.2.1", - "serde", - "serde_json", - "sha2 0.10.9", - "shellexpand", - "thiserror 2.0.18", - "tokio", - "tokio-rustls 0.26.4", - "tokio-socks", - "tokio-stream", - "tokio-tungstenite 0.29.0", - "toml 1.1.2+spec-1.1.0", - "toml_edit 0.25.11+spec-1.1.0", - "tracing", - "url", - "uuid", - "webpki-roots 1.0.7", - "zeroclaw-api", - "zeroclaw-macros", +name = "xml5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dc9559429edf0cd3f327cc0afd9d6b36fa8cec6d93107b7fbe64f806b5f2d9" +dependencies = [ + "log", + "markup5ever 0.38.0", ] [[package]] -name = "zeroclaw-gateway" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "xmp-writer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce9e2f4a404d9ebffc0a9832cf4f50907220ba3d7fffa9099261a5cab52f2dd7" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ - "anyhow", - "async-trait", - "axum 0.8.9", - "chrono", - "directories", - "futures-util", - "hex", - "hmac 0.12.1", - "http-body-util", - "hyper 1.9.0", - "hyper-util", - "mime_guess", - "parking_lot", - "rusqlite", - "rustls 0.23.38", - "rustls-pemfile", - "serde", - "serde_json", - "sha2 0.10.9", - "tokio", - "tokio-rustls 0.26.4", - "tokio-stream", - "toml 1.1.2+spec-1.1.0", - "tower 0.5.3", - "tower-http 0.6.8", - "tracing", - "uuid", - "zeroclaw-api", - "zeroclaw-channels", - "zeroclaw-config", - "zeroclaw-hardware", - "zeroclaw-infra", - "zeroclaw-memory", - "zeroclaw-providers", - "zeroclaw-runtime", - "zeroclaw-tools", + "lzma-sys", ] [[package]] -name = "zeroclaw-hardware" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ - "aardvark-sys", - "anyhow", - "async-trait", - "directories", - "glob", - "portable-atomic", - "reqwest 0.12.28", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", - "toml 1.1.2+spec-1.1.0", - "tracing", - "uuid", - "zeroclaw-api", - "zeroclaw-config", - "zeroclaw-tools", + "linked-hash-map", ] [[package]] -name = "zeroclaw-infra" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ - "anyhow", - "chrono", - "parking_lot", - "portable-atomic", - "rusqlite", "serde", - "serde_json", - "tokio", - "tracing", - "zeroclaw-api", + "stable_deref_trait", + "yoke-derive 0.7.5", + "zerofrom", ] [[package]] -name = "zeroclaw-macros" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "stable_deref_trait", + "yoke-derive 0.8.2", + "zerofrom", ] [[package]] -name = "zeroclaw-memory" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ - "anyhow", - "async-trait", - "chrono", - "parking_lot", - "regex", - "reqwest 0.12.28", - "rusqlite", - "serde", - "serde_json", - "sha2 0.10.9", - "tokio", - "tracing", - "uuid", - "zeroclaw-api", - "zeroclaw-config", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", ] [[package]] -name = "zeroclaw-providers" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "chacha20poly1305", - "chrono", - "directories", - "futures-util", - "hex", - "hmac 0.12.1", - "parking_lot", - "rand 0.10.1", - "regex", - "reqwest 0.12.28", - "ring", - "serde", - "serde_json", - "sha2 0.10.9", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "uuid", - "zeroclaw-api", - "zeroclaw-config", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", ] [[package]] -name = "zeroclaw-runtime" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" dependencies = [ - "aardvark-sys", - "anyhow", + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", "async-trait", - "base64 0.22.1", - "chacha20poly1305", - "chrono", - "chrono-tz 0.10.4", - "console", - "cron 0.15.0", - "dialoguer", - "directories", - "flate2", - "futures-util", - "glob", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", "hex", - "hmac 0.12.1", - "hostname", - "image", - "indicatif", "libc", - "lru 0.16.4", - "nanohtml2text", - "parking_lot", - "portable-atomic", - "prometheus", - "rand 0.10.1", - "regex", - "reqwest 0.12.28", - "ring", - "rumqttc", - "rusqlite", - "rustls 0.23.38", - "rustls-pemfile", - "rustls-pki-types", - "schemars 1.2.1", + "ordered-stream", + "rustix 1.1.4", "serde", - "serde_json", - "sha2 0.10.9", - "shellexpand", - "tar", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-rustls 0.26.4", - "tokio-stream", - "tokio-tungstenite 0.29.0", - "tokio-util", - "toml 1.1.2+spec-1.1.0", + "serde_repr", "tracing", - "urlencoding", + "uds_windows", "uuid", - "webpki-roots 1.0.7", - "which 8.0.2", - "zeroclaw-api", - "zeroclaw-config", - "zeroclaw-infra", - "zeroclaw-macros", - "zeroclaw-memory", - "zeroclaw-providers", - "zeroclaw-tool-call-parser", - "zeroclaw-tools", - "zip 8.5.1", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] -name = "zeroclaw-tool-call-parser" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" dependencies = [ - "regex", - "serde", - "serde_json", - "tracing", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", ] [[package]] -name = "zeroclaw-tools" -version = "0.7.0" -source = "git+https://github.com/zeroclaw-labs/zeroclaw.git?rev=30395e909f660c9a6ec3075df1f70d84077a96d0#30395e909f660c9a6ec3075df1f70d84077a96d0" +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "chrono", - "directories", - "futures-util", - "glob", - "hex", - "nanohtml2text", - "parking_lot", - "regex", - "reqwest 0.12.28", "serde", - "serde_json", - "sha2 0.10.9", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.29.0", - "tokio-util", - "toml 1.1.2+spec-1.1.0", - "tracing", - "urlencoding", - "uuid", - "which 8.0.2", - "zeroclaw-api", - "zeroclaw-config", - "zeroclaw-infra", - "zeroclaw-memory", - "zeroclaw-providers", + "winnow 0.7.15", + "zvariant", ] [[package]] @@ -25211,7 +21286,7 @@ dependencies = [ "displaydoc", "flate2", "getrandom 0.3.4", - "hmac 0.12.1", + "hmac", "indexmap 2.14.0", "lzma-rs", "memchr", @@ -25237,19 +21312,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "zip" -version = "8.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59" -dependencies = [ - "crc32fast", - "flate2", - "indexmap 2.14.0", - "memchr", - "typed-path", -] - [[package]] name = "zlib-rs" version = "0.5.5" diff --git a/Cargo.toml b/Cargo.toml index 8f32839ab0..7e350848be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,6 @@ debug = false [workspace] resolver = "2" members = [ - "apps/api", - "apps/claw", - "apps/cli", "apps/desktop/src-tauri", "crates/*", "legacy/*", @@ -26,38 +23,13 @@ exclude = [ ] [workspace.dependencies] -hypr-activity-capture = { path = "crates/activity-capture", package = "activity-capture" } -hypr-activity-capture-interface = { path = "crates/activity-capture-interface", package = "activity-capture-interface" } -hypr-activity-capture-macos = { path = "crates/activity-capture-macos", package = "activity-capture-macos" } -hypr-activity-capture-windows = { path = "crates/activity-capture-windows", package = "activity-capture-windows" } hypr-aec = { path = "crates/aec", package = "aec" } hypr-afconvert = { path = "crates/afconvert", package = "afconvert" } -hypr-agc = { path = "crates/agc", package = "agc" } hypr-agent-core = { path = "crates/agent-core", package = "agent-core" } hypr-am = { path = "crates/am", package = "am" } -hypr-amp = { path = "crates/amp", package = "amp" } hypr-analytics = { path = "crates/analytics", package = "analytics" } -hypr-api-agent = { path = "crates/api-agent", package = "api-agent" } -hypr-api-auth = { path = "crates/api-auth", package = "api-auth" } -hypr-api-bot = { path = "crates/api-bot", package = "api-bot" } -hypr-api-cactus = { path = "crates/api-cactus", package = "api-cactus" } -hypr-api-calendar = { path = "crates/api-calendar", package = "api-calendar" } -hypr-api-claw = { path = "crates/api-claw", package = "api-claw" } hypr-api-client = { path = "crates/api-client", package = "api-client" } -hypr-api-env = { path = "crates/api-env", package = "api-env" } -hypr-api-error = { path = "crates/api-error", package = "api-error" } -hypr-api-mail = { path = "crates/api-mail", package = "api-mail" } -hypr-api-messenger = { path = "crates/api-messenger", package = "api-messenger" } -hypr-api-nango = { path = "crates/api-nango", package = "api-nango" } -hypr-api-pyannote = { path = "crates/api-pyannote", package = "api-pyannote" } -hypr-api-research = { path = "crates/api-research", package = "api-research" } -hypr-api-storage = { path = "crates/api-storage", package = "api-storage" } -hypr-api-subscription = { path = "crates/api-subscription", package = "api-subscription" } -hypr-api-support = { path = "crates/api-support", package = "api-support" } -hypr-api-sync = { path = "crates/api-sync", package = "api-sync" } -hypr-api-ticket = { path = "crates/api-ticket", package = "api-ticket" } hypr-apple-calendar = { path = "crates/apple-calendar", package = "apple-calendar" } -hypr-apple-note = { path = "crates/apple-note", package = "apple-note" } hypr-apple-todo = { path = "crates/apple-todo", package = "apple-todo" } hypr-askama-utils = { path = "crates/askama-utils", package = "askama-utils" } hypr-audacity = { path = "crates/audacity", package = "audacity" } @@ -78,16 +50,13 @@ hypr-cactus = { path = "crates/cactus", package = "cactus" } hypr-cactus-model = { path = "crates/cactus-model", package = "cactus-model" } hypr-calendar = { path = "crates/calendar", package = "calendar" } hypr-calendar-interface = { path = "crates/calendar-interface", package = "calendar-interface" } -hypr-chatwoot = { path = "crates/chatwoot", package = "chatwoot" } hypr-claude = { path = "crates/claude", package = "claude" } hypr-cli-process = { path = "crates/cli-process", package = "cli-process" } hypr-cloudsync = { path = "crates/cloudsync", package = "cloudsync" } hypr-codex = { path = "crates/codex", package = "codex" } -hypr-cursor = { path = "crates/cursor", package = "cursor" } hypr-data = { path = "crates/data", package = "data" } hypr-db-app = { path = "crates/db-app", package = "db-app" } hypr-db-change = { path = "crates/db-change", package = "db-change" } -hypr-db-cli = { path = "crates/db-cli", package = "db-cli" } hypr-db-core = { path = "crates/db-core", package = "db-core" } hypr-db-execute = { path = "crates/db-execute", package = "db-execute" } hypr-db-migrate = { path = "crates/db-migrate", package = "db-migrate" } @@ -95,13 +64,9 @@ hypr-db-reactive = { path = "crates/db-reactive", package = "db-reactive" } hypr-denoise = { path = "crates/denoise", package = "denoise" } hypr-detect = { path = "crates/detect", package = "detect" } hypr-device-monitor = { path = "crates/device-monitor", package = "device-monitor" } -hypr-devin = { path = "crates/devin", package = "devin" } hypr-dictation-ui-macos = { path = "crates/dictation-ui-macos", package = "dictation-ui-macos" } hypr-docs = { path = "crates/docs", package = "docs" } hypr-download-interface = { path = "crates/download-interface", package = "download-interface" } -hypr-embedding = { path = "crates/embedding", package = "embedding" } -hypr-exa = { path = "crates/exa", package = "exa" } -hypr-exedev = { path = "crates/exedev", package = "exedev" } hypr-export-core = { path = "crates/export-core", package = "export-core" } hypr-file = { path = "crates/file", package = "file" } hypr-frontmatter = { path = "crates/frontmatter", package = "frontmatter" } @@ -111,83 +76,54 @@ hypr-gbnf = { path = "crates/gbnf", package = "gbnf" } hypr-gguf = { path = "crates/gguf", package = "gguf" } hypr-github-issues = { path = "crates/github-issues", package = "github-issues" } hypr-google-calendar = { path = "crates/google-calendar", package = "google-calendar" } -hypr-google-drive = { path = "crates/google-drive", package = "google-drive" } -hypr-google-mail = { path = "crates/google-mail", package = "google-mail" } hypr-granola = { path = "crates/granola", package = "granola" } -hypr-hf = { path = "crates/hf", package = "hf" } -hypr-hid-host = { path = "crates/hid-host", package = "hid-host" } -hypr-hid-interface = { path = "crates/hid-interface", package = "hid-interface" } hypr-hooks = { path = "crates/hooks", package = "hooks" } hypr-host = { path = "crates/host", package = "host" } hypr-http = { path = "crates/http", package = "hypr-http-utils" } hypr-importer-core = { path = "crates/importer-core", package = "importer-core" } hypr-intercept = { path = "crates/intercept", package = "intercept" } -hypr-jina = { path = "crates/jina", package = "jina" } hypr-language = { path = "crates/language", package = "language" } -hypr-linear = { path = "crates/linear", package = "linear" } hypr-listener-core = { path = "crates/listener-core", package = "listener-core" } hypr-listener2-core = { path = "crates/listener2-core", package = "listener2-core" } hypr-llm-cactus = { path = "crates/llm-cactus", package = "llm-cactus" } -hypr-llm-proxy = { path = "crates/llm-proxy", package = "llm-proxy" } hypr-llm-types = { path = "crates/llm-types", package = "llm-types" } hypr-lmstudio = { path = "crates/lmstudio", package = "lmstudio" } hypr-local-llm-core = { path = "crates/local-llm-core", package = "local-llm-core" } hypr-local-model = { path = "crates/local-model", package = "local-model" } -hypr-local-stt-core = { path = "crates/local-stt-core", package = "local-stt-core" } -hypr-local-stt-server = { path = "crates/local-stt-server", package = "local-stt-server" } -hypr-loops = { path = "crates/loops", package = "loops" } -hypr-mac = { path = "crates/mac", package = "mac" } -hypr-mcp = { path = "crates/mcp", package = "mcp" } -hypr-mobile-bridge = { path = "crates/mobile-bridge", package = "mobile-bridge" } hypr-model-downloader = { path = "crates/model-downloader", package = "model-downloader" } hypr-model-manager = { path = "crates/model-manager", package = "model-manager" } hypr-mp3 = { path = "crates/mp3", package = "mp3" } -hypr-nango = { path = "crates/nango", package = "nango" } hypr-notification = { path = "crates/notification", package = "notification" } hypr-notification-interface = { path = "crates/notification-interface", package = "notification-interface" } -hypr-notification-macos2 = { path = "crates/notification-macos2", package = "notification-macos2" } hypr-notion = { path = "crates/notion", package = "notion" } hypr-observability = { path = "crates/observability", package = "observability" } hypr-onnx = { path = "crates/onnx", package = "onnx" } hypr-opencode = { path = "crates/opencode", package = "opencode" } hypr-openrouter = { path = "crates/openrouter", package = "openrouter" } -hypr-openstatus = { path = "crates/openstatus", package = "openstatus" } hypr-outlook-calendar = { path = "crates/outlook-calendar", package = "outlook-calendar" } -hypr-porkbun = { path = "crates/porkbun", package = "porkbun" } -hypr-power = { path = "crates/power", package = "power" } -hypr-pyannote-cloud = { path = "crates/pyannote-cloud", package = "pyannote-cloud" } hypr-pyannote-local = { path = "crates/pyannote-local", package = "pyannote-local" } -hypr-recall = { path = "crates/recall", package = "recall" } hypr-resampler = { path = "crates/resampler", package = "resampler" } hypr-s3 = { path = "crates/s3", package = "s3" } -hypr-screen-core = { path = "crates/screen-core", package = "screen-core" } -hypr-segmentation = { path = "crates/segmentation", package = "segmentation" } hypr-shortcut-macos = { path = "crates/shortcut-macos", package = "shortcut-macos" } -hypr-slack-web = { path = "crates/slack-web", package = "slack-web" } hypr-storage = { path = "crates/storage", package = "storage" } hypr-supabase-auth = { path = "crates/supabase-auth", package = "supabase-auth" } -hypr-supabase-storage = { path = "crates/supabase-storage", package = "supabase-storage" } hypr-tantivy-core = { path = "crates/tantivy", package = "tantivy-core" } hypr-tauri-utils = { path = "crates/tauri-utils", package = "tauri-utils" } hypr-tcc = { path = "crates/tcc", package = "tcc" } -hypr-teems = { path = "crates/teems", package = "teems" } hypr-template-app = { path = "crates/template-app", package = "template-app" } hypr-template-app-legacy = { path = "crates/template-app-legacy", package = "template-app-legacy" } -hypr-template-cli = { path = "crates/template-cli", package = "template-cli" } hypr-template-eval = { path = "crates/template-eval", package = "template-eval" } hypr-template-support = { path = "crates/template-support", package = "template-support" } hypr-ticket-interface = { path = "crates/ticket-interface", package = "ticket-interface" } hypr-tiptap = { path = "crates/tiptap", package = "tiptap" } hypr-transcribe-cactus = { path = "crates/transcribe-cactus", package = "transcribe-cactus" } hypr-transcribe-core = { path = "crates/transcribe-core", package = "transcribe-core" } -hypr-transcribe-proxy = { path = "crates/transcribe-proxy", package = "transcribe-proxy" } +hypr-transcribe-soniqo = { path = "crates/transcribe-soniqo", package = "transcribe-soniqo" } hypr-transcribe-whisper-local = { path = "crates/transcribe-whisper-local", package = "transcribe-whisper-local" } hypr-transcript = { path = "crates/transcript", package = "transcript" } hypr-transcription-core = { path = "crates/transcription-core", package = "transcription-core" } -hypr-updater-core = { path = "crates/updater-core", package = "updater-core" } hypr-vad = { path = "crates/vad", package = "vad" } hypr-vad-masking = { path = "crates/vad-masking", package = "vad-masking" } -hypr-version = { path = "crates/version", package = "version" } hypr-whisper = { path = "crates/whisper", package = "whisper" } hypr-whisper-local = { path = "crates/whisper-local", package = "whisper-local" } hypr-whisper-local-model = { path = "crates/whisper-local-model", package = "whisper-local-model" } @@ -201,7 +137,6 @@ progenitor-client = "0.13" openai-transcription = { path = "crates/openai-transcription", package = "openai-transcription" } owhisper-client = { path = "crates/owhisper-client", package = "owhisper-client" } -owhisper-config = { path = "crates/owhisper-config", package = "owhisper-config" } owhisper-interface = { path = "crates/owhisper-interface", package = "owhisper-interface" } soniox = { path = "crates/soniox", package = "soniox" } @@ -224,7 +159,6 @@ tauri-plugin-store = "2.4" tauri-plugin-updater = "2.10" tauri-plugin-window-state = "2.4" -tauri-plugin-activity-capture = { path = "plugins/activity-capture" } tauri-plugin-agent = { path = "plugins/agent" } tauri-plugin-analytics = { path = "plugins/analytics" } tauri-plugin-audio-priority = { path = "plugins/audio-priority" } @@ -258,7 +192,6 @@ tauri-plugin-overlay = { path = "plugins/overlay" } tauri-plugin-path2 = { path = "plugins/path2" } tauri-plugin-permissions = { path = "plugins/permissions" } tauri-plugin-relay = { path = "plugins/relay" } -tauri-plugin-screen = { path = "plugins/screen" } tauri-plugin-settings = { path = "plugins/settings" } tauri-plugin-sfx = { path = "plugins/sfx" } tauri-plugin-shortcut = { path = "plugins/shortcut" } @@ -271,7 +204,6 @@ tauri-plugin-tracing = { path = "plugins/tracing" } tauri-plugin-transcription = { path = "plugins/transcription" } tauri-plugin-tray = { path = "plugins/tray" } tauri-plugin-updater2 = { path = "plugins/updater2" } -tauri-plugin-webhook = { path = "plugins/webhook" } tauri-plugin-windows = { path = "plugins/windows" } async-stream = "0.3.6" diff --git a/OBSERVABILITY.md b/OBSERVABILITY.md deleted file mode 100644 index fc1d2e4671..0000000000 --- a/OBSERVABILITY.md +++ /dev/null @@ -1,542 +0,0 @@ -# Observability - -This document is the living spec for observability in this repo. - -It defines: - -- how we use OpenTelemetry -- how data is expected to appear in Honeycomb -- what `x-request-id` means -- how we use Sentry tags, contexts, and user identity -- which attribute names are allowed - -If a change introduces new tracing fields, propagation behavior, or Sentry tagging conventions, update this file in the same change. - -## Scope - -This repo has multiple binaries and runtime surfaces, but the same conventions apply everywhere: - -- `apps/api` is one OTEL service -- `apps/desktop` is one Sentry/desktop service -- internal route groups or modules are not separate OTEL services -- internal logical breakdowns use `hyprnote.subsystem` - -Current canonical subsystem values include: - -- `edge` -- `llm` -- `stt` -- `subscription` - -## Observability Stack - -We use three separate concepts: - -1. OpenTelemetry - - canonical tracing model - - canonical attribute naming model - - canonical propagation model -2. Honeycomb - - primary trace analysis backend - - expects OTEL resources, spans, and high-cardinality fields -3. Sentry - - error reporting and local debugging context - - should mirror OTEL naming where practical - -`x-request-id` is not trace propagation. It is a separate request-correlation mechanism. - -## Resource And Service Model - -### Canonical OTEL resource attributes - -Every process should set: - -- `service.namespace = "hyprnote"` -- `service.name = ` -- `service.version` -- `deployment.environment` - -Current canonical service names: - -- API: `api` -- Desktop: `desktop` - -### What counts as a service - -Use one `service.name` per deployable/runtime process. - -Do not create separate `service.name` values for: - -- axum route groups -- internal modules -- handler categories -- provider adapters - -For example, `edge`, `llm`, `stt`, and `subscription` inside `apps/api` are not separate services. They are subsystems within the `api` service. - -### Subsystems - -Use: - -- `hyprnote.subsystem` - -Examples: - -- API ingress span: `hyprnote.subsystem = "edge"` -- LLM handler span: `hyprnote.subsystem = "llm"` -- STT websocket/session spans: `hyprnote.subsystem = "stt"` - -Do not use a bare `service` span field for this. - -## Propagation - -### Canonical propagation format - -For distributed tracing, use W3C Trace Context: - -- `traceparent` -- `baggage` only when intentionally needed - -Sentry headers may also exist: - -- `sentry-trace` -- `baggage` - -But OTEL trace stitching must work through W3C propagation. - -### Rules - -Inbound requests: - -- extract remote W3C trace context -- set the server span parent from the extracted context - -Outbound requests: - -- inject current W3C trace context - -Do not use custom trace propagation headers when W3C exists. - -### Current implementation - -Rust shared helpers live in: - -- [`crates/observability/src/lib.rs`](/Users/yujonglee/dev/char/crates/observability/src/lib.rs) - -API ingress extraction and root HTTP span setup live in: - -- [`apps/api/src/main.rs`](/Users/yujonglee/dev/char/apps/api/src/main.rs) - -Desktop request header creation lives in: - -- [`apps/desktop/src/shared/utils.ts`](/Users/yujonglee/dev/char/apps/desktop/src/shared/utils.ts) -- [`apps/desktop/src/ai/traced-fetch.ts`](/Users/yujonglee/dev/char/apps/desktop/src/ai/traced-fetch.ts) -- [`apps/desktop/src/auth/context.tsx`](/Users/yujonglee/dev/char/apps/desktop/src/auth/context.tsx) - -### Baggage policy - -Do not put user identity or device identifiers into baggage by default. - -In particular, do not propagate: - -- `enduser.id` -- `enduser.pseudo.id` -- device fingerprints - -as baggage unless there is an explicit need and a privacy review. - -## Request ID - -### Meaning - -`x-request-id` is a correlation ID for support, logs, and local debugging. - -It is not: - -- a trace ID -- a span ID -- a substitute for `traceparent` - -### Rules - -- generate it once at ingress if missing -- forward it unchanged when useful -- record it as `hyprnote.request.id` -- keep it semantically separate from OTEL trace context - -Never do this: - -- `x-request-id = trace_id` -- reconstruct trace relationships from `x-request-id` - -### Current implementation - -API ingress uses request-id middleware and records the value on the root span: - -- [`apps/api/src/main.rs`](/Users/yujonglee/dev/char/apps/api/src/main.rs) - -Desktop client requests add `x-request-id` separately from `traceparent`: - -- [`apps/desktop/src/ai/traced-fetch.ts`](/Users/yujonglee/dev/char/apps/desktop/src/ai/traced-fetch.ts) -- [`apps/desktop/src/auth/context.tsx`](/Users/yujonglee/dev/char/apps/desktop/src/auth/context.tsx) - -## Naming Rules - -### Rule 1: Prefer OTEL semantic conventions - -If OTEL defines a field for the concept, use the OTEL field. - -Examples: - -- `service.namespace` -- `service.name` -- `http.request.method` -- `http.route` -- `http.response.status_code` -- `url.path` -- `enduser.id` -- `enduser.pseudo.id` -- `error.type` -- `error.message` -- `error.code` -- `service.peer.name` -- `gen_ai.operation.name` -- `gen_ai.provider.name` -- `gen_ai.request.model` -- `gen_ai.response.model` -- `gen_ai.response.id` -- `gen_ai.usage.input_tokens` -- `gen_ai.usage.output_tokens` - -### Rule 2: Custom fields must use `hyprnote.*` - -If OTEL does not define a field, use: - -- `hyprnote.*` - -Do not use: - -- `app.*` -- bare ad hoc names like `service`, `provider`, `status`, `session_id`, `user_id` - -We avoid `app.*` because OpenTelemetry owns that namespace. - -### Rule 3: One concept, one name - -If a concept already has an approved key, reuse it everywhere: - -- OTEL spans -- tracing logs/events -- Sentry tags -- Sentry contexts - -Do not rename the same concept differently per backend. - -## Canonical Field Families - -### Identity - -- `enduser.id` -- `enduser.pseudo.id` - -Use: - -- `enduser.id` for authenticated user ID -- `enduser.pseudo.id` for device fingerprint or other stable pseudonymous device identity - -### Request and duration - -- `hyprnote.request.id` -- `hyprnote.duration_ms` -- `hyprnote.retry.delay_ms` -- `hyprnote.timeout_s` -- `hyprnote.timeout.elapsed` - -### HTTP and routing - -- `http.request.method` -- `http.route` -- `http.response.status_code` -- `url.path` -- `url.full` when needed -- `otel.kind` -- `otel.name` - -Ingress HTTP spans should be `otel.kind = "server"`. - -### LLM - -Use OTEL GenAI fields where available: - -- `gen_ai.operation.name` -- `gen_ai.provider.name` -- `gen_ai.request.model` -- `gen_ai.response.model` -- `gen_ai.response.id` -- `gen_ai.usage.input_tokens` -- `gen_ai.usage.output_tokens` - -Use `hyprnote.*` for Hyprnote-specific request metadata: - -- `hyprnote.gen_ai.request.streaming` -- `hyprnote.gen_ai.request.message_count` -- `hyprnote.gen_ai.request.model_candidate_count` -- `hyprnote.gen_ai.request.tool_calling` -- `hyprnote.task.name` - -### STT and audio - -Use: - -- `hyprnote.stt.provider.name` -- `hyprnote.stt.routing_strategy` -- `hyprnote.stt.model` -- `hyprnote.stt.language_codes` -- `hyprnote.stt.language_code` -- `hyprnote.stt.session.id` -- `hyprnote.stt.job.id` -- `hyprnote.stt.provider_session.id` -- `hyprnote.stt.provider_session.duration_s` -- `hyprnote.stt.provider_session.expires_at` -- `hyprnote.stt.provider.error_code` -- `hyprnote.audio.sample_rate_hz` -- `hyprnote.audio.channel_count` -- `hyprnote.audio.channel_index` -- `hyprnote.audio.size_bytes` -- `hyprnote.audio.duration_s` -- `hyprnote.audio.device` - -### Vendor-specific fields - -Keep vendor-specific fields namespaced: - -- `hyprnote.supabase.*` -- `hyprnote.stripe.*` -- `hyprnote.connection.*` -- `hyprnote.integration.*` -- `hyprnote.bot.*` - -Always prefer `service.peer.name` for the downstream system name. - -### Payload and debug-only fields - -If raw payload capture is necessary for debug logs, use: - -- `hyprnote.payload.raw` -- `hyprnote.http.response.body` -- `hyprnote.http.body_preview` - -Do not put large raw payloads on high-volume spans by default. - -## Honeycomb Conventions - -### Service breakdown - -Honeycomb service views come from OTEL resource attributes, especially: - -- `service.name` - -Because of that: - -- `apps/api` must stay one Honeycomb service: `api` -- internal analysis should use `hyprnote.subsystem` - -### High cardinality - -Honeycomb handles high-cardinality fields well. IDs are allowed when they help debugging. - -Good high-cardinality examples: - -- `hyprnote.request.id` -- `enduser.id` -- `enduser.pseudo.id` -- `gen_ai.response.id` -- `hyprnote.stt.job.id` -- provider session IDs - -Do not avoid useful IDs just because they are high cardinality. - -### Root span quality - -Server entry spans should: - -- have a remote parent if the request carries one -- set `otel.kind = "server"` -- set `otel.name` -- record HTTP route and status - -### Span field declaration rule - -When using `tracing`, declare fields up front if you plan to `record` them later. - -This matters for: - -- `#[tracing::instrument(fields(...))]` -- `tracing::info_span!(...)` - -If a field is not declared on span creation, later `span.record(...)` calls will not create a new OTEL attribute. - -## Sentry Conventions - -### Purpose - -Sentry is for: - -- errors -- crash reports -- request-local debugging context - -It is not the canonical trace schema. OTEL is. - -### Tag naming - -Reuse OTEL names when possible. - -Canonical Sentry tags include: - -- `service.namespace` -- `service.name` -- `enduser.id` -- `enduser.pseudo.id` -- `http.response.status_code` -- `error.type` -- `gen_ai.provider.name` -- `gen_ai.request.model` -- `hyprnote.gen_ai.request.streaming` -- `hyprnote.stt.provider.name` -- `hyprnote.stt.routing_strategy` -- `hyprnote.stt.model` -- `hyprnote.stt.language_codes` - -### Context naming - -Use contexts for structured objects that are too rich for tags. - -Canonical context names include: - -- `gen_ai.request` -- `gen_ai.response` -- `hyprnote.stt.request` -- `hyprnote.enduser.claims` -- `hyprnote.session` - -### Sentry user - -Set `scope.set_user(...)` when identity is available. - -API: - -- authenticated requests use the auth subject as the Sentry user ID - -Desktop: - -- use a pseudonymous device identity when no authenticated user exists yet - -### Alignment rule - -Do not invent Sentry-only field names for concepts that already exist in OTEL unless Sentry forces it. - -Good: - -- `enduser.id` -- `service.name` -- `error.type` - -Bad: - -- `user_id` -- `service` -- `upstream.status` -- `llm.model` when `gen_ai.request.model` already exists - -## Error Conventions - -Use: - -- `error.type` for machine-readable classification -- `error.message` for the display/debug message -- `error.code` when an external or protocol code exists - -Examples: - -- provider returned a structured error code -- timeout class -- invalid payload class - -Avoid ad hoc variants such as: - -- `message` -- `error` -- `error_type` -- `error_code` - -## Header Conventions - -Canonical headers used in this repo: - -- `traceparent` -- `baggage` -- `sentry-trace` -- `x-request-id` -- `x-device-fingerprint` - -Meaning: - -- `traceparent`: canonical trace propagation -- `baggage`: optional propagation metadata, usually originating from Sentry on desktop HTTP requests -- `sentry-trace`: Sentry tracing integration -- `x-request-id`: request correlation only -- `x-device-fingerprint`: local pseudonymous device identifier - -## What To Do When Adding Instrumentation - -1. Decide whether the concept already has an OTEL semantic convention. -2. If yes, use the OTEL field name. -3. If no, add a `hyprnote.*` field. -4. If the field will be recorded later on a span, declare it at span creation. -5. If the code crosses a network boundary, extract or inject W3C trace context. -6. If request correlation is needed, keep `x-request-id` separate from trace propagation. -7. Mirror the most important fields into Sentry tags or contexts using the same names. -8. Update this file if you introduce a new field family or a new rule. - -## Anti-Patterns - -Do not do any of the following: - -- per-span `service = "llm"` style fields -- `x-request-id = trace_id` -- custom propagation instead of W3C trace context -- `app.*` custom fields -- different names for the same concept across OTEL and Sentry -- stuffing user identity into baggage by default -- creating new span attributes with `span.record` without declaring them first -- using route groups as separate Honeycomb services - -## Current Reference Points - -The current implementation that this spec describes is centered in: - -- [`apps/api/src/observability.rs`](/Users/yujonglee/dev/char/apps/api/src/observability.rs) -- [`apps/api/src/main.rs`](/Users/yujonglee/dev/char/apps/api/src/main.rs) -- [`apps/api/src/auth.rs`](/Users/yujonglee/dev/char/apps/api/src/auth.rs) -- [`crates/observability/src/lib.rs`](/Users/yujonglee/dev/char/crates/observability/src/lib.rs) -- [`crates/llm-proxy/src/handler/mod.rs`](/Users/yujonglee/dev/char/crates/llm-proxy/src/handler/mod.rs) -- [`crates/llm-proxy/src/handler/non_streaming.rs`](/Users/yujonglee/dev/char/crates/llm-proxy/src/handler/non_streaming.rs) -- [`crates/llm-proxy/src/handler/streaming.rs`](/Users/yujonglee/dev/char/crates/llm-proxy/src/handler/streaming.rs) -- [`crates/transcribe-proxy/src/routes/streaming/mod.rs`](/Users/yujonglee/dev/char/crates/transcribe-proxy/src/routes/streaming/mod.rs) -- [`crates/transcribe-proxy/src/routes/streaming/session.rs`](/Users/yujonglee/dev/char/crates/transcribe-proxy/src/routes/streaming/session.rs) -- [`apps/desktop/src/shared/utils.ts`](/Users/yujonglee/dev/char/apps/desktop/src/shared/utils.ts) -- [`apps/desktop/src/ai/traced-fetch.ts`](/Users/yujonglee/dev/char/apps/desktop/src/ai/traced-fetch.ts) -- [`apps/desktop/src/auth/context.tsx`](/Users/yujonglee/dev/char/apps/desktop/src/auth/context.tsx) -- [`apps/desktop/src-tauri/src/lib.rs`](/Users/yujonglee/dev/char/apps/desktop/src-tauri/src/lib.rs) - -## Change Policy - -Treat this document as normative. - -If code and this file disagree: - -- update the code to match this spec, or -- update this spec in the same change with a deliberate rationale - -Do not let drift accumulate. diff --git a/README.md b/README.md index 131531f806..d6e4fcd182 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Granola, rearranged. Download the latest release for your platform: -→ [github.com/fastrepl/char/releases/latest](https://github.com/fastrepl/char/releases/latest) +Download for Apple Silicon · Download for Apple Intel Open it. Join a meeting. anarlog records, transcribes locally, and saves your notes as markdown on disk. Bring your own LLM (OpenAI, Anthropic, Gemini, OpenRouter, Ollama, LM Studio, anything OpenAI-compatible). diff --git a/Taskfile.yaml b/Taskfile.yaml index 57b85c518e..51d5f81a60 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -14,17 +14,6 @@ tasks: - stripe listen --skip-verify --forward-to http://localhost:8788/webhook/stripe bacon: bacon {{.CLI_ARGS}} - cli:ui: - platforms: [darwin] - cmds: - - cd apps/cli-ui && swift build -c release --arch arm64 --arch x86_64 - - cli:dev: - platforms: [darwin] - cmds: - - task: cli:ui - - cp apps/cli-ui/.build/apple/Products/Release/char-cli-ui target/debug/ - - cargo run -p cli --features standalone-macos -- {{.CLI_ARGS}} stat: btop -f hyprnote -u 500 --preset 1 clean-plugins: diff --git a/apps/api/AGENTS.md b/apps/api/AGENTS.md deleted file mode 100644 index 6adee0f3d8..0000000000 --- a/apps/api/AGENTS.md +++ /dev/null @@ -1,9 +0,0 @@ -```bash -infisical export \ - --env=dev \ - --secret-overriding=false \ - --format=dotenv \ - --output-file="apps/api/.env" \ - --projectId=87dad7b5-72a6-4791-9228-b3b86b169db1 \ - --path="/ai" -``` diff --git a/apps/api/Cargo.toml b/apps/api/Cargo.toml deleted file mode 100644 index f88733611e..0000000000 --- a/apps/api/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "api" -version = "0.1.0" -edition = "2024" - -[dependencies] -hypr-analytics = { workspace = true } -hypr-api-auth = { workspace = true } -hypr-api-cactus = { workspace = true } -hypr-api-calendar = { workspace = true } -hypr-api-env = { workspace = true } -hypr-api-mail = { workspace = true } -hypr-api-nango = { workspace = true } -hypr-api-pyannote = { workspace = true } -hypr-api-research = { workspace = true } -hypr-api-subscription = { workspace = true } -hypr-api-support = { workspace = true } -hypr-api-ticket = { workspace = true } -hypr-linear = { workspace = true } -hypr-llm-proxy = { workspace = true } -hypr-observability = { workspace = true } -hypr-transcribe-proxy = { workspace = true } -owhisper-client = { workspace = true } - -axum = { workspace = true, features = ["ws"] } -opentelemetry = { workspace = true } -opentelemetry-otlp = { workspace = true } -opentelemetry_sdk = { workspace = true } -rustls = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] } -tower-http = { workspace = true, features = ["trace", "cors", "request-id"] } -tracing = { workspace = true } -tracing-opentelemetry = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } - -reqwest = { workspace = true, features = ["json"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -url = { workspace = true } - -dotenvy = { workspace = true } -envy = { workspace = true } -governor = { workspace = true } -jsonwebtoken = { workspace = true } -sentry = { workspace = true, features = ["tower", "tower-axum-matched-path", "tracing"] } -tower = { workspace = true } -utoipa = { workspace = true } diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile deleted file mode 100644 index f0c6778018..0000000000 --- a/apps/api/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG RUST_VERSION=1.93.0 - -FROM rust:${RUST_VERSION}-bookworm AS planner -RUN apt-get update && apt-get install -y --no-install-recommends pkg-config libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/* -RUN cargo install cargo-chef --locked -WORKDIR /app -COPY Cargo.toml Cargo.lock ./ -RUN sed -i '/^members = \[/,/^\]/c\members = ["apps/api", "crates/*"]' Cargo.toml -COPY crates crates -COPY apps/api/Cargo.toml apps/api/Cargo.toml -RUN mkdir -p apps/api/src && echo "fn main() {}" > apps/api/src/main.rs -RUN cargo chef prepare --recipe-path recipe.json - -FROM rust:${RUST_VERSION}-bookworm AS build -ARG APP_VERSION -RUN apt-get update && apt-get install -y --no-install-recommends pkg-config libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/* -RUN cargo install cargo-chef --locked -ENV APP_VERSION=${APP_VERSION} -WORKDIR /app -COPY --from=planner /app/recipe.json recipe.json -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo chef cook --release --recipe-path recipe.json -p api -COPY Cargo.toml Cargo.lock ./ -RUN sed -i '/^members = \[/,/^\]/c\members = ["apps/api", "crates/*"]' Cargo.toml -COPY crates crates -COPY apps/api apps/api -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo clean -p chatwoot -p progenitor-utils -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo build --release -p api - -FROM debian:bookworm-slim AS runtime -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libssl3 && rm -rf /var/lib/apt/lists/* -RUN groupadd -g 1001 appgroup && \ - useradd -u 1001 -g appgroup -d /nonexistent -s /usr/sbin/nologin -M appuser -COPY --from=build --chown=appuser:appgroup /app/target/release/api /usr/local/bin/api -USER appuser -WORKDIR /app -EXPOSE 3001 -ENTRYPOINT ["/usr/local/bin/api"] diff --git a/apps/api/fly.toml b/apps/api/fly.toml deleted file mode 100644 index ab4530fc40..0000000000 --- a/apps/api/fly.toml +++ /dev/null @@ -1,38 +0,0 @@ -app = 'hyprnote-ai' -primary_region = 'sjc' -kill_signal = 'SIGTERM' -kill_timeout = 30 -swap_size_mb = 512 - -[deploy] -strategy = "bluegreen" - -[env] -PORT = "3001" - -[http_service] -processes = ['app'] -internal_port = 3001 -force_https = true -auto_stop_machines = 'stop' -auto_start_machines = true -min_machines_running = 1 - -[http_service.concurrency] -type = "connections" -hard_limit = 200 -soft_limit = 150 - -[[http_service.checks]] -grace_period = "20s" -interval = "15s" -method = "GET" -path = "/health" -protocol = "http" -timeout = "4s" - -[[vm]] -processes = ['app'] -memory = '1gb' -cpu_kind = 'shared' -cpus = 1 diff --git a/apps/api/src/auth.rs b/apps/api/src/auth.rs deleted file mode 100644 index fc39cdb664..0000000000 --- a/apps/api/src/auth.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::collections::BTreeMap; - -use axum::{extract::Request, middleware::Next, response::Response}; - -use hypr_api_auth::AuthContext; -pub use hypr_api_auth::{AuthState, optional_auth, require_auth}; - -const DEVICE_FINGERPRINT_HEADER: &str = "x-device-fingerprint"; - -pub async fn sentry_and_analytics(mut request: Request, next: Next) -> Response { - let span = tracing::Span::current(); - let device_fingerprint = request - .headers() - .get(DEVICE_FINGERPRINT_HEADER) - .and_then(|h| h.to_str().ok()) - .map(String::from); - - if let Some(auth) = request.extensions().get::() { - sentry::configure_scope(|scope| { - scope.set_user(Some(sentry::User { - id: Some(auth.claims.sub.clone()), - email: auth.claims.email.clone(), - username: Some(auth.claims.sub.clone()), - ..Default::default() - })); - scope.set_tag("enduser.id", &auth.claims.sub); - if let Some(fingerprint) = device_fingerprint.as_deref() { - scope.set_tag("enduser.pseudo.id", fingerprint); - } - - let mut ctx = BTreeMap::new(); - ctx.insert( - "hyprnote.enduser.entitlements".into(), - sentry::protocol::Value::Array( - auth.claims - .entitlements - .iter() - .map(|e| sentry::protocol::Value::String(e.clone())) - .collect(), - ), - ); - scope.set_context( - "hyprnote.enduser.claims", - sentry::protocol::Context::Other(ctx), - ); - }); - - let user_id = auth.claims.sub.clone(); - span.record("enduser.id", user_id.as_str()); - request - .extensions_mut() - .insert(hypr_analytics::AuthenticatedUserId(user_id)); - } - - if let Some(fingerprint) = device_fingerprint { - span.record("enduser.pseudo.id", fingerprint.as_str()); - request - .extensions_mut() - .insert(hypr_analytics::DeviceFingerprint(fingerprint)); - } - - next.run(request).await -} diff --git a/apps/api/src/env.rs b/apps/api/src/env.rs deleted file mode 100644 index f334fcb238..0000000000 --- a/apps/api/src/env.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::path::Path; -use std::sync::OnceLock; - -use envy::Error as EnvyError; -use serde::Deserialize; - -fn default_port() -> u16 { - 3001 -} - -#[derive(Deserialize)] -pub struct Env { - #[serde(default = "default_port")] - pub port: u16, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub sentry_dsn: Option, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub posthog_api_key: Option, - - #[serde(flatten)] - pub observability: crate::observability::Env, - - #[serde(flatten)] - pub supabase: hypr_api_env::SupabaseEnv, - #[serde(flatten)] - pub nango: hypr_api_env::NangoEnv, - #[serde(flatten)] - pub stripe: hypr_api_env::StripeEnv, - #[serde(flatten)] - pub pyannote: hypr_api_env::PyannoteEnv, - #[serde(flatten)] - pub github_app: hypr_api_support::GitHubAppEnv, - #[serde(flatten)] - pub support_database: hypr_api_support::SupportDatabaseEnv, - #[serde(flatten)] - pub chatwoot: hypr_api_support::ChatwootEnv, - - pub cactus_api_key: String, - pub exa_api_key: String, - pub jina_api_key: String, - - #[serde(flatten)] - pub loops: hypr_api_env::LoopsEnv, - - #[serde(flatten)] - pub llm: hypr_llm_proxy::Env, - #[serde(flatten)] - pub stt: hypr_transcribe_proxy::Env, -} - -static ENV: OnceLock = OnceLock::new(); - -pub fn env() -> &'static Env { - ENV.get_or_init(|| { - let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let repo_root = manifest_dir - .parent() - .and_then(|p| p.parent()) - .unwrap_or(manifest_dir); - - let _ = dotenvy::from_path(repo_root.join(".env.supabase")); - let _ = dotenvy::from_path(manifest_dir.join(".env")); - envy::from_env().unwrap_or_else(|error| panic!("{}", format_env_error(error))) - }) -} - -fn format_env_error(error: EnvyError) -> String { - match error { - EnvyError::MissingValue(field) => { - let env_var = field_name_to_env_var(&field); - format!("Failed to load environment: missing {env_var} (field: {field})") - } - other => format!("Failed to load environment: {other}"), - } -} - -fn field_name_to_env_var(field: &str) -> String { - field - .chars() - .flat_map(|ch| ch.to_uppercase()) - .collect::() -} diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs deleted file mode 100644 index 4bb7b88d0f..0000000000 --- a/apps/api/src/main.rs +++ /dev/null @@ -1,557 +0,0 @@ -mod auth; -mod env; -mod observability; -mod openapi; -mod rate_limit; - -use std::net::SocketAddr; -use std::num::NonZeroU32; -use std::sync::Arc; -use std::time::Duration; -use std::time::SystemTime; - -use axum::{Router, body::Body, extract::MatchedPath, http::HeaderMap, http::Request, middleware}; -use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer}; -use sentry::protocol::{Context, Value}; -use tower::ServiceBuilder; -use tower_http::{ - classify::ServerErrorsFailureClass, - cors::{self, CorsLayer}, - request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer}, - trace::TraceLayer, -}; - -use auth::AuthState; -use env::env; - -use crate::env::Env; - -pub const DEVICE_FINGERPRINT_HEADER: &str = "x-device-fingerprint"; -pub const REQUEST_ID_HEADER: &str = "x-request-id"; - -fn forwarded_header_value(headers: &HeaderMap, name: &str) -> Option { - headers - .get(name) - .and_then(|value| value.to_str().ok()) - .and_then(|value| value.split(',').next()) - .map(str::trim) - .filter(|value| !value.is_empty()) - .map(ToString::to_string) -} - -fn request_scheme(request: &Request) -> String { - forwarded_header_value(request.headers(), "x-forwarded-proto") - .or_else(|| request.uri().scheme_str().map(ToString::to_string)) - .unwrap_or_else(|| "http".to_string()) -} - -fn request_server_endpoint(request: &Request, scheme: &str) -> (Option, Option) { - let authority = forwarded_header_value(request.headers(), "x-forwarded-host") - .or_else(|| { - request - .headers() - .get("host") - .and_then(|value| value.to_str().ok()) - .map(ToString::to_string) - }) - .or_else(|| request.uri().host().map(ToString::to_string)); - let Some(authority) = authority else { - return (None, None); - }; - let authority = authority.trim(); - if authority.is_empty() { - return (None, None); - } - let Ok(url) = reqwest::Url::parse(&format!("{scheme}://{authority}")) else { - return (Some(authority.to_string()), None); - }; - let host = url.host_str().map(ToString::to_string); - let port = url.port_or_known_default(); - (host, port) -} - -fn request_client_address(request: &Request) -> Option { - forwarded_header_value(request.headers(), "x-forwarded-for") -} - -async fn app() -> Router { - let env = env(); - - let analytics = build_analytics_client(env); - - let llm_config = - hypr_llm_proxy::LlmProxyConfig::new(&env.llm).with_analytics(analytics.clone()); - let stt_config = hypr_transcribe_proxy::SttProxyConfig::new(&env.stt, &env.supabase) - .with_hyprnote_routing(hypr_transcribe_proxy::HyprnoteRoutingConfig::default()) - .with_analytics(analytics.clone()); - - let stt_rate_limit = rate_limit::RateLimitState::builder() - .pro( - governor::Quota::with_period(Duration::from_mins(5)) - .unwrap() - .allow_burst(NonZeroU32::new(20).unwrap()), - ) - .free( - governor::Quota::with_period(Duration::from_hours(24)) - .unwrap() - .allow_burst(NonZeroU32::new(3).unwrap()), - ) - .build(); - let llm_rate_limit = rate_limit::RateLimitState::builder() - .pro( - governor::Quota::with_period(Duration::from_secs(1)) - .unwrap() - .allow_burst(NonZeroU32::new(30).unwrap()), - ) - .free( - governor::Quota::with_period(Duration::from_hours(12)) - .unwrap() - .allow_burst(NonZeroU32::new(5).unwrap()), - ) - .build(); - - let auth_state_pro = AuthState::new(&env.supabase.supabase_url) - .with_required_entitlements(vec!["hyprnote_pro".into(), "hyprnote_lite".into()]); - let auth_state_basic = AuthState::new(&env.supabase.supabase_url); - let auth_state_support = AuthState::new(&env.supabase.supabase_url); - - let nango_config = hypr_api_nango::NangoConfig::new( - &env.nango, - &env.supabase, - Some(env.supabase.supabase_service_role_key.clone()), - ); - let nango_connection_state = hypr_api_nango::NangoConnectionState::from_config(&nango_config); - let subscription_config = - hypr_api_subscription::SubscriptionConfig::new(&env.supabase, &env.stripe, &env.loops) - .with_analytics(analytics.clone()); - let support_config = hypr_api_support::SupportConfig::new( - &env.github_app, - &env.llm, - &env.support_database, - &env.stripe, - &env.supabase, - &env.chatwoot, - auth_state_support.clone(), - ); - let cactus_config = hypr_api_cactus::CactusProxyConfig { - api_key: env.cactus_api_key.clone(), - upstream_base: None, - }; - let research_config = hypr_api_research::ResearchConfig { - exa_api_key: env.exa_api_key.clone(), - jina_api_key: env.jina_api_key.clone(), - }; - let pyannote_config = hypr_api_pyannote::PyannoteConfig::new(&env.pyannote); - - use hypr_api_nango::NangoIntegrationId; - - let mut forward_handlers = hypr_api_nango::ForwardHandlerRegistry::new(); - forward_handlers.insert( - hypr_api_nango::Linear::ID.to_string(), - hypr_api_nango::forward_handler(hypr_linear::webhook::handle), - ); - - let webhook_routes = Router::new() - .nest( - "/nango", - hypr_api_nango::webhook_router(nango_config.clone(), forward_handlers), - ) - .nest( - "/stt", - hypr_transcribe_proxy::callback_router(stt_config.clone()), - ); - - let auth_state_integration = - AuthState::new(&env.supabase.supabase_url).with_required_entitlement("hyprnote_pro"); - - let pro_routes = Router::new() - .merge(hypr_api_research::router(research_config)) - .nest("/pyannote", hypr_api_pyannote::router(pyannote_config)) - .route_layer(middleware::from_fn(auth::sentry_and_analytics)) - .route_layer(middleware::from_fn_with_state( - auth_state_pro, - auth::require_auth, - )); - - let integration_routes = Router::new() - .nest("/calendar", hypr_api_calendar::router()) - .nest("/mail", hypr_api_mail::router()) - .nest("/ticket", hypr_api_ticket::router()) - .nest( - "/nango", - hypr_api_nango::session_router(nango_config.clone()), - ) - .layer(axum::Extension(nango_connection_state)) - .route_layer(middleware::from_fn(auth::sentry_and_analytics)) - .route_layer(middleware::from_fn_with_state( - auth_state_integration, - auth::require_auth, - )); - - let integration_management_routes = Router::new() - .nest( - "/nango", - hypr_api_nango::management_router(nango_config.clone()), - ) - .route_layer(middleware::from_fn(auth::sentry_and_analytics)) - .route_layer(middleware::from_fn_with_state( - auth_state_basic.clone(), - auth::require_auth, - )); - - let stt_routes = Router::new() - .merge(hypr_transcribe_proxy::listen_router(stt_config.clone())) - .nest("/stt", hypr_transcribe_proxy::router(stt_config)) - .route_layer(middleware::from_fn_with_state( - stt_rate_limit, - rate_limit::rate_limit, - )); - - let llm_routes = Router::new() - .merge(hypr_llm_proxy::chat_completions_router(llm_config.clone())) - .nest("/llm", hypr_llm_proxy::router(llm_config)) - .route_layer(middleware::from_fn_with_state( - llm_rate_limit, - rate_limit::rate_limit, - )); - - let subscription_router = hypr_api_subscription::router(subscription_config); - let auth_routes = Router::new() - .merge(stt_routes) - .merge(llm_routes) - .nest("/subscription", subscription_router.clone()) - .nest("/rpc", subscription_router.clone()) - .nest("/billing", subscription_router) - .route_layer(middleware::from_fn(auth::sentry_and_analytics)) - .route_layer(middleware::from_fn_with_state( - auth_state_basic, - auth::require_auth, - )); - - let support_routes = Router::new() - .merge(hypr_api_support::router(support_config).await) - .layer(middleware::from_fn_with_state( - auth_state_support.clone(), - auth::optional_auth, - )); - - Router::new() - .route("/health", axum::routing::get(version)) - .route("/openapi.json", axum::routing::get(openapi_json)) - .nest("/cactus", hypr_api_cactus::router(cactus_config)) - .merge(support_routes) - .merge(webhook_routes) - .merge(pro_routes) - .merge(integration_routes) - .merge(integration_management_routes) - .merge(auth_routes) - .layer( - CorsLayer::new() - .allow_origin(cors::Any) - .allow_methods(cors::Any) - .allow_headers(cors::Any) - .expose_headers([axum::http::header::HeaderName::from_static( - REQUEST_ID_HEADER, - )]), - ) - .layer( - ServiceBuilder::new() - .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid)) - .layer(PropagateRequestIdLayer::x_request_id()) - .layer(NewSentryLayer::>::new_from_top()) - .layer(SentryHttpLayer::new().enable_transaction()) - .layer( - TraceLayer::new_for_http() - .make_span_with(|request: &Request| { - let path = request.uri().path(); - - if path == "/health" { - return tracing::Span::none(); - } - - let method = request.method(); - let matched_path = request - .extensions() - .get::() - .map(MatchedPath::as_str) - .unwrap_or(path); - let scheme = request_scheme(request); - let (server_address, server_port) = - request_server_endpoint(request, &scheme); - let client_address = request_client_address(request); - let span_op = match path { - p if p.starts_with("/llm") - || p.starts_with("/chat/completions") => - { - "http.server.llm" - } - p if p.starts_with("/stt") || p.starts_with("/listen") => { - "http.server.stt" - } - _ => "http.server", - }; - - let span = tracing::info_span!( - "http_request", - http.request.method = %method, - http.route = %matched_path, - url.path = %path, - url.scheme = %scheme, - http.response.status_code = tracing::field::Empty, - server.address = tracing::field::Empty, - server.port = tracing::field::Empty, - client.address = tracing::field::Empty, - hyprnote.subsystem = "edge", - enduser.id = tracing::field::Empty, - enduser.pseudo.id = tracing::field::Empty, - hyprnote.stt.provider.name = tracing::field::Empty, - hyprnote.stt.routing_strategy = tracing::field::Empty, - hyprnote.stt.model = tracing::field::Empty, - hyprnote.stt.language_codes = tracing::field::Empty, - hyprnote.audio.sample_rate_hz = tracing::field::Empty, - hyprnote.audio.channel_count = tracing::field::Empty, - gen_ai.provider.name = tracing::field::Empty, - hyprnote.gen_ai.request.streaming = tracing::field::Empty, - hyprnote.gen_ai.request.message_count = tracing::field::Empty, - hyprnote.request.id = tracing::field::Empty, - error.type = tracing::field::Empty, - otel.status_code = tracing::field::Empty, - otel.kind = "server", - otel.name = %format!("{} {}", method, matched_path), - span.op = %span_op, - ); - if let Some(server_address) = server_address.as_deref() { - span.record("server.address", server_address); - } - if let Some(server_port) = server_port { - span.record("server.port", server_port as i64); - } - if let Some(client_address) = client_address.as_deref() { - span.record("client.address", client_address); - } - hypr_observability::set_remote_parent(&span, request.headers()); - span - }) - .on_request(|request: &Request, span: &tracing::Span| { - // Skip logging for health checks - if request.uri().path() == "/health" { - return; - } - if let Some(request_id) = request - .headers() - .get(REQUEST_ID_HEADER) - .and_then(|v| v.to_str().ok()) - { - span.record("hyprnote.request.id", request_id); - } - configure_sentry_trace_scope(span, env, SystemTime::now()); - tracing::info!( - parent: span, - http.request.method = %request.method(), - url.path = %request.uri().path(), - "http_request_started" - ); - }) - .on_response( - |response: &axum::http::Response, - latency: std::time::Duration, - span: &tracing::Span| { - if span.is_disabled() { - return; - } - span.record( - "http.response.status_code", - response.status().as_u16() as i64, - ); - if response.status().is_server_error() { - hypr_observability::mark_span_as_error( - span, - &response.status().as_u16().to_string(), - ); - } - tracing::info!( - parent: span, - http.response.status_code = %response.status().as_u16(), - hyprnote.duration_ms = %latency.as_millis(), - "http_request_finished" - ); - }, - ) - .on_failure( - |failure_class: ServerErrorsFailureClass, - latency: std::time::Duration, - span: &tracing::Span| { - if span.is_disabled() { - return; - } - let error_type = match &failure_class { - ServerErrorsFailureClass::StatusCode(status) => { - status.as_u16().to_string() - } - ServerErrorsFailureClass::Error(_) => { - "http_server_failure".to_string() - } - }; - hypr_observability::mark_span_as_error(span, error_type.as_str()); - tracing::error!( - parent: span, - error.type = %error_type, - error = %failure_class, - hyprnote.duration_ms = %latency.as_millis(), - "http_request_failed" - ); - }, - ), - ), - ) -} - -fn build_analytics_client(env: &Env) -> Arc { - let mut builder = hypr_analytics::AnalyticsClientBuilder::default(); - if cfg!(debug_assertions) { - tracing::info!("analytics: dev mode, printing events as tracing"); - } else { - let key = env - .posthog_api_key - .as_ref() - .expect("POSTHOG_API_KEY is required in production"); - builder = builder.with_posthog(key); - } - Arc::new(builder.build()) -} - -fn main() -> std::io::Result<()> { - rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .expect("failed to install rustls crypto provider"); - - let _ = openapi::write_openapi_json(); - - let env = env(); - - let _guard = sentry::init(sentry::ClientOptions { - dsn: env.sentry_dsn.as_ref().and_then(|s| s.parse().ok()), - release: option_env!("APP_VERSION").map(|v| format!("hyprnote-api@{}", v).into()), - environment: Some( - if cfg!(debug_assertions) { - "development" - } else { - "production" - } - .into(), - ), - traces_sample_rate: 1.0, - sample_rate: 1.0, - send_default_pii: true, - auto_session_tracking: true, - session_mode: sentry::SessionMode::Request, - attach_stacktrace: true, - max_breadcrumbs: 100, - ..Default::default() - }); - - sentry::configure_scope(|scope| { - scope.set_tag("service.namespace", "hyprnote"); - scope.set_tag("service.name", "api"); - }); - - let observability = observability::init("api", &env.observability); - - hypr_transcribe_proxy::ApiKeys::from(&env.stt.stt).log_configured_providers(); - - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()? - .block_on(async { - let addr = SocketAddr::from(([0, 0, 0, 0], env.port)); - tracing::info!(addr = %addr, "server_listening"); - - let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, app().await) - .with_graceful_shutdown(shutdown_signal()) - .await - .unwrap(); - }); - - if let Some(client) = sentry::Hub::current().client() { - client.close(Some(Duration::from_secs(2))); - } - observability.shutdown(); - - Ok(()) -} - -async fn shutdown_signal() { - tokio::signal::ctrl_c() - .await - .expect("failed to install CTRL+C signal handler"); - tracing::info!("shutdown_signal_received"); -} - -async fn openapi_json() -> axum::Json { - axum::Json(openapi::openapi()) -} - -async fn version() -> &'static str { - option_env!("APP_VERSION").unwrap_or("unknown") -} - -fn configure_sentry_trace_scope(span: &tracing::Span, env: &Env, request_started_at: SystemTime) { - let Some(trace_identifiers) = hypr_observability::span_identifiers(span) else { - return; - }; - - let trace_url = build_honeycomb_trace_url(env, &trace_identifiers, request_started_at); - sentry::configure_scope(|scope| { - scope.set_tag( - "hyprnote.honeycomb.trace_id", - trace_identifiers.trace_id.as_str(), - ); - scope.set_tag( - "hyprnote.honeycomb.span_id", - trace_identifiers.span_id.as_str(), - ); - if let Some(trace_url) = trace_url.as_deref() { - scope.set_tag("hyprnote.honeycomb.trace_url", trace_url); - } - - let mut context = std::collections::BTreeMap::new(); - context.insert("trace_id".into(), Value::String(trace_identifiers.trace_id)); - context.insert("span_id".into(), Value::String(trace_identifiers.span_id)); - if let Some(trace_url) = trace_url { - context.insert("trace_url".into(), Value::String(trace_url)); - } - scope.set_context("hyprnote.honeycomb", Context::Other(context)); - }); -} - -fn build_honeycomb_trace_url( - env: &Env, - trace_identifiers: &hypr_observability::TraceIdentifiers, - request_started_at: SystemTime, -) -> Option { - let team = env.observability.honeycomb_ui_team.as_deref()?; - let environment = env.observability.honeycomb_ui_environment.as_deref()?; - let base_url = env - .observability - .honeycomb_ui_base_url - .as_deref() - .unwrap_or("https://ui.honeycomb.io") - .trim_end_matches('/'); - let trace_start_ts = request_started_at - .duration_since(SystemTime::UNIX_EPOCH) - .ok()? - .as_secs() - .to_string(); - - let mut url = url::Url::parse(&format!( - "{base_url}/{team}/environments/{environment}/trace" - )) - .ok()?; - url.query_pairs_mut() - .append_pair("trace_id", trace_identifiers.trace_id.as_str()) - .append_pair("span", trace_identifiers.span_id.as_str()) - .append_pair("trace_start_ts", trace_start_ts.as_str()); - - Some(url.into()) -} diff --git a/apps/api/src/observability.rs b/apps/api/src/observability.rs deleted file mode 100644 index 55ada0aad5..0000000000 --- a/apps/api/src/observability.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::collections::HashMap; - -use opentelemetry::KeyValue; -use opentelemetry::global; -use opentelemetry::trace::TracerProvider as _; -use opentelemetry_otlp::WithExportConfig; -use opentelemetry_otlp::WithHttpConfig; -use opentelemetry_sdk::Resource; -use opentelemetry_sdk::trace::SdkTracerProvider; -use serde::Deserialize; -use tracing_subscriber::prelude::*; - -#[derive(Deserialize)] -pub struct Env { - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub otel_service_name: Option, - #[serde(flatten)] - direct: DirectHoneycombEnv, - #[serde(flatten)] - collector: OtelCollectorEnv, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub honeycomb_ui_base_url: Option, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub honeycomb_ui_team: Option, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - pub honeycomb_ui_environment: Option, -} - -#[derive(Deserialize)] -struct DirectHoneycombEnv { - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - honeycomb_api_key: Option, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - honeycomb_api_endpoint: Option, - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - honeycomb_dataset: Option, -} - -#[derive(Deserialize)] -struct OtelCollectorEnv { - #[serde(default, deserialize_with = "hypr_api_env::filter_empty")] - otel_exporter_otlp_endpoint: Option, -} - -pub struct ObservabilityGuard { - otel_provider: Option, -} - -pub fn init(service_name: &str, env: &Env) -> ObservabilityGuard { - hypr_observability::install_trace_context_propagator(); - let otel_provider = init_otel_tracer_provider(service_name, env); - let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "info,tower_http=debug".into()); - - if let Some(provider) = otel_provider.as_ref() { - let tracer = provider.tracer(service_name.to_string()); - tracing_subscriber::registry() - .with(env_filter) - .with(tracing_subscriber::fmt::layer()) - .with(tracing_opentelemetry::layer().with_tracer(tracer)) - .with(sentry::integrations::tracing::layer()) - .init(); - } else { - tracing_subscriber::registry() - .with(env_filter) - .with(tracing_subscriber::fmt::layer()) - .with(sentry::integrations::tracing::layer()) - .init(); - } - - ObservabilityGuard { otel_provider } -} - -impl ObservabilityGuard { - pub fn shutdown(self) { - if let Some(provider) = self.otel_provider - && let Err(e) = provider.shutdown() - { - tracing::warn!(error = %e, "otel_tracer_shutdown_failed"); - } - } -} - -fn init_otel_tracer_provider(service_name: &str, env: &Env) -> Option { - let export_config = trace_export_config(env)?; - - let exporter_builder = opentelemetry_otlp::SpanExporter::builder() - .with_http() - .with_endpoint(trace_export_endpoint(&export_config.endpoint)) - .with_headers(export_config.headers); - let exporter = exporter_builder.build().ok()?; - - let configured_service_name = env - .otel_service_name - .clone() - .unwrap_or_else(|| service_name.to_string()); - let environment = if cfg!(debug_assertions) { - "development" - } else { - "production" - }; - let version = option_env!("APP_VERSION").unwrap_or("unknown"); - - let resource = Resource::builder_empty() - .with_attributes([ - KeyValue::new("service.namespace", "hyprnote"), - KeyValue::new("service.name", configured_service_name), - KeyValue::new("service.version", version.to_string()), - KeyValue::new("deployment.environment", environment), - ]) - .build(); - - let provider = SdkTracerProvider::builder() - .with_batch_exporter(exporter) - .with_resource(resource) - .build(); - - global::set_tracer_provider(provider.clone()); - Some(provider) -} - -struct TraceExportConfig { - endpoint: String, - headers: HashMap, -} - -fn trace_export_config(env: &Env) -> Option { - if let Some(config) = env.direct.trace_export_config() { - return Some(config); - } - - env.collector.trace_export_config() -} - -impl DirectHoneycombEnv { - fn trace_export_config(&self) -> Option { - let api_key = self.honeycomb_api_key.clone()?; - let mut headers = HashMap::from([("x-honeycomb-team".to_string(), api_key)]); - if let Some(dataset) = self.honeycomb_dataset.clone() { - headers.insert("x-honeycomb-dataset".to_string(), dataset); - } - - Some(TraceExportConfig { - endpoint: normalize_endpoint( - self.honeycomb_api_endpoint - .as_deref() - .unwrap_or("https://api.honeycomb.io"), - "https", - ), - headers, - }) - } -} - -impl OtelCollectorEnv { - fn trace_export_config(&self) -> Option { - Some(TraceExportConfig { - endpoint: normalize_endpoint(self.otel_exporter_otlp_endpoint.as_deref()?, "http"), - headers: HashMap::new(), - }) - } -} - -fn normalize_endpoint(endpoint: &str, default_scheme: &str) -> String { - if endpoint.contains("://") { - endpoint.to_string() - } else { - format!("{default_scheme}://{endpoint}") - } -} - -fn trace_export_endpoint(base_url: &str) -> String { - let base_url = base_url.trim_end_matches('/'); - if base_url.ends_with("/v1/traces") { - return base_url.to_string(); - } - - format!("{base_url}/v1/traces") -} diff --git a/apps/api/src/openapi.rs b/apps/api/src/openapi.rs deleted file mode 100644 index da565574de..0000000000 --- a/apps/api/src/openapi.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::collections::BTreeMap; - -use utoipa::openapi::path::{Operation, PathItem}; -use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityRequirement, SecurityScheme}; -use utoipa::{Modify, OpenApi}; - -#[derive(OpenApi)] -#[openapi( - info( - title = "Char AI API", - version = "1.0.0", - description = "AI services API for speech-to-text transcription, LLM chat completions, and subscription management" - ), - tags( - (name = "stt", description = "Speech-to-text transcription endpoints"), - (name = "llm", description = "LLM chat completions endpoints"), - (name = "pyannote", description = "Pyannote speaker diarization and voice processing"), - (name = "calendar", description = "Calendar management"), - (name = "mail", description = "Mail management"), - (name = "ticket", description = "Ticket management"), - (name = "nango", description = "Integration management via Nango"), - (name = "subscription", description = "Subscription and trial management") - ), - modifiers(&SecurityAddon) -)] -pub struct ApiDoc; - -pub fn openapi() -> utoipa::openapi::OpenApi { - let mut doc = ApiDoc::openapi(); - - let stt_doc = hypr_transcribe_proxy::openapi(); - let llm_doc = hypr_llm_proxy::openapi(); - let pyannote_doc = with_path_prefix(hypr_api_pyannote::openapi(), "/pyannote"); - let calendar_doc = with_path_prefix(hypr_api_calendar::openapi(), "/calendar"); - let mail_doc = with_path_prefix(hypr_api_mail::openapi(), "/mail"); - let ticket_doc = with_path_prefix(hypr_api_ticket::openapi(), "/ticket"); - let nango_doc = with_path_prefix(hypr_api_nango::openapi(), "/nango"); - let subscription_doc = with_path_prefix(hypr_api_subscription::openapi(), "/subscription"); - let support_doc = hypr_api_support::openapi(); - - doc.merge(stt_doc); - doc.merge(llm_doc); - doc.merge(pyannote_doc); - doc.merge(calendar_doc); - doc.merge(mail_doc); - doc.merge(ticket_doc); - doc.merge(nango_doc); - doc.merge(subscription_doc); - doc.merge(support_doc); - - apply_bearer_auth_to_protected_paths(&mut doc); - - doc -} - -pub fn write_openapi_json() -> std::io::Result { - let doc = openapi(); - let json = serde_json::to_string_pretty(&doc) - .map_err(|e| std::io::Error::other(format!("serialize openapi: {e}")))?; - - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("openapi.gen.json"); - std::fs::write(&path, json)?; - Ok(path) -} - -struct SecurityAddon; - -impl Modify for SecurityAddon { - fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { - if let Some(components) = openapi.components.as_mut() { - components.add_security_scheme( - "bearer_auth", - SecurityScheme::Http( - Http::builder() - .scheme(HttpAuthScheme::Bearer) - .bearer_format("JWT") - .description(Some("Supabase JWT token")) - .build(), - ), - ); - } - } -} - -fn with_path_prefix(mut doc: utoipa::openapi::OpenApi, prefix: &str) -> utoipa::openapi::OpenApi { - let prefix = prefix.trim_end_matches('/'); - if prefix.is_empty() { - return doc; - } - - let paths = std::mem::take(&mut doc.paths.paths); - - let prefixed: BTreeMap = paths - .into_iter() - .map(|(path, item)| (format!("{prefix}{path}"), item)) - .collect(); - - doc.paths.paths = prefixed; - doc -} - -fn apply_bearer_auth_to_protected_paths(doc: &mut utoipa::openapi::OpenApi) { - let paths = &mut doc.paths.paths; - - for (path, item) in paths.iter_mut() { - if path == "/nango/webhook" { - clear_operation_security(item); - continue; - } - - if path.starts_with("/calendar") - || path.starts_with("/mail") - || path.starts_with("/ticket") - || path.starts_with("/subscription") - || path.starts_with("/nango") - || path.starts_with("/pyannote") - { - set_operation_security(item); - } - } -} - -fn set_operation_security(item: &mut PathItem) { - let security = Some(vec![SecurityRequirement::new( - "bearer_auth", - Vec::::new(), - )]); - - with_each_operation(item, |op| { - op.security = security.clone(); - }); -} - -fn clear_operation_security(item: &mut PathItem) { - with_each_operation(item, |op| { - op.security = None; - }); -} - -fn with_each_operation(item: &mut PathItem, mut f: impl FnMut(&mut Operation)) { - if let Some(op) = item.get.as_mut() { - f(op); - } - if let Some(op) = item.put.as_mut() { - f(op); - } - if let Some(op) = item.post.as_mut() { - f(op); - } - if let Some(op) = item.delete.as_mut() { - f(op); - } - if let Some(op) = item.options.as_mut() { - f(op); - } - if let Some(op) = item.head.as_mut() { - f(op); - } - if let Some(op) = item.patch.as_mut() { - f(op); - } - if let Some(op) = item.trace.as_mut() { - f(op); - } -} - -#[cfg(test)] -mod tests { - fn assert_bearer(path: &utoipa::openapi::path::PathItem, method: &str) { - let operation = match method { - "get" => path.get.as_ref().unwrap(), - "post" => path.post.as_ref().unwrap(), - _ => unreachable!("unsupported method"), - }; - let security = operation.security.as_ref().unwrap(); - - assert!(security.iter().any(|item| { - serde_json::to_value(item) - .unwrap() - .get("bearer_auth") - .is_some() - })); - } - - #[test] - fn pyannote_paths_are_prefixed_and_protected() { - let doc = super::openapi(); - assert_bearer(doc.paths.paths.get("/pyannote/v1/diarize").unwrap(), "post"); - assert_bearer( - doc.paths.paths.get("/pyannote/v1/identify").unwrap(), - "post", - ); - assert_bearer( - doc.paths.paths.get("/pyannote/v1/voiceprint").unwrap(), - "post", - ); - assert!(!doc.paths.paths.contains_key("/pyannote/v1/jobs")); - assert!(!doc.paths.paths.contains_key("/pyannote/v1/jobs/{jobId}")); - assert!(!doc.paths.paths.contains_key("/pyannote/v1/media/input")); - assert!(!doc.paths.paths.contains_key("/pyannote/v1/media/output")); - assert!(!doc.paths.paths.contains_key("/pyannote/v1/test")); - } - - #[test] - fn gen_openapi_json() { - super::write_openapi_json().unwrap(); - } -} diff --git a/apps/api/src/rate_limit.rs b/apps/api/src/rate_limit.rs deleted file mode 100644 index 543ca6f457..0000000000 --- a/apps/api/src/rate_limit.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::sync::Arc; - -use axum::{ - extract::Request, - http::StatusCode, - middleware::Next, - response::{IntoResponse, Response}, -}; -use governor::{ - Quota, RateLimiter, - clock::{Clock, DefaultClock}, - state::keyed::DefaultKeyedStateStore, -}; -use hypr_api_auth::AuthContext; - -type KeyedLimiter = RateLimiter, DefaultClock>; - -#[derive(Clone)] -pub struct RateLimitState { - limiter_pro: Arc, - limiter_free: Arc, -} - -impl RateLimitState { - pub fn builder() -> RateLimitStateBuilder { - RateLimitStateBuilder { - pro: None, - free: None, - } - } -} - -pub struct RateLimitStateBuilder { - pro: Option, - free: Option, -} - -impl RateLimitStateBuilder { - pub fn pro(mut self, quota: Quota) -> Self { - self.pro = Some(quota); - self - } - - pub fn free(mut self, quota: Quota) -> Self { - self.free = Some(quota); - self - } - - pub fn build(self) -> RateLimitState { - RateLimitState { - limiter_pro: Arc::new(RateLimiter::keyed(self.pro.expect("pro quota is required"))), - limiter_free: Arc::new(RateLimiter::keyed( - self.free.expect("free quota is required"), - )), - } - } -} - -pub async fn rate_limit( - axum::extract::State(state): axum::extract::State, - request: Request, - next: Next, -) -> Result { - if cfg!(debug_assertions) { - return Ok(next.run(request).await); - } - - if let Some(auth) = request.extensions().get::() { - let limiter = if auth.claims.is_paid() { - &state.limiter_pro - } else { - &state.limiter_free - }; - if let Err(not_until) = limiter.check_key(&auth.claims.sub) { - let wait = not_until.wait_time_from(DefaultClock::default().now()); - let retry_after = wait.as_secs().max(1).to_string(); - return Err(( - StatusCode::TOO_MANY_REQUESTS, - [("retry-after", retry_after)], - "rate limit exceeded", - ) - .into_response()); - } - } - - Ok(next.run(request).await) -} diff --git a/apps/bot/.gitignore b/apps/bot/.gitignore deleted file mode 100644 index 43370fa915..0000000000 --- a/apps/bot/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.tsbuildinfo diff --git a/apps/chrome/.gitignore b/apps/chrome/.gitignore deleted file mode 100644 index 8b6f140f03..0000000000 --- a/apps/chrome/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.output/ -.wxt/ -node_modules/ diff --git a/apps/chrome/README.md b/apps/chrome/README.md deleted file mode 100644 index e423706abf..0000000000 --- a/apps/chrome/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Char Chrome Extension (WXT) - -Google Meet POC: DOM parsing + native messaging to `com.char.native_host`. - -## Dev - -- `pnpm -F @hypr/chrome dev` -- WXT launches a browser profile and hot-reloads extension changes. -- `pnpm -F @hypr/chrome dev:nobrowser` (builds + watches, but does not auto-launch Chrome) -- Popup UI uses React via `@wxt-dev/module-react`. -- Styling uses Tailwind CSS v4. -- Meet badge UI is injected via WXT Shadow Root UI to isolate styles from the page. - -## No-browser dev workflow - -1. Run `pnpm -F @hypr/chrome dev:nobrowser`. -2. Keep that process running for rebuilds/HMR. -3. In Chrome, open `chrome://extensions` and enable Developer mode. -4. Click Load unpacked and select `apps/chrome/.output/chrome-mv3-dev`. -5. Test popup via the extension icon or open `chrome-extension:///popup.html`. - -Notes: - -- `http://localhost:3000` is WXT's internal dev server endpoint for extension tooling, not a normal app page. -- If you run `pnpm -F @hypr/chrome build`, load `apps/chrome/.output/chrome-mv3` instead. - -## Testing - -- `pnpm -F @hypr/chrome test:unit` -- `pnpm -F @hypr/chrome test:e2e` - -## Build / Publish - -- `pnpm -F @hypr/chrome build` (output: `apps/chrome/.output/chrome-mv3`) -- `pnpm -F @hypr/chrome zip` (output zip for Chrome Web Store upload) - -## Native host - -Requires a registered native messaging host `com.char.native_host` pointing to `char-chrome-native-host`. diff --git a/apps/chrome/assets/tailwind.css b/apps/chrome/assets/tailwind.css deleted file mode 100644 index f1d8c73cdc..0000000000 --- a/apps/chrome/assets/tailwind.css +++ /dev/null @@ -1 +0,0 @@ -@import "tailwindcss"; diff --git a/apps/chrome/entrypoints/background.ts b/apps/chrome/entrypoints/background.ts deleted file mode 100644 index 28ad7550b0..0000000000 --- a/apps/chrome/entrypoints/background.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { Browser } from "wxt/browser"; - -import { type HostMessage, parseIncomingMessage } from "./shared/host-message"; - -const NATIVE_HOST_NAME = "com.char.native_host"; - -let nativePort: Browser.runtime.Port | null = null; - -function connectNativeHost() { - if (nativePort) { - return nativePort; - } - - nativePort = browser.runtime.connectNative(NATIVE_HOST_NAME); - - nativePort.onDisconnect.addListener(() => { - const error = browser.runtime.lastError; - if (error) { - console.warn("[char] native host disconnected:", error.message); - } - nativePort = null; - }); - - return nativePort; -} - -function postToNativeHost(payload: HostMessage) { - try { - const port = connectNativeHost(); - port.postMessage(payload); - return true; - } catch (error) { - console.warn("[char] failed to post to native host:", error); - nativePort = null; - return false; - } -} - -export default defineBackground(() => { - browser.runtime.onMessage.addListener((message) => { - const payload = parseIncomingMessage(message); - if (!payload) { - return { ok: false, error: "invalid_message" }; - } - - const ok = postToNativeHost(payload); - return { ok }; - }); -}); diff --git a/apps/chrome/entrypoints/meet.content/dom.ts b/apps/chrome/entrypoints/meet.content/dom.ts deleted file mode 100644 index 82dd49f671..0000000000 --- a/apps/chrome/entrypoints/meet.content/dom.ts +++ /dev/null @@ -1,144 +0,0 @@ -const MAX_PARTICIPANTS = 30; -const MAX_PARTICIPANT_NAME_LENGTH = 80; - -export type Participant = { - name: string; - is_self: boolean; -}; - -function byTextPattern(value: string | null | undefined, patterns: string[]) { - const text = (value || "").toLowerCase(); - return patterns.some((pattern) => text.includes(pattern)); -} - -export function normalizeParticipantName( - value: string | null | undefined, -): string | null { - const name = (value || "").trim().replace(/\s+/g, " "); - if (!name || name.length > MAX_PARTICIPANT_NAME_LENGTH) { - return null; - } - - if (byTextPattern(name, ["microphone", "camera", "pin", "present"])) { - return null; - } - - return name; -} - -export function isSelfParticipant(name: string) { - const normalized = name.toLowerCase(); - return ( - normalized === "you" || - normalized.endsWith("(you)") || - normalized.includes(" (you)") - ); -} - -export function getMuteState(root: ParentNode) { - const muteSelectors = [ - "button[aria-label*='microphone' i]", - "button[aria-label*='mic' i]", - "button[data-is-muted]", - ]; - - for (const selector of muteSelectors) { - const button = root.querySelector(selector); - if (!button) { - continue; - } - - const dataMuted = button.getAttribute("data-is-muted"); - if (dataMuted === "true") { - return true; - } - if (dataMuted === "false") { - return false; - } - - const ariaPressed = button.getAttribute("aria-pressed"); - if (ariaPressed === "true") { - return true; - } - if (ariaPressed === "false") { - return false; - } - - const label = button.getAttribute("aria-label") || ""; - if (byTextPattern(label, ["turn on microphone", "unmute"])) { - return true; - } - if (byTextPattern(label, ["turn off microphone", "mute"])) { - return false; - } - } - - return false; -} - -export function getParticipantNames(root: ParentNode): Participant[] { - const names = new Map(); - - const participantElements = root.querySelectorAll( - "[data-participant-id]", - ); - - for (const participant of participantElements) { - const participantId = participant - .getAttribute("data-participant-id") - ?.trim(); - - const selfNameElement = - participant.querySelector("[data-self-name]"); - const labelElement = participant.querySelector( - "[aria-label], span[dir='auto'], span", - ); - - const rawName = - selfNameElement?.getAttribute("data-self-name") || - labelElement?.getAttribute("aria-label") || - labelElement?.textContent || - participant.getAttribute("aria-label") || - ""; - - const name = normalizeParticipantName(rawName); - if (!name) { - continue; - } - - const isSelf = Boolean(selfNameElement) || isSelfParticipant(name); - const key = participantId || name.toLowerCase(); - const existing = names.get(key); - - if (existing) { - existing.is_self = existing.is_self || isSelf; - continue; - } - - names.set(key, { name, is_self: isSelf }); - - if (names.size >= MAX_PARTICIPANTS) { - break; - } - } - - return Array.from(names.values()); -} - -export function isMeetPageActive( - location: { hostname: string; pathname: string }, - root: ParentNode, -) { - if (location.hostname !== "meet.google.com") { - return false; - } - - const codePattern = /^\/[a-z]{3}-[a-z]{4}-[a-z]{3}/; - if (codePattern.test(location.pathname)) { - return true; - } - - return Boolean( - root.querySelector("[role='toolbar'], [aria-label*='Call controls' i]"), - ); -} diff --git a/apps/chrome/entrypoints/meet.content/index.ts b/apps/chrome/entrypoints/meet.content/index.ts deleted file mode 100644 index dac1d0fe88..0000000000 --- a/apps/chrome/entrypoints/meet.content/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import "~/assets/tailwind.css"; - -import { - getMuteState, - getParticipantNames, - isMeetPageActive, - type Participant, -} from "./dom"; - -const SEND_INTERVAL_MS = 2500; -const MUTATION_DEBOUNCE_MS = 250; -const BADGE_ANCHOR_SELECTOR = - "[role='toolbar'], [aria-label*='Call controls' i], footer"; - -type MeetingStateMessage = { - type: "meeting_state"; - url: string; - is_active: true; - muted: boolean; - participants: Participant[]; -}; - -type MeetingEndedMessage = { - type: "meeting_ended"; - url: string; - is_active: false; -}; - -type HostMessage = MeetingStateMessage | MeetingEndedMessage; - -let badgeElement: HTMLDivElement | null = null; - -function buildMeetingStatePayload(): MeetingStateMessage { - return { - type: "meeting_state", - url: window.location.href, - is_active: true, - muted: getMuteState(document), - participants: getParticipantNames(document), - }; -} - -function buildMeetingEndedPayload(): MeetingEndedMessage { - return { - type: "meeting_ended", - url: window.location.href, - is_active: false, - }; -} - -function getBadgeClass(isConnected: boolean) { - const stateClass = isConnected - ? "border-[#2c7a5e] bg-[#0f2f26] text-[#8af8c5]" - : "border-[#9a6a34] bg-[#302012] text-[#f9c786]"; - - return [ - "whitespace-nowrap rounded-full border px-3 py-2 text-xs leading-none tracking-[0.01em]", - "pointer-events-none mr-[10px] self-center font-[IBM_Plex_Sans] transition-colors duration-150", - stateClass, - ].join(" "); -} - -function updateBadge(isConnected: boolean) { - if (!badgeElement) { - return; - } - - badgeElement.className = getBadgeClass(isConnected); - badgeElement.textContent = isConnected - ? "Char: connected" - : "Char: disconnected"; -} - -async function sendPayload(payload: HostMessage) { - try { - const response = (await browser.runtime.sendMessage(payload)) as - | { ok?: boolean } - | undefined; - updateBadge(Boolean(response?.ok)); - } catch (error) { - console.warn("[char] failed to send message to background:", error); - updateBadge(false); - } -} - -export default defineContentScript({ - matches: ["https://meet.google.com/*"], - cssInjectionMode: "ui", - runAt: "document_idle", - async main(ctx) { - const ui = await createShadowRootUi(ctx, { - name: "char-meet-badge", - position: "inline", - anchor: BADGE_ANCHOR_SELECTOR, - append: "first", - onMount: (container) => { - badgeElement = document.createElement("div"); - container.append(badgeElement); - updateBadge(false); - return badgeElement; - }, - onRemove: () => { - badgeElement = null; - }, - }); - - ui.autoMount(); - - let lastPayloadKey = ""; - let lastSentAt = 0; - let mutationTimeout: number | null = null; - - const maybeSendState = (force = false) => { - if (!isMeetPageActive(window.location, document)) { - if (force) { - void sendPayload(buildMeetingEndedPayload()); - } - return; - } - - const payload = buildMeetingStatePayload(); - const payloadKey = JSON.stringify(payload); - const now = Date.now(); - const shouldSend = - force || - payloadKey !== lastPayloadKey || - now - lastSentAt >= SEND_INTERVAL_MS; - - if (!shouldSend) { - return; - } - - lastPayloadKey = payloadKey; - lastSentAt = now; - void sendPayload(payload); - }; - - const observer = new MutationObserver(() => { - if (mutationTimeout !== null) { - return; - } - - mutationTimeout = window.setTimeout(() => { - mutationTimeout = null; - maybeSendState(); - }, MUTATION_DEBOUNCE_MS); - }); - - observer.observe(document.documentElement, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ["aria-label", "data-is-muted", "aria-pressed"], - }); - - maybeSendState(true); - - ctx.setInterval(() => { - maybeSendState(); - }, SEND_INTERVAL_MS); - - ctx.addEventListener(window, "beforeunload", () => { - if (mutationTimeout !== null) { - window.clearTimeout(mutationTimeout); - } - void sendPayload(buildMeetingEndedPayload()); - }); - - ctx.addEventListener(window, "wxt:locationchange", () => { - maybeSendState(true); - }); - }, -}); diff --git a/apps/chrome/entrypoints/popup/App.tsx b/apps/chrome/entrypoints/popup/App.tsx deleted file mode 100644 index 9a90f93b1c..0000000000 --- a/apps/chrome/entrypoints/popup/App.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function App() { - return ( -
-

- Char for Google Meet -

-

- Native host bridge is enabled for meeting state sync. -

-
- ); -} diff --git a/apps/chrome/entrypoints/popup/index.html b/apps/chrome/entrypoints/popup/index.html deleted file mode 100644 index e7eba9ac89..0000000000 --- a/apps/chrome/entrypoints/popup/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Char - - - -
- - - diff --git a/apps/chrome/entrypoints/popup/main.tsx b/apps/chrome/entrypoints/popup/main.tsx deleted file mode 100644 index 09371d4434..0000000000 --- a/apps/chrome/entrypoints/popup/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createRoot } from "react-dom/client"; - -import { App } from "./App"; - -const container = document.getElementById("root"); - -if (container) { - const root = createRoot(container); - root.render(); -} diff --git a/apps/chrome/entrypoints/shared/host-message.ts b/apps/chrome/entrypoints/shared/host-message.ts deleted file mode 100644 index 74152c5a4e..0000000000 --- a/apps/chrome/entrypoints/shared/host-message.ts +++ /dev/null @@ -1,121 +0,0 @@ -const MAX_URL_LENGTH = 2048; -const MAX_PARTICIPANTS = 30; -const MAX_PARTICIPANT_NAME_LENGTH = 80; - -type Participant = { - name: string; - is_self: boolean; -}; - -type MeetingStateMessage = { - type: "meeting_state"; - url: string; - is_active: true; - muted: boolean; - participants: Participant[]; -}; - -type MeetingEndedMessage = { - type: "meeting_ended"; - url: string; - is_active: false; -}; - -export type HostMessage = MeetingStateMessage | MeetingEndedMessage; - -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; -} - -function isMeetUrl(value: unknown): value is string { - if (typeof value !== "string") { - return false; - } - - const url = value.trim(); - if (!url || url.length > MAX_URL_LENGTH) { - return false; - } - - try { - const parsed = new URL(url); - return ( - parsed.protocol === "https:" && parsed.hostname === "meet.google.com" - ); - } catch { - return false; - } -} - -function parseParticipant(value: unknown): Participant | null { - if (!isRecord(value)) { - return null; - } - - const name = typeof value.name === "string" ? value.name.trim() : ""; - if (!name || name.length > MAX_PARTICIPANT_NAME_LENGTH) { - return null; - } - - if (typeof value.is_self !== "boolean") { - return null; - } - - return { - name, - is_self: value.is_self, - }; -} - -export function parseIncomingMessage(message: unknown): HostMessage | null { - if (!isRecord(message) || typeof message.type !== "string") { - return null; - } - - if (message.type === "meeting_state") { - if (message.is_active !== true || typeof message.muted !== "boolean") { - return null; - } - - const { url, participants: rawParticipants } = message; - if (!isMeetUrl(url) || !Array.isArray(rawParticipants)) { - return null; - } - - const participants: Participant[] = []; - for (const rawParticipant of rawParticipants) { - const participant = parseParticipant(rawParticipant); - if (!participant) { - continue; - } - - participants.push(participant); - if (participants.length >= MAX_PARTICIPANTS) { - break; - } - } - - return { - type: "meeting_state", - url, - is_active: true, - muted: message.muted, - participants, - }; - } - - if (message.type === "meeting_ended") { - const { url } = message; - if (message.is_active !== false || !isMeetUrl(url)) { - return null; - } - - return { - type: "meeting_ended", - url, - is_active: false, - }; - } - - return null; -} diff --git a/apps/chrome/package.json b/apps/chrome/package.json deleted file mode 100644 index c1667a6a06..0000000000 --- a/apps/chrome/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@hypr/chrome", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "wxt", - "dev:nobrowser": "WXT_NO_BROWSER=1 wxt", - "dev:firefox": "wxt -b firefox", - "build": "wxt build", - "zip": "wxt zip", - "typecheck": "tsc --noEmit", - "test:unit": "vitest run", - "test:e2e": "wxt build && playwright test", - "postinstall": "wxt prepare" - }, - "dependencies": { - "react": "^19.2.5", - "react-dom": "^19.2.5" - }, - "devDependencies": { - "@playwright/test": "^1.59.1", - "@tailwindcss/vite": "^4.2.2", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@wxt-dev/module-react": "^1.2.2", - "happy-dom": "^20.8.9", - "tailwindcss": "^4.2.2", - "typescript": "^5.9.3", - "vitest": "^4.1.4", - "wxt": "^0.20.20" - } -} diff --git a/apps/chrome/playwright.config.ts b/apps/chrome/playwright.config.ts deleted file mode 100644 index 3fbe02e325..0000000000 --- a/apps/chrome/playwright.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "@playwright/test"; - -export default defineConfig({ - testDir: "tests/e2e", - fullyParallel: false, - workers: 1, - timeout: 60_000, -}); diff --git a/apps/chrome/tests/e2e/popup.spec.ts b/apps/chrome/tests/e2e/popup.spec.ts deleted file mode 100644 index 58666f6f6c..0000000000 --- a/apps/chrome/tests/e2e/popup.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { chromium, expect, test } from "@playwright/test"; -import os from "node:os"; -import path from "node:path"; - -test("popup renders basic React UI", async () => { - const extensionPath = path.resolve( - import.meta.dirname, - "../../.output/chrome-mv3", - ); - - const context = await chromium.launchPersistentContext( - path.join(os.tmpdir(), "char-wxt-e2e"), - { - channel: "chromium", - headless: false, - args: [ - `--disable-extensions-except=${extensionPath}`, - `--load-extension=${extensionPath}`, - ], - }, - ); - - try { - let [background] = context.serviceWorkers(); - if (!background) { - background = await context.waitForEvent("serviceworker"); - } - - const extensionId = background.url().split("/")[2]; - const page = await context.newPage(); - - await page.goto(`chrome-extension://${extensionId}/popup.html`); - - await expect( - page.getByRole("heading", { name: "Char for Google Meet" }), - ).toBeVisible(); - await expect( - page.getByText("Native host bridge is enabled for meeting state sync."), - ).toBeVisible(); - } finally { - await context.close(); - } -}); diff --git a/apps/chrome/tests/unit/dom.test.ts b/apps/chrome/tests/unit/dom.test.ts deleted file mode 100644 index 2989c35e0f..0000000000 --- a/apps/chrome/tests/unit/dom.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { describe, expect, it } from "vitest"; - -import { - getMuteState, - getParticipantNames, - isMeetPageActive, - isSelfParticipant, - normalizeParticipantName, -} from "../../entrypoints/meet.content/dom"; - -function loadFixture(name: string) { - const filePath = path.resolve(import.meta.dirname, "fixtures", name); - return fs.readFileSync(filePath, "utf8"); -} - -function renderFixture(name: string) { - document.body.innerHTML = loadFixture(name); -} - -describe("getMuteState", () => { - it("returns false when mic is unmuted", () => { - renderFixture("meet-active-unmuted.html"); - expect(getMuteState(document)).toBe(false); - }); - - it("returns true when data-is-muted is true", () => { - renderFixture("meet-active-muted.html"); - expect(getMuteState(document)).toBe(true); - }); - - it("returns true when aria-pressed is true", () => { - renderFixture("meet-self-and-filtered.html"); - expect(getMuteState(document)).toBe(true); - }); - - it("defaults to false when no mute control exists", () => { - renderFixture("meet-lobby.html"); - expect(getMuteState(document)).toBe(false); - }); -}); - -describe("getParticipantNames", () => { - it("extracts participants from real-looking DOM", () => { - renderFixture("meet-active-unmuted.html"); - - expect(getParticipantNames(document)).toEqual([ - { name: "Alice Kim", is_self: false }, - { name: "Bob Stone", is_self: false }, - ]); - }); - - it("detects self participants and filters invalid names", () => { - renderFixture("meet-self-and-filtered.html"); - - expect(getParticipantNames(document)).toEqual([ - { name: "Casey Rivera", is_self: true }, - { name: "Taylor (You)", is_self: true }, - ]); - }); - - it("caps participants at the configured maximum", () => { - renderFixture("meet-many-participants.html"); - - const participants = getParticipantNames(document); - expect(participants).toHaveLength(30); - expect(participants[0]).toEqual({ name: "Participant 01", is_self: false }); - expect(participants[29]).toEqual({ - name: "Participant 30", - is_self: false, - }); - }); - - it("deduplicates repeated participant ids", () => { - document.body.innerHTML = ` -
Alice Kim
-
Alice Kim
- `; - - expect(getParticipantNames(document)).toEqual([ - { name: "Alice Kim", is_self: false }, - ]); - }); -}); - -describe("isMeetPageActive", () => { - it("returns true for a meet code pathname", () => { - renderFixture("meet-lobby.html"); - expect( - isMeetPageActive( - { hostname: "meet.google.com", pathname: "/abc-defg-hij" }, - document, - ), - ).toBe(true); - }); - - it("returns true when call controls exist", () => { - renderFixture("meet-active-unmuted.html"); - expect( - isMeetPageActive( - { hostname: "meet.google.com", pathname: "/landing" }, - document, - ), - ).toBe(true); - }); - - it("returns false for non-meet domains", () => { - renderFixture("meet-active-unmuted.html"); - expect( - isMeetPageActive( - { hostname: "example.com", pathname: "/abc-defg-hij" }, - document, - ), - ).toBe(false); - }); - - it("returns false for meet lobby without controls", () => { - renderFixture("meet-lobby.html"); - expect( - isMeetPageActive( - { hostname: "meet.google.com", pathname: "/landing" }, - document, - ), - ).toBe(false); - }); -}); - -describe("normalizeParticipantName", () => { - it("normalizes whitespace", () => { - expect(normalizeParticipantName(" Alice Kim ")).toBe("Alice Kim"); - }); - - it("rejects control-like values", () => { - expect(normalizeParticipantName("Turn on microphone")).toBeNull(); - }); -}); - -describe("isSelfParticipant", () => { - it("detects you markers", () => { - expect(isSelfParticipant("You")).toBe(true); - expect(isSelfParticipant("Alex (You)")).toBe(true); - }); - - it("does not flag regular names", () => { - expect(isSelfParticipant("Yousef")).toBe(false); - }); -}); diff --git a/apps/chrome/tests/unit/fixtures/meet-active-muted.html b/apps/chrome/tests/unit/fixtures/meet-active-muted.html deleted file mode 100644 index b4b32133a2..0000000000 --- a/apps/chrome/tests/unit/fixtures/meet-active-muted.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
- -
- -
-
- Alice Kim -
-
-
diff --git a/apps/chrome/tests/unit/fixtures/meet-active-unmuted.html b/apps/chrome/tests/unit/fixtures/meet-active-unmuted.html deleted file mode 100644 index d959b9e612..0000000000 --- a/apps/chrome/tests/unit/fixtures/meet-active-unmuted.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
- -
- -
-
- Alice Kim -
-
- -
-
-
diff --git a/apps/chrome/tests/unit/fixtures/meet-lobby.html b/apps/chrome/tests/unit/fixtures/meet-lobby.html deleted file mode 100644 index 63213c51d7..0000000000 --- a/apps/chrome/tests/unit/fixtures/meet-lobby.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
- -
-
diff --git a/apps/chrome/tests/unit/fixtures/meet-many-participants.html b/apps/chrome/tests/unit/fixtures/meet-many-participants.html deleted file mode 100644 index 25f532d344..0000000000 --- a/apps/chrome/tests/unit/fixtures/meet-many-participants.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
-
-
Participant 01
-
Participant 02
-
Participant 03
-
Participant 04
-
Participant 05
-
Participant 06
-
Participant 07
-
Participant 08
-
Participant 09
-
Participant 10
-
Participant 11
-
Participant 12
-
Participant 13
-
Participant 14
-
Participant 15
-
Participant 16
-
Participant 17
-
Participant 18
-
Participant 19
-
Participant 20
-
Participant 21
-
Participant 22
-
Participant 23
-
Participant 24
-
Participant 25
-
Participant 26
-
Participant 27
-
Participant 28
-
Participant 29
-
Participant 30
-
Participant 31
-
Participant 32
-
-
diff --git a/apps/chrome/tests/unit/fixtures/meet-self-and-filtered.html b/apps/chrome/tests/unit/fixtures/meet-self-and-filtered.html deleted file mode 100644 index dbcf62885c..0000000000 --- a/apps/chrome/tests/unit/fixtures/meet-self-and-filtered.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
- -
-
-
- Ignored Name -
-
- Taylor (You) -
-
- Turn on microphone -
-
- -
-
-
diff --git a/apps/chrome/tests/unit/host-message.test.ts b/apps/chrome/tests/unit/host-message.test.ts deleted file mode 100644 index 28d8a9c4ee..0000000000 --- a/apps/chrome/tests/unit/host-message.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { parseIncomingMessage } from "../../entrypoints/shared/host-message"; - -describe("parseIncomingMessage", () => { - it("parses valid meeting_state payload", () => { - const message = parseIncomingMessage({ - type: "meeting_state", - url: "https://meet.google.com/abc-defg-hij", - is_active: true, - muted: false, - participants: [{ name: "Alice", is_self: true }], - }); - - expect(message).toEqual({ - type: "meeting_state", - url: "https://meet.google.com/abc-defg-hij", - is_active: true, - muted: false, - participants: [{ name: "Alice", is_self: true }], - }); - }); - - it("rejects invalid origin", () => { - const message = parseIncomingMessage({ - type: "meeting_state", - url: "https://example.com/abc", - is_active: true, - muted: false, - participants: [], - }); - - expect(message).toBeNull(); - }); - - it("parses meeting_ended payload", () => { - const message = parseIncomingMessage({ - type: "meeting_ended", - url: "https://meet.google.com/abc-defg-hij", - is_active: false, - }); - - expect(message).toEqual({ - type: "meeting_ended", - url: "https://meet.google.com/abc-defg-hij", - is_active: false, - }); - }); -}); diff --git a/apps/chrome/tsconfig.json b/apps/chrome/tsconfig.json deleted file mode 100644 index 6ad10a0017..0000000000 --- a/apps/chrome/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./.wxt/tsconfig.json", - "compilerOptions": { - "jsx": "react-jsx" - } -} diff --git a/apps/chrome/turbo.json b/apps/chrome/turbo.json deleted file mode 100644 index c222a2210d..0000000000 --- a/apps/chrome/turbo.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "tasks": { - "build": { - "outputs": [".output/**"] - } - } -} diff --git a/apps/chrome/vitest.config.ts b/apps/chrome/vitest.config.ts deleted file mode 100644 index 34ae4dffdc..0000000000 --- a/apps/chrome/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; -import { WxtVitest } from "wxt/testing/vitest-plugin"; - -export default defineConfig({ - plugins: [WxtVitest()], - test: { - environment: "happy-dom", - include: ["tests/unit/**/*.test.ts"], - }, -}); diff --git a/apps/chrome/wxt.config.ts b/apps/chrome/wxt.config.ts deleted file mode 100644 index 8eac6c45ec..0000000000 --- a/apps/chrome/wxt.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import tailwindcss from "@tailwindcss/vite"; -import { defineConfig } from "wxt"; - -const NO_BROWSER = process.env.WXT_NO_BROWSER === "1"; - -export default defineConfig({ - modules: ["@wxt-dev/module-react"], - vite: () => ({ - plugins: [tailwindcss() as any], - }), - webExt: NO_BROWSER ? { disabled: true } : undefined, - manifest: { - name: "Char for Google Meet", - description: - "POC extension: Google Meet DOM parsing and native messaging bridge to Char desktop.", - permissions: ["nativeMessaging"], - }, -}); diff --git a/apps/cli-ui/.gitignore b/apps/cli-ui/.gitignore deleted file mode 100644 index 24e5b0a1ae..0000000000 --- a/apps/cli-ui/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.build diff --git a/apps/cli-ui/Package.swift b/apps/cli-ui/Package.swift deleted file mode 100644 index b25a19af10..0000000000 --- a/apps/cli-ui/Package.swift +++ /dev/null @@ -1,14 +0,0 @@ -// swift-tools-version: 5.9 - -import PackageDescription - -let package = Package( - name: "CliUI", - platforms: [.macOS(.v14)], - targets: [ - .executableTarget( - name: "char-cli-ui", - path: "Sources/CliUI" - ) - ] -) diff --git a/apps/cli-ui/Sources/CliUI/ContentView.swift b/apps/cli-ui/Sources/CliUI/ContentView.swift deleted file mode 100644 index 8ce08b47a6..0000000000 --- a/apps/cli-ui/Sources/CliUI/ContentView.swift +++ /dev/null @@ -1,58 +0,0 @@ -import SwiftUI - -struct ContentView: View { - @ObservedObject var state: CliUIState - - var body: some View { - HStack(spacing: 10) { - // Cancel button - Button(action: { sendAction("cancel") }) { - Image(systemName: "xmark") - .font(.system(size: 10, weight: .medium)) - .foregroundColor(.white.opacity(0.6)) - .frame(width: 24, height: 24) - .background(Color.white.opacity(0.1)) - .clipShape(Circle()) - } - .buttonStyle(.plain) - - // Recording indicator + waveform - HStack(spacing: 6) { - Circle() - .fill(state.isRecording ? Color.blue : Color.gray) - .frame(width: 8, height: 8) - - WaveformView(level: state.audioLevel, tick: state.levelTick) - } - - // Stop button - Button(action: { sendAction("stop") }) { - RoundedRectangle(cornerRadius: 2) - .fill(Color.red) - .frame(width: 10, height: 10) - .frame(width: 24, height: 24) - .background(Color.white.opacity(0.1)) - .clipShape(Circle()) - } - .buttonStyle(.plain) - } - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background( - RoundedRectangle(cornerRadius: 26) - .fill(.ultraThinMaterial) - .environment(\.colorScheme, .dark) - ) - .overlay( - RoundedRectangle(cornerRadius: 26) - .stroke(Color.white.opacity(0.1), lineWidth: 0.5) - ) - } -} - -final class CliUIState: ObservableObject { - @Published var isRecording = true - @Published var audioLevel: Float = 0 - @Published var levelTick: UInt64 = 0 - @Published var status: String? -} diff --git a/apps/cli-ui/Sources/CliUI/OverlayPanel.swift b/apps/cli-ui/Sources/CliUI/OverlayPanel.swift deleted file mode 100644 index d864e76aeb..0000000000 --- a/apps/cli-ui/Sources/CliUI/OverlayPanel.swift +++ /dev/null @@ -1,37 +0,0 @@ -import AppKit -import SwiftUI - -final class OverlayPanel: NSPanel { - init(contentView: NSView) { - super.init( - contentRect: .zero, - styleMask: [.borderless, .nonactivatingPanel], - backing: .buffered, - defer: false - ) - - self.level = .floating - self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] - self.isOpaque = false - self.backgroundColor = .clear - self.hasShadow = true - self.hidesOnDeactivate = false - self.contentView = contentView - - positionAtBottomRight() - } - - private func positionAtBottomRight() { - guard let screen = NSScreen.main else { return } - - let panelWidth: CGFloat = 220 - let panelHeight: CGFloat = 44 - let rightMargin: CGFloat = 20 - let bottomMargin: CGFloat = 20 - - let x = screen.frame.maxX - panelWidth - rightMargin - let y = screen.frame.origin.y + bottomMargin - - setFrame(NSRect(x: x, y: y, width: panelWidth, height: panelHeight), display: true) - } -} diff --git a/apps/cli-ui/Sources/CliUI/Protocol.swift b/apps/cli-ui/Sources/CliUI/Protocol.swift deleted file mode 100644 index 0e9cdab2b8..0000000000 --- a/apps/cli-ui/Sources/CliUI/Protocol.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation - -// MARK: - Daemon → UI (stdin) - -enum InboundMessage { - case state(StateMessage) - case levels(LevelsMessage) - case dismiss -} - -struct StateMessage: Decodable { - let recording: Bool? - let status: String? -} - -struct LevelsMessage: Decodable { - let left: Float - let right: Float -} - -extension InboundMessage { - static func parse(_ line: String) -> InboundMessage? { - guard let data = line.data(using: .utf8) else { return nil } - - struct Envelope: Decodable { - let type: String - } - - guard let envelope = try? JSONDecoder().decode(Envelope.self, from: data) else { - return nil - } - - switch envelope.type { - case "state": - guard let msg = try? JSONDecoder().decode(StateMessage.self, from: data) else { - return nil - } - return .state(msg) - case "levels": - guard let msg = try? JSONDecoder().decode(LevelsMessage.self, from: data) else { - return nil - } - return .levels(msg) - case "dismiss": - return .dismiss - default: - return nil - } - } -} - -// MARK: - UI → Daemon (stdout) - -struct OutboundAction: Encodable { - let type = "action" - let action: String -} - -func sendAction(_ action: String) { - let msg = OutboundAction(action: action) - guard let data = try? JSONEncoder().encode(msg), - let json = String(data: data, encoding: .utf8) - else { return } - print(json) - fflush(stdout) -} diff --git a/apps/cli-ui/Sources/CliUI/Waveform.swift b/apps/cli-ui/Sources/CliUI/Waveform.swift deleted file mode 100644 index eee3655ae8..0000000000 --- a/apps/cli-ui/Sources/CliUI/Waveform.swift +++ /dev/null @@ -1,30 +0,0 @@ -import SwiftUI - -struct WaveformView: View { - let level: Float - let tick: UInt64 - - private let barCount = 9 - @State private var animatedBars: [CGFloat] = Array(repeating: 0.15, count: 9) - - var body: some View { - HStack(spacing: 2) { - ForEach(0.. { - console.error(err); - process.exit(1); -}); - -child.on("exit", (code, signal) => { - if (signal) { - process.kill(process.pid, signal); - } else { - process.exit(code ?? 1); - } -}); diff --git a/apps/cli/npm/install.js b/apps/cli/npm/install.js deleted file mode 100644 index 726a7a2729..0000000000 --- a/apps/cli/npm/install.js +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env node - -const { execSync } = require("node:child_process"); -const { existsSync, mkdirSync } = require("node:fs"); -const { join } = require("node:path"); - -const REPO = "fastrepl/char"; - -const TARGETS = { - "darwin-arm64": "aarch64-apple-darwin", - "darwin-x64": "x86_64-apple-darwin", - "linux-x64": "x86_64-unknown-linux-gnu", - "linux-arm64": "aarch64-unknown-linux-gnu", -}; - -const target = TARGETS[`${process.platform}-${process.arch}`]; -if (!target) { - console.error(`Unsupported platform: ${process.platform}-${process.arch}`); - process.exit(1); -} - -const version = require("./package.json").version; -const tag = `cli_v${version}`; -const archive = `char-${version}-${target}.tar.xz`; -const url = `https://github.com/${REPO}/releases/download/${tag}/${archive}`; - -const binDir = join(__dirname, "bin"); -if (!existsSync(binDir)) { - mkdirSync(binDir, { recursive: true }); -} - -const dest = join(binDir, "char"); -if (existsSync(dest)) { - process.exit(0); -} - -console.error(`Downloading char from ${url}`); -const tmp = join(require("node:os").tmpdir(), archive); -execSync(`curl -fsSL -o "${tmp}" "${url}"`, { stdio: "inherit" }); -execSync(`tar -xf "${tmp}" -C "${binDir}"`, { stdio: "inherit" }); -execSync(`rm -f "${tmp}"`); -execSync(`chmod +x "${dest}"`); - -const uiDest = join(binDir, "char-cli-ui"); -if (existsSync(uiDest)) { - execSync(`chmod +x "${uiDest}"`); -} diff --git a/apps/cli/npm/package.json b/apps/cli/npm/package.json deleted file mode 100644 index 3fb57e3a7e..0000000000 --- a/apps/cli/npm/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "char", - "version": "0.0.0", - "description": "Live transcription and audio tools", - "license": "GPL-3.0", - "repository": { - "type": "git", - "url": "git+https://github.com/fastrepl/char.git", - "directory": "apps/cli/npm" - }, - "homepage": "https://char.com", - "bugs": { - "url": "https://github.com/fastrepl/char/issues" - }, - "keywords": [ - "char", - "cli", - "transcription", - "notes" - ], - "bin": { - "char": "bin/run.js" - }, - "scripts": { - "postinstall": "node install.js" - }, - "files": [ - "bin/", - "install.js" - ], - "engines": { - "node": ">=18" - } -} diff --git a/apps/cli/src/app.rs b/apps/cli/src/app.rs deleted file mode 100644 index bd6846e74b..0000000000 --- a/apps/cli/src/app.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::config::paths::{self, AppPaths}; -#[cfg(feature = "_cli-audio")] -use crate::stt; - -pub struct AppContext { - analytics: hypr_analytics::AnalyticsClient, - paths: AppPaths, - #[cfg(feature = "_cli-audio")] - quiet: bool, - trace_buffer: crate::OptTraceBuffer, -} - -impl AppContext { - pub fn new( - base: Option<&std::path::Path>, - quiet: bool, - trace_buffer: crate::OptTraceBuffer, - ) -> Self { - #[cfg(not(feature = "_cli-audio"))] - let _ = quiet; - - let paths = paths::resolve_paths(base); - - Self { - analytics: analytics_client(), - paths, - #[cfg(feature = "_cli-audio")] - quiet, - trace_buffer, - } - } - - pub fn track_command(&self, subcommand: &'static str) { - let client = self.analytics.clone(); - tokio::spawn(async move { - let machine_id = hypr_host::fingerprint(); - let payload = hypr_analytics::AnalyticsPayload::builder("cli_command_invoked") - .with("subcommand", subcommand) - .with("app_identifier", "com.char.cli") - .with( - "app_version", - option_env!("APP_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")), - ) - .build(); - let _ = client.event(machine_id, payload).await; - }); - } - - #[cfg(feature = "_cli-audio")] - pub fn quiet(&self) -> bool { - self.quiet - } - - #[cfg(feature = "_cli-audio")] - pub fn paths(&self) -> &AppPaths { - &self.paths - } - - #[cfg(feature = "_cli-audio")] - pub fn stt_overrides( - &self, - provider: Option, - base_url: Option, - api_key: Option, - model: Option, - language: String, - ) -> stt::SttOverrides { - stt::SttOverrides { - provider, - base_url, - api_key, - model, - language, - models_base: self.paths.models_base.clone(), - } - } - - #[cfg(feature = "_cli-tui")] - pub fn trace_buffer(&self) -> crate::OptTraceBuffer { - self.trace_buffer.clone() - } - - #[cfg(not(feature = "_cli-tui"))] - pub fn trace_buffer(&self) -> crate::OptTraceBuffer { - self.trace_buffer - } -} - -fn analytics_client() -> hypr_analytics::AnalyticsClient { - let mut builder = hypr_analytics::AnalyticsClientBuilder::default(); - if std::env::var_os("DO_NOT_TRACK").is_none() - && let Some(key) = option_env!("POSTHOG_API_KEY") - { - builder = builder.with_posthog(key); - } - builder.build() -} diff --git a/apps/cli/src/cli.rs b/apps/cli/src/cli.rs deleted file mode 100644 index eb02ae3e54..0000000000 --- a/apps/cli/src/cli.rs +++ /dev/null @@ -1,357 +0,0 @@ -use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; -use clap_verbosity_flag::{InfoLevel, Verbosity}; - -/// Live transcription and audio tools -#[derive(Parser)] -#[command( - name = "char", - version = option_env!("APP_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")), - propagate_version = true, - after_help = "Docs: https://cli.char.com\nDiscussions: https://github.com/fastrepl/char/discussions/4788\nBugs: https://github.com/fastrepl/char/issues" -)] -pub struct Cli { - #[command(subcommand)] - pub command: Option, - - #[arg(long)] - pub no_color: bool, - - #[command(flatten)] - pub verbose: Verbosity, -} - -pub(crate) fn parse_base_url(value: &str) -> Result { - let parsed = url::Url::parse(value).map_err(|e| format!("invalid URL '{value}': {e}"))?; - if parsed.scheme() != "http" && parsed.scheme() != "https" { - return Err(format!( - "invalid URL '{value}': scheme must be http or https" - )); - } - Ok(value.to_string()) -} - -#[derive(Clone, Copy, Debug, ValueEnum)] -pub enum OutputFormat { - Pretty, - Json, -} - -#[derive(Subcommand, strum::IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -#[allow(clippy::large_enum_variant)] -pub enum Commands { - #[cfg(feature = "_cli-audio")] - /// Transcribe an audio file - Transcribe { - #[command(flatten)] - args: crate::commands::transcribe::Args, - }, - #[cfg(feature = "_cli-audio")] - /// Manage local models - Models { - #[command(flatten)] - args: crate::commands::model::Args, - }, - /// Generate shell completions - Completions { - #[arg(value_enum)] - shell: clap_complete::Shell, - }, - #[cfg(feature = "_cli-audio")] - /// Play an audio file - Play { - #[command(flatten)] - args: crate::commands::play::Args, - }, - #[cfg(feature = "_cli-audio")] - /// Record audio to an MP3 file - Record { - #[command(flatten)] - args: crate::commands::record::Args, - }, - #[cfg(feature = "_cli-skill-update")] - /// Install char skill for AI coding agents - Skill { - #[command(subcommand)] - command: crate::commands::skill::Commands, - }, - #[cfg(feature = "_cli-desktop")] - /// Open the desktop app or download page - Desktop, - #[cfg(feature = "_cli-db")] - /// Manage desktop SQLite data - Db { - #[command(flatten)] - args: hypr_db_cli::Args, - }, - #[cfg(feature = "_cli-desktop")] - /// Report a bug on GitHub - #[command(hide = true)] - Bug, - #[cfg(feature = "_cli-desktop")] - /// Open char.com - #[command(hide = true)] - Hello, - #[cfg(feature = "_cli-skill-update")] - /// Update char to the latest version - Update, - - #[cfg(feature = "todo")] - /// Manage todos and automations - Todo { - #[command(subcommand)] - command: Option, - }, -} - -impl Commands { - pub fn base_override(&self) -> Option<&std::path::Path> { - match self { - #[cfg(feature = "_cli-audio")] - Commands::Transcribe { args } => args.base.as_deref(), - #[cfg(feature = "_cli-audio")] - Commands::Models { args } => args.base.as_deref(), - #[cfg(feature = "_cli-audio")] - Commands::Play { args } => args.base.as_deref(), - #[cfg(feature = "_cli-audio")] - Commands::Record { args } => args.base.as_deref(), - #[cfg(feature = "_cli-db")] - Commands::Db { args } => args.base.as_deref(), - _ => None, - } - } -} - -pub fn generate_completions(shell: clap_complete::Shell) { - let mut cmd = Cli::command(); - clap_complete::generate(shell, &mut cmd, "char", &mut std::io::stdout()); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn render_help(command: &mut clap::Command) -> String { - let mut bytes = Vec::new(); - command.write_long_help(&mut bytes).unwrap(); - String::from_utf8(bytes).unwrap() - } - - #[test] - fn verify_cli() { - Cli::command().debug_assert(); - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn root_help_only_shows_truly_global_options() { - let mut command = Cli::command(); - let help = render_help(&mut command); - - assert!(help.contains("--no-color")); - assert!(!help.contains("--base-url")); - assert!(!help.contains("--api-key")); - assert!(!help.contains("--model ")); - assert!(!help.contains("--language ")); - assert!(!help.contains("--base ")); - } - - #[test] - #[cfg(all( - feature = "_cli-desktop", - not(feature = "_cli-audio"), - not(feature = "_cli-skill-update"), - not(feature = "todo") - ))] - fn desktop_help_stays_lightweight() { - let mut command = Cli::command(); - let help = render_help(&mut command); - - assert!(help.contains("desktop")); - assert!(help.contains("completions")); - assert!(!help.contains("transcribe")); - assert!(!help.contains("models")); - assert!(!help.contains("record")); - assert!(!help.contains("play")); - assert!(!help.contains("update")); - assert!(!help.contains("meetings")); - assert!(!help.contains("export")); - } - - #[test] - #[cfg(feature = "_cli-db")] - fn desktop_db_help_shows_db_command() { - let mut command = Cli::command(); - let help = render_help(&mut command); - - assert!(help.contains("db")); - } - - #[test] - #[cfg(feature = "_cli-db")] - fn db_help_renders() { - let mut command = Cli::command(); - let mut db = command.find_subcommand_mut("db").unwrap().clone(); - let help = render_help(&mut db); - - assert!(help.contains("templates")); - assert!(help.contains("--base ")); - } - - #[test] - #[cfg(feature = "_cli-db")] - fn db_templates_help_renders() { - let mut command = Cli::command(); - let mut db = command.find_subcommand_mut("db").unwrap().clone(); - let mut templates = db.find_subcommand_mut("templates").unwrap().clone(); - let help = render_help(&mut templates); - - assert!(help.contains("list")); - assert!(help.contains("get")); - assert!(help.contains("upsert")); - assert!(help.contains("delete")); - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn transcribe_help_keeps_stt_config() { - let mut command = Cli::command(); - let mut transcribe = command.find_subcommand_mut("transcribe").unwrap().clone(); - let help = render_help(&mut transcribe); - - assert!(help.contains("--base-url")); - assert!(help.contains("--api-key")); - assert!(help.contains("--model ")); - assert!(help.contains("--language ")); - assert!(help.contains("--base ")); - } - - #[test] - #[cfg(all(feature = "_cli-audio", target_os = "macos"))] - fn transcribe_accepts_whispercpp_provider() { - let input = tempfile::NamedTempFile::new().unwrap(); - Cli::try_parse_from([ - "char", - "transcribe", - "--input", - input.path().to_str().unwrap(), - "--provider", - "whispercpp", - ]) - .unwrap(); - } - - #[test] - #[cfg(all( - feature = "_cli-audio", - target_os = "macos", - any(target_arch = "arm", target_arch = "aarch64") - ))] - fn transcribe_accepts_cactus_provider_on_apple_silicon() { - let input = tempfile::NamedTempFile::new().unwrap(); - Cli::try_parse_from([ - "char", - "transcribe", - "--input", - input.path().to_str().unwrap(), - "--provider", - "cactus", - ]) - .unwrap(); - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn models_help_only_shows_base_override() { - let mut command = Cli::command(); - let mut models = command.find_subcommand_mut("models").unwrap().clone(); - let help = render_help(&mut models); - - assert!(help.contains("--base ")); - assert!(help.contains("list")); - assert!(help.contains("download")); - assert!(help.contains("delete")); - assert!(!help.contains("paths")); - assert!(!help.contains("cactus")); - assert!(!help.contains("--supported")); - assert!(!help.contains("--base-url")); - assert!(!help.contains("--api-key")); - assert!(!help.contains("--model ")); - assert!(!help.contains("--language ")); - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn record_rejects_stt_only_flags() { - match Cli::try_parse_from(["char", "record", "--api-key", "secret"]) { - Ok(_) => panic!("record unexpectedly accepted --api-key"), - Err(error) => assert_eq!(error.kind(), clap::error::ErrorKind::UnknownArgument), - } - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn model_delete_uses_long_only_force() { - Cli::try_parse_from(["char", "models", "delete", "tiny", "--force"]).unwrap(); - - match Cli::try_parse_from(["char", "models", "delete", "tiny", "-f"]) { - Ok(_) => panic!("models delete unexpectedly accepted -f"), - Err(error) => assert_eq!(error.kind(), clap::error::ErrorKind::UnknownArgument), - } - } - - #[test] - #[cfg(feature = "_cli-audio")] - fn models_list_accepts_json_format() { - let mut command = Cli::command(); - let mut list = command - .find_subcommand_mut("models") - .unwrap() - .find_subcommand_mut("list") - .unwrap() - .clone(); - let help = render_help(&mut list); - - assert!(help.contains("json")); - } - - #[test] - #[cfg(all( - feature = "_cli-audio", - feature = "_cli-skill-update", - feature = "_cli-db" - ))] - fn generate_docs_standalone() { - let cmd = Cli::command(); - let json = cli_docs::generate_json(&cmd); - - let json_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../cli-web/src/data/cli.gen.json"); - std::fs::create_dir_all(json_path.parent().unwrap()).unwrap(); - std::fs::write(&json_path, json).unwrap(); - } - - #[test] - #[cfg(all( - feature = "_cli-desktop", - not(feature = "_cli-audio"), - not(feature = "_cli-skill-update"), - not(feature = "todo") - ))] - fn generate_docs_desktop() { - let cmd = Cli::command(); - let md = cli_docs::generate(&cmd); - - let frontmatter = "\ ---- -title: \"CLI Reference\" -section: \"CLI\" -description: \"Command-line reference for the char CLI\" ----\n\n"; - - let mdx_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../web/content/docs/cli/index.mdx"); - std::fs::create_dir_all(mdx_path.parent().unwrap()).unwrap(); - std::fs::write(&mdx_path, format!("{frontmatter}{md}")).unwrap(); - } -} diff --git a/apps/cli/src/commands/bug.rs b/apps/cli/src/commands/bug.rs deleted file mode 100644 index 031bac247f..0000000000 --- a/apps/cli/src/commands/bug.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::error::{CliError, CliResult}; - -const BUG_URL: &str = "https://github.com/fastrepl/char/issues/new?labels=cli"; - -pub fn run() -> CliResult<()> { - if let Err(e) = open::that(BUG_URL) { - return Err(CliError::operation_failed( - "open bug report page", - format!("{e}\nPlease visit: {BUG_URL}"), - )); - } - - Ok(()) -} diff --git a/apps/cli/src/commands/desktop.rs b/apps/cli/src/commands/desktop.rs deleted file mode 100644 index 798da0ce11..0000000000 --- a/apps/cli/src/commands/desktop.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::error::{CliError, CliResult}; - -const DOWNLOAD_URL: &str = "https://char.com/download"; -const DESKTOP_DEEPLINKS: &[&str] = &[ - "hyprnote://focus", - "hyprnote-nightly://focus", - "hyprnote-staging://focus", - "hypr://focus", -]; -const DESKTOP_DATA_FOLDERS: &[&str] = &[ - "hyprnote", - "com.hyprnote.dev", - "com.hyprnote.staging", - "com.hyprnote.nightly", - "com.hyprnote.stable", -]; - -pub enum DesktopAction { - OpenedApp, - OpenedDownloadPage, -} - -pub fn run() -> CliResult { - if !desktop_app_exists() { - if let Err(e) = open::that(DOWNLOAD_URL) { - return Err(CliError::operation_failed( - "open desktop app or download page", - format!("{e}\nPlease visit: {DOWNLOAD_URL}"), - )); - } - - return Ok(DesktopAction::OpenedDownloadPage); - } - - for deeplink in DESKTOP_DEEPLINKS { - if open::that(deeplink).is_ok() { - return Ok(DesktopAction::OpenedApp); - } - } - - Err(CliError::operation_failed( - "open desktop app", - "Desktop app appears to be installed, but no registered deeplink responded".to_string(), - )) -} - -fn desktop_app_exists() -> bool { - let Some(data_dir) = dirs::data_dir() else { - return false; - }; - - DESKTOP_DATA_FOLDERS - .iter() - .any(|folder| data_dir.join(folder).exists()) -} diff --git a/apps/cli/src/commands/hello.rs b/apps/cli/src/commands/hello.rs deleted file mode 100644 index ca2d277ec1..0000000000 --- a/apps/cli/src/commands/hello.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::error::{CliError, CliResult}; - -const HELLO_URL: &str = "https://char.com"; - -pub fn run() -> CliResult<()> { - if let Err(e) = open::that(HELLO_URL) { - return Err(CliError::operation_failed( - "open char.com", - format!("{e}\nPlease visit: {HELLO_URL}"), - )); - } - - Ok(()) -} diff --git a/apps/cli/src/commands/integration/claude.rs b/apps/cli/src/commands/integration/claude.rs deleted file mode 100644 index cbdefd04a0..0000000000 --- a/apps/cli/src/commands/integration/claude.rs +++ /dev/null @@ -1,48 +0,0 @@ -use clap::Subcommand; - -use crate::error::{CliError, CliResult}; - -#[derive(Subcommand)] -pub enum Commands { - /// Receive a hook event from Claude Code (reads JSON from stdin) - Notify, - /// Install char as a Claude Code hook handler - Install, - /// Remove char from Claude Code hooks - Uninstall, -} - -pub async fn run(command: Commands) -> CliResult<()> { - match command { - Commands::Notify => notify(), - Commands::Install => install(), - Commands::Uninstall => uninstall(), - } -} - -fn notify() -> CliResult<()> { - let event = super::read_stdin_json()?; - - // TODO: write to app DB - super::print_pretty_json(&event) -} - -fn install() -> CliResult<()> { - let response = hypr_agent_core::install_cli(hypr_agent_core::InstallCliRequest { - provider: hypr_agent_core::ProviderKind::Claude, - }) - .map_err(|e| CliError::operation_failed("install claude integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} - -fn uninstall() -> CliResult<()> { - let response = hypr_agent_core::uninstall_cli(hypr_agent_core::UninstallCliRequest { - provider: hypr_agent_core::ProviderKind::Claude, - }) - .map_err(|e| CliError::operation_failed("uninstall claude integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} diff --git a/apps/cli/src/commands/integration/codex.rs b/apps/cli/src/commands/integration/codex.rs deleted file mode 100644 index 3c2ce4a324..0000000000 --- a/apps/cli/src/commands/integration/codex.rs +++ /dev/null @@ -1,52 +0,0 @@ -use clap::Subcommand; - -use crate::error::{CliError, CliResult}; - -#[derive(Subcommand)] -pub enum Commands { - /// Receive a notification from Codex - Notify { - /// JSON payload from Codex - payload: String, - }, - /// Install char as the Codex notify handler - Install, - /// Remove char from the Codex notify handler - Uninstall, -} - -pub async fn run(command: Commands) -> CliResult<()> { - match command { - Commands::Notify { payload } => notify(&payload), - Commands::Install => install(), - Commands::Uninstall => uninstall(), - } -} - -fn notify(payload: &str) -> CliResult<()> { - let event: hypr_codex::NotifyEvent = serde_json::from_str(payload) - .map_err(|e| CliError::invalid_argument("payload", payload.to_string(), e.to_string()))?; - - // TODO: write to app DB - super::print_pretty_json(&event) -} - -fn install() -> CliResult<()> { - let response = hypr_agent_core::install_cli(hypr_agent_core::InstallCliRequest { - provider: hypr_agent_core::ProviderKind::Codex, - }) - .map_err(|e| CliError::operation_failed("install codex integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} - -fn uninstall() -> CliResult<()> { - let response = hypr_agent_core::uninstall_cli(hypr_agent_core::UninstallCliRequest { - provider: hypr_agent_core::ProviderKind::Codex, - }) - .map_err(|e| CliError::operation_failed("uninstall codex integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} diff --git a/apps/cli/src/commands/integration/mod.rs b/apps/cli/src/commands/integration/mod.rs deleted file mode 100644 index 4d24253039..0000000000 --- a/apps/cli/src/commands/integration/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod claude; -pub mod codex; -pub mod opencode; - -use serde::Serialize; - -use crate::error::{CliError, CliResult}; - -pub(super) fn read_stdin() -> CliResult { - std::io::read_to_string(std::io::stdin()) - .map_err(|e| CliError::operation_failed("read stdin", e.to_string())) -} - -pub(super) fn read_stdin_json() -> CliResult { - let input = read_stdin()?; - - serde_json::from_str(&input) - .map_err(|e| CliError::invalid_argument("stdin", input, e.to_string())) -} - -pub(super) fn print_pretty_json(event: &impl Serialize) -> CliResult<()> { - println!( - "{}", - serde_json::to_string_pretty(event) - .map_err(|e| CliError::operation_failed("serialize", e.to_string()))? - ); - Ok(()) -} diff --git a/apps/cli/src/commands/integration/opencode.rs b/apps/cli/src/commands/integration/opencode.rs deleted file mode 100644 index deab6cd00b..0000000000 --- a/apps/cli/src/commands/integration/opencode.rs +++ /dev/null @@ -1,52 +0,0 @@ -use clap::Subcommand; - -use crate::error::{CliError, CliResult}; - -#[derive(Subcommand)] -pub enum Commands { - /// Receive a hook event from OpenCode - Notify { - /// JSON payload from OpenCode - payload: String, - }, - /// Install char as an OpenCode plugin - Install, - /// Remove char from OpenCode plugins - Uninstall, -} - -pub async fn run(command: Commands) -> CliResult<()> { - match command { - Commands::Notify { payload } => notify(&payload), - Commands::Install => install(), - Commands::Uninstall => uninstall(), - } -} - -fn notify(payload: &str) -> CliResult<()> { - let event: serde_json::Value = serde_json::from_str(payload) - .map_err(|e| CliError::invalid_argument("payload", payload.to_string(), e.to_string()))?; - - // TODO: write to app DB - super::print_pretty_json(&event) -} - -fn install() -> CliResult<()> { - let response = hypr_agent_core::install_cli(hypr_agent_core::InstallCliRequest { - provider: hypr_agent_core::ProviderKind::Opencode, - }) - .map_err(|e| CliError::operation_failed("install opencode integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} - -fn uninstall() -> CliResult<()> { - let response = hypr_agent_core::uninstall_cli(hypr_agent_core::UninstallCliRequest { - provider: hypr_agent_core::ProviderKind::Opencode, - }) - .map_err(|e| CliError::operation_failed("uninstall opencode integration", e))?; - - eprintln!("{}", response.message); - Ok(()) -} diff --git a/apps/cli/src/commands/mod.rs b/apps/cli/src/commands/mod.rs deleted file mode 100644 index 62207bbe72..0000000000 --- a/apps/cli/src/commands/mod.rs +++ /dev/null @@ -1,119 +0,0 @@ -#[cfg(feature = "_cli-audio")] -pub mod transcribe; -#[cfg(feature = "_cli-audio")] -pub(crate) mod update_check; - -#[cfg(feature = "todo")] -pub mod integration; -#[cfg(feature = "todo")] -pub mod todo; - -#[cfg(feature = "_cli-desktop")] -pub mod bug; -#[cfg(feature = "_cli-desktop")] -pub mod desktop; -#[cfg(feature = "_cli-desktop")] -pub mod hello; -#[cfg(feature = "_cli-audio")] -pub mod model; -#[cfg(feature = "_cli-audio")] -pub mod play; -#[cfg(feature = "_cli-audio")] -pub mod record; -#[cfg(feature = "_cli-skill-update")] -pub mod skill; -#[cfg(feature = "_cli-skill-update")] -pub mod update; - -use std::path::{Path, PathBuf}; - -use crate::app::AppContext; -use crate::cli::{Cli, Commands as CliCommand}; -use crate::error::{CliError, CliResult}; - -pub(crate) fn resolve_session_dir(base: Option<&Path>, timestamp: &str) -> CliResult { - let base = base.map(Path::to_path_buf).unwrap_or_else(|| { - dirs::data_dir() - .unwrap_or_else(std::env::temp_dir) - .join("char") - }); - - let mut dir = base.join(timestamp); - if !dir.exists() { - std::fs::create_dir_all(&dir) - .map_err(|e| CliError::operation_failed("create session directory", e.to_string()))?; - return Ok(dir); - } - - for i in 1.. { - dir = base.join(format!("{timestamp}-{i}")); - if !dir.exists() { - std::fs::create_dir_all(&dir).map_err(|e| { - CliError::operation_failed("create session directory", e.to_string()) - })?; - return Ok(dir); - } - } - - unreachable!() -} - -pub async fn run(ctx: &AppContext, command: Option) -> CliResult<()> { - #[cfg(not(any(feature = "_cli-audio", feature = "_cli-skill-update")))] - let _ = ctx; - - match command { - #[cfg(feature = "_cli-audio")] - Some(CliCommand::Transcribe { args }) => transcribe::run(ctx, args).await, - #[cfg(feature = "_cli-audio")] - Some(CliCommand::Models { args }) => model::run(ctx, args).await, - #[cfg(feature = "_cli-audio")] - Some(CliCommand::Play { args }) => play::run(ctx, args).await, - #[cfg(feature = "_cli-audio")] - Some(CliCommand::Record { args }) => record::run(ctx, args).await, - #[cfg(feature = "_cli-skill-update")] - Some(CliCommand::Skill { command }) => skill::run(ctx, command).await, - Some(CliCommand::Completions { shell }) => { - crate::cli::generate_completions(shell); - Ok(()) - } - #[cfg(feature = "_cli-desktop")] - Some(CliCommand::Desktop) => { - use desktop::DesktopAction; - match desktop::run()? { - DesktopAction::OpenedApp => eprintln!("Opened desktop app"), - DesktopAction::OpenedDownloadPage => { - eprintln!("Desktop app not found — opened download page") - } - } - Ok(()) - } - #[cfg(feature = "_cli-db")] - Some(CliCommand::Db { args }) => hypr_db_cli::run(args) - .await - .map_err(|e| e.to_string().into()), - #[cfg(feature = "_cli-desktop")] - Some(CliCommand::Bug) => { - bug::run()?; - eprintln!("Opened bug report page in browser"); - Ok(()) - } - #[cfg(feature = "_cli-desktop")] - Some(CliCommand::Hello) => { - hello::run()?; - eprintln!("Opened char.com in browser"); - Ok(()) - } - #[cfg(feature = "_cli-skill-update")] - Some(CliCommand::Update) => update::run(), - #[cfg(feature = "todo")] - Some(CliCommand::Todo { command }) => todo::run(command).await, - None => { - use clap::CommandFactory; - - Cli::command().print_help().ok(); - println!(); - Ok(()) - } - } -} diff --git a/apps/cli/src/commands/model/delete.rs b/apps/cli/src/commands/model/delete.rs deleted file mode 100644 index 9ce75d21bf..0000000000 --- a/apps/cli/src/commands/model/delete.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::io::{IsTerminal, Write}; -use std::path::Path; - -use hypr_local_model::LocalModel; - -use crate::error::{CliError, CliResult}; - -pub(super) async fn delete(model: LocalModel, models_base: &Path, force: bool) -> CliResult<()> { - if !force && std::io::stderr().is_terminal() { - eprint!("Delete {}? [y/N] ", model.display_name()); - std::io::stderr() - .flush() - .map_err(|e| CliError::operation_failed("prompt", e.to_string()))?; - let mut input = String::new(); - std::io::stdin() - .read_line(&mut input) - .map_err(|e| CliError::operation_failed("read confirmation", e.to_string()))?; - if !input.trim().eq_ignore_ascii_case("y") { - eprintln!("Cancelled"); - return Ok(()); - } - } - - let manager = super::make_manager(models_base, None); - - if let Err(e) = manager.delete(&model).await { - return Err(CliError::operation_failed( - "delete model", - format!("{}: {e}", model.cli_name()), - )); - } - - eprintln!("Deleted {}", model.display_name()); - Ok(()) -} diff --git a/apps/cli/src/commands/model/download.rs b/apps/cli/src/commands/model/download.rs deleted file mode 100644 index d37d043ae7..0000000000 --- a/apps/cli/src/commands/model/download.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::path::Path; -use std::time::Duration; - -use hypr_local_model::LocalModel; -use tokio::sync::mpsc; - -use super::runtime; -use crate::error::{CliError, CliResult}; -use crate::tui::{InlineViewport, SPINNER}; - -pub(super) async fn download( - model: LocalModel, - models_base: &Path, - trace_buffer: crate::OptTraceBuffer, -) -> CliResult<()> { - let (progress_tx, mut progress_rx) = mpsc::unbounded_channel(); - - let manager = super::make_manager(models_base, Some(progress_tx)); - - if manager.is_downloaded(&model).await.unwrap_or(false) { - eprintln!( - "Model already downloaded: {} ({})", - model.display_name(), - model.install_path(models_base).display() - ); - return Ok(()); - } - - let mut viewport = if trace_buffer.is_some() { - InlineViewport::stderr(5, trace_buffer).ok() - } else { - None - }; - - if let Err(e) = manager.download(&model).await { - drop(viewport); - return Err(CliError::operation_failed( - "start model download", - format!("{}: {e}", model.cli_name()), - )); - } - - let mut pct: u8 = 0; - let mut spinner_idx: usize = 0; - let mut tick = tokio::time::interval(Duration::from_millis(80)); - - loop { - let done = tokio::select! { - event = progress_rx.recv() => { - match event { - Some(runtime::DownloadEvent::Progress(p)) => { pct = p; false } - Some(runtime::DownloadEvent::Completed | runtime::DownloadEvent::Failed) | None => true, - } - } - _ = tick.tick() => { - spinner_idx = (spinner_idx + 1) % SPINNER.len(); - false - } - }; - - draw_download(&mut viewport, &model, spinner_idx, pct); - - if done { - break; - } - } - - while manager.is_downloading(&model).await { - tokio::time::sleep(Duration::from_millis(120)).await; - } - - if let Some(ref mut vp) = viewport { - vp.clear() - .map_err(|e| CliError::operation_failed("clear viewport", e.to_string()))?; - } - drop(viewport); - - if manager.is_downloaded(&model).await.unwrap_or(false) { - eprintln!( - "Downloaded {} -> {}", - model.display_name(), - model.install_path(models_base).display() - ); - Ok(()) - } else { - Err(CliError::operation_failed( - "download model", - model.cli_name().to_string(), - )) - } -} - -fn draw_download( - viewport: &mut Option, - model: &LocalModel, - spinner_idx: usize, - pct: u8, -) { - if let Some(vp) = viewport { - vp.poll_input(); - let name = model.display_name(); - let pct_str = format!("{}%", pct); - vp.draw_strings(&[ - format!( - "{} Downloading {}... {}", - SPINNER[spinner_idx], name, pct_str - ), - format_gauge(pct), - ]); - } -} - -fn format_gauge(pct: u8) -> String { - let width = 40; - let filled = (pct as usize * width) / 100; - let empty = width - filled; - format!(" [{}{}]", "█".repeat(filled), "░".repeat(empty),) -} diff --git a/apps/cli/src/commands/model/list.rs b/apps/cli/src/commands/model/list.rs deleted file mode 100644 index 877847d4a9..0000000000 --- a/apps/cli/src/commands/model/list.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::path::Path; - -use comfy_table::{ContentArrangement, Table, presets::UTF8_FULL_CONDENSED}; -use hypr_local_model::LocalModel; -use hypr_model_downloader::{DownloadableModel, ModelDownloadManager}; - -use crate::cli::OutputFormat; -use crate::error::CliResult; - -#[derive(Clone, Debug, serde::Serialize)] -pub(crate) struct ModelRow { - pub(crate) name: String, - pub(crate) kind: String, - pub(crate) status: String, - pub(crate) downloaded: bool, - pub(crate) downloadable: bool, - pub(crate) available_on_current_platform: bool, - pub(crate) display_name: String, - pub(crate) description: String, - pub(crate) install_path: String, -} - -pub(crate) async fn collect_model_rows( - models: &[LocalModel], - models_base: &Path, - manager: &ModelDownloadManager, -) -> Vec { - let mut rows = Vec::new(); - for model in models { - let downloadable = model.download_url().is_some(); - let available_on_current_platform = model.is_available_on_current_platform(); - - let status = match manager.is_downloaded(model).await { - Ok(true) => "downloaded", - Ok(false) if downloadable && available_on_current_platform => "available", - Ok(false) if downloadable => "unsupported", - Ok(false) => continue, - Err(_) => "error", - }; - - rows.push(ModelRow { - name: model.cli_name().to_string(), - kind: model.kind().to_string(), - status: status.to_string(), - downloaded: status == "downloaded", - downloadable, - available_on_current_platform, - display_name: model.display_name().to_string(), - description: model.description().to_string(), - install_path: model.install_path(models_base).display().to_string(), - }); - } - rows.sort_by(|a, b| { - status_rank(&a.status) - .cmp(&status_rank(&b.status)) - .then_with(|| a.name.cmp(&b.name)) - }); - rows -} - -pub(super) async fn write_model_output( - rows: &[ModelRow], - _models_base: &Path, - format: OutputFormat, -) -> CliResult<()> { - match format { - OutputFormat::Json => { - crate::output::write_json(None, &rows).await?; - } - OutputFormat::Pretty => { - if rows.is_empty() { - eprintln!("No models found."); - return Ok(()); - } - - let home = dirs::home_dir(); - - let mut table = Table::new(); - table - .load_preset(UTF8_FULL_CONDENSED) - .set_content_arrangement(ContentArrangement::Dynamic); - - table.set_header(vec!["Name", "Status", "Path"]); - - for row in rows { - let path = match &home { - Some(h) => row - .install_path - .strip_prefix(&h.display().to_string()) - .map(|rest| format!("~{rest}")) - .unwrap_or_else(|| row.install_path.clone()), - None => row.install_path.clone(), - }; - table.add_row(vec![row.name.clone(), row.status.clone(), path]); - } - - println!("{table}"); - } - } - Ok(()) -} - -fn status_rank(status: &str) -> usize { - match status { - "downloaded" => 0, - "available" => 1, - "unsupported" => 2, - _ => 3, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn row(name: &str, kind: &str, status: &str) -> ModelRow { - ModelRow { - name: name.to_string(), - kind: kind.to_string(), - status: status.to_string(), - downloaded: status == "downloaded", - downloadable: status != "unavailable", - available_on_current_platform: status != "unsupported", - display_name: name.to_string(), - description: String::new(), - install_path: format!("/tmp/{name}"), - } - } - - #[test] - fn sorts_downloaded_before_available_and_unsupported() { - let mut rows = vec![ - row("model-b", "llm", "unsupported"), - row("model-c", "llm", "available"), - row("model-a", "stt-whisper", "downloaded"), - ]; - - rows.sort_by(|a, b| { - status_rank(&a.status) - .cmp(&status_rank(&b.status)) - .then_with(|| a.name.cmp(&b.name)) - }); - - assert_eq!( - rows.iter().map(|row| row.name.as_str()).collect::>(), - vec!["model-a", "model-c", "model-b"] - ); - } -} diff --git a/apps/cli/src/commands/model/mod.rs b/apps/cli/src/commands/model/mod.rs deleted file mode 100644 index 4ca9d7c072..0000000000 --- a/apps/cli/src/commands/model/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -mod delete; -mod download; -pub(crate) mod list; -pub(crate) mod runtime; - -use std::sync::Arc; - -use hypr_local_model::{LocalModel, LocalModelKind}; -use hypr_model_downloader::ModelDownloadManager; -use tokio::sync::mpsc; - -use clap::Subcommand; - -use crate::app::AppContext; -use crate::cli::OutputFormat; -use crate::error::{CliError, CliResult, did_you_mean}; -use runtime::CliModelRuntime; - -#[derive(clap::Args, Debug)] -pub struct Args { - #[arg(long, env = "CHAR_BASE", hide_env_values = true, value_name = "DIR")] - pub base: Option, - - #[command(subcommand)] - pub command: Commands, -} - -#[derive(Subcommand, Debug)] -pub enum Commands { - /// List local models and their status - List { - #[arg(short = 'f', long, value_enum, default_value = "pretty")] - format: OutputFormat, - }, - /// Download a model by name - Download { name: String }, - /// Delete a downloaded model - Delete { - name: String, - #[arg(long)] - force: bool, - }, -} - -struct ModelScope { - models: Vec, - label: &'static str, - list_cmd: &'static str, -} - -impl ModelScope { - fn all() -> Self { - Self { - models: LocalModel::all() - .into_iter() - .filter(|m| m.model_kind() == LocalModelKind::Stt) - .filter(model_is_enabled) - .collect(), - label: "model", - list_cmd: "char models list", - } - } - - fn resolve(&self, name: &str) -> CliResult { - self.models - .iter() - .find(|m| m.cli_name() == name) - .or_else(|| { - if name.starts_with("cactus-") { - None - } else { - let cactus_name = format!("cactus-{name}"); - self.models.iter().find(|m| m.cli_name() == cactus_name) - } - }) - .cloned() - .ok_or_else(|| { - let names: Vec<&str> = self.models.iter().map(|m| m.cli_name()).collect(); - let mut hint = String::new(); - if let Some(suggestion) = did_you_mean(name, &names) { - hint.push_str(&format!("Did you mean '{suggestion}'?\n\n")); - } - hint.push_str(&format!("Run `{}` to see available models.", self.list_cmd)); - CliError::not_found(format!("{} '{name}'", self.label), Some(hint)) - }) - } -} - -pub async fn run(ctx: &AppContext, args: Args) -> CliResult<()> { - let resolved = ctx.paths(); - let models_base = resolved.models_base.clone(); - - match args.command { - Commands::List { format } => list_models(&ModelScope::all(), &models_base, format).await, - Commands::Download { name } => { - let model = ModelScope::all().resolve(&name)?; - download::download(model, &models_base, ctx.trace_buffer()).await - } - Commands::Delete { name, force } => { - let model = ModelScope::all().resolve(&name)?; - delete::delete(model, &models_base, force).await - } - } -} - -async fn list_models( - scope: &ModelScope, - models_base: &std::path::Path, - format: OutputFormat, -) -> CliResult<()> { - let manager = make_manager(models_base, None); - let rows = list::collect_model_rows(&scope.models, models_base, &manager).await; - list::write_model_output(&rows, models_base, format).await -} - -fn make_manager( - models_base: &std::path::Path, - progress_tx: Option>, -) -> ModelDownloadManager { - let runtime = Arc::new(CliModelRuntime { - models_base: models_base.to_path_buf(), - progress_tx, - }); - ModelDownloadManager::new(runtime) -} - -pub(crate) fn model_is_enabled(model: &LocalModel) -> bool { - if matches!(model, LocalModel::Am(_)) { - return false; - } - cfg!(all( - target_os = "macos", - any(target_arch = "arm", target_arch = "aarch64") - )) || !matches!(model, LocalModel::Cactus(_) | LocalModel::CactusLlm(_)) -} diff --git a/apps/cli/src/commands/model/paths.rs b/apps/cli/src/commands/model/paths.rs deleted file mode 100644 index 1d0c0871eb..0000000000 --- a/apps/cli/src/commands/model/paths.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::path::Path; - -use serde::Serialize; - -use crate::cli::OutputFormat; -use crate::error::CliResult; - -#[derive(Debug, PartialEq, Eq, Serialize)] -struct PathsOutput { - base: String, - db_path: String, - models_base: String, -} - -fn build_output(base: &Path, db_path: &Path, models_base: &Path) -> PathsOutput { - PathsOutput { - base: base.display().to_string(), - db_path: db_path.display().to_string(), - models_base: models_base.display().to_string(), - } -} - -fn format_pretty(output: &PathsOutput) -> String { - format!( - "base={}\ndb_path={}\nmodels_base={}", - output.base, output.db_path, output.models_base - ) -} - -pub(super) async fn paths( - base: &Path, - db_path: &Path, - models_base: &Path, - format: OutputFormat, -) -> CliResult<()> { - let output = build_output(base, db_path, models_base); - - match format { - OutputFormat::Pretty => println!("{}", format_pretty(&output)), - OutputFormat::Json => crate::output::write_json(None, &output).await?, - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn format_pretty_matches_existing_text_shape() { - let output = build_output( - Path::new("/tmp/char"), - Path::new("/tmp/char/app.db"), - Path::new("/tmp/char/models"), - ); - - assert_eq!( - format_pretty(&output), - "base=/tmp/char\ndb_path=/tmp/char/app.db\nmodels_base=/tmp/char/models" - ); - } - - #[test] - fn json_output_uses_three_stable_keys() { - let output = build_output( - Path::new("/tmp/char"), - Path::new("/tmp/char/app.db"), - Path::new("/tmp/char/models"), - ); - - let json = serde_json::to_value(output).unwrap(); - - assert_eq!( - json, - serde_json::json!({ - "base": "/tmp/char", - "db_path": "/tmp/char/app.db", - "models_base": "/tmp/char/models", - }) - ); - } -} diff --git a/apps/cli/src/commands/model/runtime.rs b/apps/cli/src/commands/model/runtime.rs deleted file mode 100644 index 2135b78c59..0000000000 --- a/apps/cli/src/commands/model/runtime.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::path::PathBuf; - -use hypr_local_model::LocalModel; -use hypr_model_downloader::ModelDownloaderRuntime; -use tokio::sync::mpsc; - -pub(crate) enum DownloadEvent { - Progress(u8), - Completed, - Failed, -} - -pub(crate) struct CliModelRuntime { - pub(crate) models_base: PathBuf, - pub(crate) progress_tx: Option>, -} - -impl ModelDownloaderRuntime for CliModelRuntime { - fn models_base(&self) -> Result { - Ok(self.models_base.clone()) - } - - fn emit_progress(&self, _model: &LocalModel, status: hypr_model_downloader::DownloadStatus) { - let Some(tx) = &self.progress_tx else { - return; - }; - - use hypr_model_downloader::DownloadStatus; - match status { - DownloadStatus::Downloading(p) => { - let _ = tx.send(DownloadEvent::Progress(p)); - } - DownloadStatus::Completed => { - let _ = tx.send(DownloadEvent::Completed); - } - DownloadStatus::Failed(_) => { - let _ = tx.send(DownloadEvent::Failed); - } - } - } -} diff --git a/apps/cli/src/commands/play.rs b/apps/cli/src/commands/play.rs deleted file mode 100644 index 346b6aa969..0000000000 --- a/apps/cli/src/commands/play.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::io::IsTerminal; -use std::path::PathBuf; -use std::time::Duration; - -use ratatui::text::Line; -use rodio::source::Source; - -use crate::app::AppContext; -use crate::error::{CliError, CliResult}; -use crate::tui::waveform::{ - MIC_COLOR, PlaybackWaveform, SYS_COLOR, compute_peaks, deinterleave_stereo, -}; -use crate::tui::{InlineViewport, InputAction}; - -const WAVEFORM_WIDTH: usize = 46; -const SEEK_STEP: Duration = Duration::from_secs(5); - -#[derive(clap::Args)] -pub struct Args { - /// Timestamp (e.g. 20260327_143022) or path to an audio file - pub target: String, - - /// Base directory for session lookup - #[arg(long, env = "CHAR_BASE", hide_env_values = true, value_name = "DIR")] - pub base: Option, -} - -fn resolve_audio_path(target: &str, base: Option<&std::path::Path>) -> CliResult { - let as_path = PathBuf::from(target); - if as_path.is_file() { - return Ok(as_path); - } - - let base = base.map(PathBuf::from).unwrap_or_else(|| { - dirs::data_dir() - .unwrap_or_else(std::env::temp_dir) - .join("char") - }); - - let session_audio = base.join(target).join("audio.mp3"); - if session_audio.is_file() { - return Ok(session_audio); - } - - Err(CliError::not_found( - format!("audio file for '{target}'"), - Some(format!( - "Pass a file path or a session timestamp.\nLooked in: {}", - session_audio.display() - )), - )) -} - -struct PlayState { - file_name: String, - total: Duration, - left_peaks: Vec, - right_peaks: Option>, - pos: Duration, - paused: bool, -} - -impl PlayState { - fn fraction(&self) -> f64 { - if self.total.as_secs_f64() > 0.0 { - (self.pos.as_secs_f64() / self.total.as_secs_f64()).clamp(0.0, 1.0) - } else { - 0.0 - } - } - - fn waveform_lines(&self, fraction: f64) -> Vec> { - match &self.right_peaks { - Some(right) => PlaybackWaveform::lines_dual( - &self.left_peaks, - right, - fraction, - MIC_COLOR, - SYS_COLOR, - WAVEFORM_WIDTH, - 2, - ), - None => { - PlaybackWaveform::lines(&self.left_peaks, fraction, MIC_COLOR, WAVEFORM_WIDTH, 2) - } - } - } - - fn lines(&self) -> Vec> { - let mut lines = self.waveform_lines(self.fraction()); - - let status = if self.paused { "paused " } else { "playing" }; - lines.push(Line::from(format!( - "{} {} / {} {}", - status, - format_duration(self.pos), - format_duration(self.total), - self.file_name, - ))); - - lines - } - - fn completion_lines(&self) -> Vec> { - let mut lines = self.waveform_lines(1.0); - lines.push(Line::from(format!( - "played {} {}", - format_duration(self.total), - self.file_name, - ))); - lines - } -} - -fn format_duration(d: Duration) -> String { - let secs = d.as_secs(); - format!("{:02}:{:02}", secs / 60, secs % 60) -} - -pub async fn run(ctx: &AppContext, args: Args) -> CliResult<()> { - use rodio::{Decoder, Player, stream::DeviceSinkBuilder}; - - let path = resolve_audio_path(&args.target, args.base.as_deref())?; - - let bytes = std::fs::read(&path) - .map_err(|e| CliError::operation_failed("read audio file", e.to_string()))?; - - // Decode once to analyze peaks and compute duration. - let analyze = Decoder::try_from(std::io::Cursor::new(bytes.clone())) - .map_err(|e| CliError::operation_failed("decode audio file", e.to_string()))?; - let sample_rate = analyze.sample_rate().get() as f64; - let num_channels = analyze.channels().get() as usize; - let samples: Vec = analyze.collect(); - let duration = - Duration::from_secs_f64(samples.len() as f64 / (sample_rate * num_channels as f64)); - - let (left_peaks, right_peaks) = if num_channels >= 2 { - let (left, right) = deinterleave_stereo(&samples); - ( - compute_peaks(&left, WAVEFORM_WIDTH), - Some(compute_peaks(&right, WAVEFORM_WIDTH)), - ) - } else { - (compute_peaks(&samples, WAVEFORM_WIDTH), None) - }; - - // Decode again for playback. - let source = Decoder::try_from(std::io::Cursor::new(bytes)) - .map_err(|e| CliError::operation_failed("decode audio file", e.to_string()))?; - - let mut stream = DeviceSinkBuilder::open_default_sink() - .map_err(|e| CliError::operation_failed("open audio device", e.to_string()))?; - stream.log_on_drop(false); - - let file_name = path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_else(|| path.display().to_string()); - - let mut state = PlayState { - file_name, - total: duration, - left_peaks, - right_peaks, - pos: Duration::ZERO, - paused: false, - }; - - let quiet = ctx.quiet(); - let stderr_is_tty = std::io::stderr().is_terminal(); - let mut viewport = if !quiet && stderr_is_tty { - Some( - InlineViewport::stderr_interactive(5, ctx.trace_buffer(), true) - .map_err(|e| CliError::operation_failed("init play viewport", e.to_string()))?, - ) - } else { - None - }; - - if let Some(view) = viewport.as_mut() { - view.draw(&state.lines()); - } else if !quiet { - eprintln!("Playing {}", path.display()); - } - - let player = Player::connect_new(stream.mixer()); - player.append(source); - - let tick = Duration::from_millis(100); - loop { - let done = player.get_pos() >= duration || player.empty(); - - if done { - break; - } - - tokio::select! { - _ = tokio::time::sleep(tick) => { - state.pos = player.get_pos(); - state.paused = player.is_paused(); - - if let Some(view) = viewport.as_mut() { - for action in view.poll_input() { - match action { - InputAction::TogglePause => { - if player.is_paused() { - player.play(); - } else { - player.pause(); - } - } - InputAction::SeekForward => { - let target = - (player.get_pos() + SEEK_STEP).min(state.total); - let _ = player.try_seek(target); - } - InputAction::SeekBackward => { - let target = player.get_pos().saturating_sub(SEEK_STEP); - let _ = player.try_seek(target); - } - _ => {} - } - } - state.pos = player.get_pos(); - state.paused = player.is_paused(); - view.draw(&state.lines()); - } - } - _ = tokio::signal::ctrl_c() => { - break; - } - } - } - - if let Some(view) = viewport.as_mut() { - state.pos = player.get_pos(); - view.draw(&state.completion_lines()); - view.finish() - .map_err(|e| CliError::operation_failed("finish play viewport", e.to_string()))?; - } - - Ok(()) -} diff --git a/apps/cli/src/commands/record/app.rs b/apps/cli/src/commands/record/app.rs deleted file mode 100644 index 1426cc087d..0000000000 --- a/apps/cli/src/commands/record/app.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::path::PathBuf; -use std::time::Duration; - -use ratatui::text::{Line, Span}; - -use super::{AudioMode, ProgressUpdate}; -use crate::tui::waveform::{LiveWaveform, LiveWaveformState, WaveformMode}; - -const WAVEFORM_WIDTH: usize = 16; - -pub(crate) struct App { - pub(crate) output: PathBuf, - pub(crate) sample_rate: u32, - pub(crate) channels: u16, - audio: AudioMode, - elapsed: Duration, - audio_secs: f64, - waveform: LiveWaveformState, -} - -impl App { - pub(crate) fn new(audio: AudioMode, output: PathBuf, sample_rate: u32, channels: u16) -> Self { - Self { - output, - sample_rate, - channels, - audio, - elapsed: Duration::ZERO, - audio_secs: 0.0, - waveform: LiveWaveformState::new(WAVEFORM_WIDTH), - } - } - - pub(crate) fn audio_label(&self) -> &'static str { - match self.audio { - AudioMode::Input => "mic", - AudioMode::Output => "system", - AudioMode::Dual => "dual", - } - } - - fn waveform_mode(&self) -> WaveformMode { - match self.audio { - AudioMode::Dual => WaveformMode::Dual, - AudioMode::Input | AudioMode::Output => WaveformMode::Mono, - } - } - - pub(crate) fn update(&mut self, progress: &ProgressUpdate) { - self.elapsed = progress.elapsed; - self.audio_secs = progress.audio_secs; - self.waveform - .push(progress.left_level, progress.right_level); - } - - pub(crate) fn finish(&mut self, elapsed: Duration, audio_secs: f64) { - self.elapsed = elapsed; - self.audio_secs = audio_secs; - } - - pub(crate) fn lines(&self) -> Vec> { - let file_name = self - .output - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_else(|| self.output.display().to_string()); - - let line0 = Line::from(format!( - "recording {} {}", - self.audio_label(), - format_elapsed(self.elapsed) - )); - let line1 = Line::from(format!("{} Hz {} ch", self.sample_rate, self.channels)); - - let mut spans = vec![Span::raw(format!("{} ", file_name))]; - spans.extend(LiveWaveform::spans( - &self.waveform, - self.waveform_mode(), - WAVEFORM_WIDTH, - )); - let line2 = Line::from(spans); - - vec![line0, line1, line2] - } - - pub(crate) fn completion_lines(&self) -> Vec> { - let short = short_output_path(&self.output); - let session_dir = session_dir_name(&self.output); - - vec![ - Line::from(format!("saved {:.1}s {}", self.audio_secs, short)), - Line::from(format!("char play {session_dir}")), - Line::from(format!("char transcribe {session_dir}")), - ] - } - - pub(crate) fn summary_line(&self) -> String { - let session_dir = session_dir_name(&self.output); - - format!( - "{:.1}s {}\n\nchar play {session_dir}\nchar transcribe {session_dir}", - self.audio_secs, - self.output.display(), - ) - } -} - -fn short_output_path(path: &std::path::Path) -> String { - if let Some(home) = dirs::home_dir() { - if let Ok(rel) = path.strip_prefix(&home) { - return format!("~/{}", rel.display()); - } - } - path.display().to_string() -} - -fn session_dir_name(output: &std::path::Path) -> String { - output - .parent() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_default() -} - -fn format_elapsed(duration: Duration) -> String { - let secs = duration.as_secs(); - format!("{:02}:{:02}", secs / 60, secs % 60) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tui::waveform::{MIC_COLOR, SYS_COLOR}; - - const BLOCKS: [char; 9] = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; - - #[test] - fn dual_mode_overlaid_waveform() { - let mut app = App::new(AudioMode::Dual, PathBuf::from("out.wav"), 16_000, 2); - for _ in 0..3 { - app.update(&ProgressUpdate { - elapsed: Duration::from_secs(5), - sample_count: 80_000, - audio_secs: 5.0, - left_level: 0.75, - right_level: 0.25, - render_ui: true, - emit_event: true, - }); - } - - let lines = app.lines(); - let text: String = lines[2].spans.iter().map(|s| s.content.as_ref()).collect(); - assert!(text.contains("mic")); - assert!(text.contains("sys")); - - let has_block = lines[2] - .spans - .iter() - .any(|s| s.content.chars().any(|c| BLOCKS[1..].contains(&c))); - assert!(has_block); - } - - #[test] - fn mic_dominant_gets_mic_color() { - let mut app = App::new(AudioMode::Dual, PathBuf::from("out.wav"), 16_000, 2); - app.update(&ProgressUpdate { - elapsed: Duration::from_secs(1), - sample_count: 16_000, - audio_secs: 1.0, - left_level: 0.5, - right_level: 0.01, - render_ui: true, - emit_event: true, - }); - let lines = app.lines(); - let block_span = lines[2] - .spans - .iter() - .find(|s| s.content.chars().any(|c| BLOCKS[1..].contains(&c))); - assert!(block_span.is_some()); - assert_eq!(block_span.unwrap().style.fg, Some(MIC_COLOR)); - } - - #[test] - fn sys_dominant_gets_sys_color() { - let mut app = App::new(AudioMode::Dual, PathBuf::from("out.wav"), 16_000, 2); - app.update(&ProgressUpdate { - elapsed: Duration::from_secs(1), - sample_count: 16_000, - audio_secs: 1.0, - left_level: 0.01, - right_level: 0.5, - render_ui: true, - emit_event: true, - }); - let lines = app.lines(); - let block_span = lines[2] - .spans - .iter() - .find(|s| s.content.chars().any(|c| BLOCKS[1..].contains(&c))); - assert!(block_span.is_some()); - assert_eq!(block_span.unwrap().style.fg, Some(SYS_COLOR)); - } -} diff --git a/apps/cli/src/commands/record/mod.rs b/apps/cli/src/commands/record/mod.rs deleted file mode 100644 index ef38a6b458..0000000000 --- a/apps/cli/src/commands/record/mod.rs +++ /dev/null @@ -1,263 +0,0 @@ -mod app; -mod runtime; - -use std::fs; -use std::io::{BufWriter, IsTerminal, Write}; -use std::path::PathBuf; - -use hypr_audio::AudioProvider; -use hypr_audio_actual::ActualAudio; -use hypr_audio_utils::chunk_size_for_stt; -use serde::Serialize; - -use crate::app::AppContext; -use crate::cli::OutputFormat; -use crate::error::{CliError, CliResult}; -use crate::output::EventWriter; - -pub(crate) use app::App; -pub(crate) use runtime::{CaptureResult, ProgressUpdate}; - -use crate::tui::InlineViewport; - -#[derive(Clone, Copy, Debug, clap::ValueEnum)] -pub enum AudioMode { - Input, - Output, - Dual, -} - -#[derive(clap::Args)] -pub struct Args { - #[arg(long, value_enum, default_value = "dual")] - pub audio: AudioMode, - #[arg(short = 'o', long, value_name = "FILE")] - pub output: Option, - #[arg(short = 'f', long, value_enum, default_value = "pretty")] - pub format: OutputFormat, - #[arg(long, env = "CHAR_BASE", hide_env_values = true, value_name = "DIR")] - pub base: Option, -} - -#[derive(Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum RecordEvent { - Started { - audio: String, - sample_rate: u32, - channels: u16, - output: String, - started_at: String, - }, - Progress { - elapsed_ms: u64, - audio_secs: f64, - sample_count: u64, - level_left: f32, - level_right: f32, - }, - Stopped { - reason: String, - elapsed_ms: u64, - audio_secs: f64, - output: String, - }, - Failed { - stage: String, - message: String, - }, -} - -pub async fn run(ctx: &AppContext, args: Args) -> CliResult<()> { - run_with_audio(args, ctx.quiet(), ctx.trace_buffer(), &ActualAudio).await -} - -async fn run_with_audio( - args: Args, - quiet: bool, - trace_buffer: Option, - audio: &A, -) -> CliResult<()> { - let update_check = super::update_check::UpdateHandle::spawn(); - let sample_rate = 16_000u32; - let format = args.format; - - let output_path = match args.output { - Some(output) => output, - None => { - let ts = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); - let dir = super::resolve_session_dir(args.base.as_deref(), &ts)?; - dir.join("audio.mp3") - } - }; - - let channels: u16 = match args.audio { - AudioMode::Input | AudioMode::Output => 1, - AudioMode::Dual => 2, - }; - let chunk_size = chunk_size_for_stt(sample_rate); - - let mut app = App::new(args.audio, output_path.clone(), sample_rate, channels); - - let mut event_writer = match format { - OutputFormat::Json => Some(EventWriter::new(BufWriter::new(std::io::stdout()))), - OutputFormat::Pretty => None, - }; - if let Some(writer) = event_writer.as_mut() { - writer.emit(&RecordEvent::Started { - audio: app.audio_label().to_string(), - sample_rate, - channels, - output: output_path.display().to_string(), - started_at: chrono::Utc::now().to_rfc3339(), - })?; - } - - let stderr_is_tty = std::io::stderr().is_terminal(); - let mut viewport = match format { - OutputFormat::Pretty if !quiet && stderr_is_tty => Some( - InlineViewport::stderr(5, trace_buffer) - .map_err(|e| CliError::operation_failed("init record viewport", e.to_string()))?, - ), - _ => None, - }; - if let Some(view) = viewport.as_mut() { - view.draw(&app.lines()); - } else if !quiet && matches!(format, OutputFormat::Pretty) { - eprintln!( - "recording {} -> {}", - app.audio_label(), - output_path.display() - ); - } - - let capture = runtime::capture(audio, args.audio, sample_rate, chunk_size, |progress| { - app.update(&progress); - if progress.emit_event - && let Some(writer) = event_writer.as_mut() - { - writer.emit(&RecordEvent::Progress { - elapsed_ms: progress.elapsed.as_millis() as u64, - audio_secs: progress.audio_secs, - sample_count: progress.sample_count, - level_left: progress.left_level, - level_right: progress.right_level, - })?; - } - if progress.render_ui - && let Some(view) = viewport.as_mut() - { - view.poll_input(); - view.draw(&app.lines()); - } - Ok(()) - }) - .await; - - match capture { - Ok(result) => { - finish_success( - result, - &mut app, - viewport.as_mut(), - event_writer.as_mut(), - quiet, - update_check, - ) - .await - } - Err(error) => { - if let Some(view) = viewport.as_mut() { - view.clear().ok(); - } - if let Some(writer) = event_writer.as_mut() { - writer.emit(&RecordEvent::Failed { - stage: "capture".to_string(), - message: error.to_string(), - })?; - } - Err(error) - } - } -} - -async fn finish_success( - result: CaptureResult, - app: &mut App, - viewport: Option<&mut InlineViewport>, - event_writer: Option<&mut EventWriter>, - quiet: bool, - update_check: super::update_check::UpdateHandle, -) -> CliResult<()> { - ensure_parent_dirs(&app.output)?; - - let mut mp3_buf = Vec::new(); - match app.channels { - 1 => { - let mut encoder = hypr_mp3::MonoStreamEncoder::new(app.sample_rate) - .map_err(|e| CliError::operation_failed("create mp3 encoder", e.to_string()))?; - encoder - .encode_i16(&result.samples, &mut mp3_buf) - .map_err(|e| CliError::operation_failed("encode mp3", e.to_string()))?; - encoder - .flush(&mut mp3_buf) - .map_err(|e| CliError::operation_failed("flush mp3", e.to_string()))?; - } - 2 => { - let mut encoder = hypr_mp3::StereoStreamEncoder::new(app.sample_rate) - .map_err(|e| CliError::operation_failed("create mp3 encoder", e.to_string()))?; - let (left, right): (Vec, Vec) = result - .samples - .chunks(2) - .map(|pair| (pair[0], pair.get(1).copied().unwrap_or(0))) - .unzip(); - encoder - .encode_i16(&left, &right, &mut mp3_buf) - .map_err(|e| CliError::operation_failed("encode mp3", e.to_string()))?; - encoder - .flush(&mut mp3_buf) - .map_err(|e| CliError::operation_failed("flush mp3", e.to_string()))?; - } - _ => { - return Err(CliError::operation_failed( - "encode mp3", - format!("unsupported channel count: {}", app.channels), - )); - } - } - std::fs::write(&app.output, &mp3_buf) - .map_err(|e| CliError::operation_failed("write mp3 file", e.to_string()))?; - - app.finish(result.elapsed, result.audio_secs); - - if let Some(writer) = event_writer { - writer.emit(&RecordEvent::Stopped { - reason: result.stop_reason.as_str().to_string(), - elapsed_ms: result.elapsed.as_millis() as u64, - audio_secs: result.audio_secs, - output: app.output.display().to_string(), - })?; - } - - if let Some(view) = viewport { - view.draw(&app.completion_lines()); - view.finish() - .map_err(|e| CliError::operation_failed("finish record viewport", e.to_string()))?; - } else if !quiet { - eprintln!("{}", app.summary_line()); - } - - if let Some(version) = update_check.result().await { - eprintln!(" update available ({version}): char update"); - } - - Ok(()) -} - -fn ensure_parent_dirs(path: &std::path::Path) -> CliResult<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .map_err(|e| CliError::operation_failed("create output directory", e.to_string()))?; - } - Ok(()) -} diff --git a/apps/cli/src/commands/record/runtime.rs b/apps/cli/src/commands/record/runtime.rs deleted file mode 100644 index b8ca2fc04d..0000000000 --- a/apps/cli/src/commands/record/runtime.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::pin::pin; -use std::time::{Duration, Instant}; - -use hypr_audio::{AudioProvider, CaptureConfig}; -use tokio::signal; -use tokio_stream::StreamExt; - -use super::AudioMode; -use crate::error::{CliError, CliResult}; - -const UI_TICK: Duration = Duration::from_millis(250); -const EVENT_TICK: Duration = Duration::from_secs(1); - -pub(crate) struct ProgressUpdate { - pub(crate) elapsed: Duration, - pub(crate) sample_count: u64, - pub(crate) audio_secs: f64, - pub(crate) left_level: f32, - pub(crate) right_level: f32, - pub(crate) render_ui: bool, - pub(crate) emit_event: bool, -} - -pub(crate) struct CaptureResult { - pub(crate) samples: Vec, - pub(crate) elapsed: Duration, - pub(crate) audio_secs: f64, - pub(crate) stop_reason: StopReason, -} - -#[derive(Clone, Copy)] -pub(crate) enum StopReason { - CtrlC, - Eof, -} - -impl StopReason { - pub(crate) fn as_str(self) -> &'static str { - match self { - Self::CtrlC => "ctrl_c", - Self::Eof => "eof", - } - } -} - -pub(crate) async fn capture( - audio: &A, - mode: AudioMode, - sample_rate: u32, - chunk_size: usize, - mut on_progress: impl FnMut(ProgressUpdate) -> CliResult<()>, -) -> CliResult { - let stream = match mode { - AudioMode::Input => audio - .open_mic_capture(None, sample_rate, chunk_size) - .map_err(|e| CliError::operation_failed("open mic capture", e.to_string()))?, - AudioMode::Output => audio - .open_speaker_capture(sample_rate, chunk_size) - .map_err(|e| CliError::operation_failed("open speaker capture", e.to_string()))?, - AudioMode::Dual => audio - .open_capture(CaptureConfig { - sample_rate, - chunk_size, - mic_device: None, - enable_aec: false, - }) - .map_err(|e| CliError::operation_failed("open dual capture", e.to_string()))?, - }; - let mut stream = pin!(stream); - let mut samples = Vec::new(); - let started = Instant::now(); - let mut last_ui = Instant::now() - UI_TICK; - let mut last_event = Instant::now() - EVENT_TICK; - let mut stop_reason = StopReason::Eof; - - loop { - tokio::select! { - frame = stream.next() => { - let Some(result) = frame else { break }; - let frame = result - .map_err(|e| CliError::operation_failed("audio capture", e.to_string()))?; - - let (left, right) = match mode { - AudioMode::Input => { - let raw = frame.preferred_mic(); - samples.extend(raw.iter().map(|&s| to_i16(s))); - (peak_level(&raw), 0.0) - } - AudioMode::Output => { - let raw = &frame.raw_speaker; - samples.extend(raw.iter().map(|&s| to_i16(s))); - (peak_level(raw), 0.0) - } - AudioMode::Dual => { - let (mic, speaker) = frame.raw_dual(); - for (&m, &s) in mic.iter().zip(speaker.iter()) { - samples.push(to_i16(m)); - samples.push(to_i16(s)); - } - (peak_level(&mic), peak_level(&speaker)) - } - }; - - let elapsed = started.elapsed(); - let audio_secs = samples_to_audio_secs(samples.len() as u64, sample_rate, mode); - let render_ui = last_ui.elapsed() >= UI_TICK; - let emit_event = last_event.elapsed() >= EVENT_TICK; - if render_ui || emit_event { - on_progress(ProgressUpdate { - elapsed, - sample_count: audio_frame_count(samples.len() as u64, mode), - audio_secs, - left_level: left, - right_level: right, - render_ui, - emit_event, - })?; - let now = Instant::now(); - if render_ui { - last_ui = now; - } - if emit_event { - last_event = now; - } - } - } - _ = signal::ctrl_c() => { - stop_reason = StopReason::CtrlC; - break; - } - } - } - - let elapsed = started.elapsed(); - let audio_secs = samples_to_audio_secs(samples.len() as u64, sample_rate, mode); - on_progress(ProgressUpdate { - elapsed, - sample_count: audio_frame_count(samples.len() as u64, mode), - audio_secs, - left_level: 0.0, - right_level: 0.0, - render_ui: true, - emit_event: true, - })?; - - Ok(CaptureResult { - samples, - elapsed, - audio_secs, - stop_reason, - }) -} - -fn to_i16(sample: f32) -> i16 { - (sample * 32767.0) as i16 -} - -fn peak_level(samples: &[f32]) -> f32 { - samples - .iter() - .map(|sample| sample.abs()) - .fold(0.0_f32, f32::max) - .clamp(0.0, 1.0) -} - -fn samples_to_audio_secs(samples_len: u64, sample_rate: u32, mode: AudioMode) -> f64 { - let divisor = match mode { - AudioMode::Dual => sample_rate as f64 * 2.0, - AudioMode::Input | AudioMode::Output => sample_rate as f64, - }; - samples_len as f64 / divisor -} - -fn audio_frame_count(samples_len: u64, mode: AudioMode) -> u64 { - match mode { - AudioMode::Dual => samples_len / 2, - AudioMode::Input | AudioMode::Output => samples_len, - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use super::*; - use hypr_audio::{CaptureFrame, CaptureStream, Error}; - use tokio_stream::iter; - - #[test] - fn peak_level_clamps_to_unit_interval() { - assert_eq!(peak_level(&[0.1, -0.4, 0.2]), 0.4); - assert_eq!(peak_level(&[2.0]), 1.0); - } - - #[test] - fn dual_audio_frame_count_uses_stereo_pairs() { - assert_eq!(audio_frame_count(10, AudioMode::Dual), 5); - assert_eq!(audio_frame_count(10, AudioMode::Input), 10); - } - - struct TestAudio { - frames: Vec>, - } - - impl AudioProvider for TestAudio { - fn open_capture(&self, _config: CaptureConfig) -> Result { - Ok(CaptureStream::new(iter(self.frames.clone()))) - } - - fn open_speaker_capture( - &self, - _sample_rate: u32, - _chunk_size: usize, - ) -> Result { - Ok(CaptureStream::new(iter(self.frames.clone()))) - } - - fn open_mic_capture( - &self, - _device: Option, - _sample_rate: u32, - _chunk_size: usize, - ) -> Result { - Ok(CaptureStream::new(iter(self.frames.clone()))) - } - - fn default_device_name(&self) -> String { - "test".to_string() - } - - fn list_mic_devices(&self) -> Vec { - vec!["test".to_string()] - } - - fn play_silence(&self) -> std::sync::mpsc::Sender<()> { - let (tx, _rx) = std::sync::mpsc::channel(); - tx - } - - fn play_bytes(&self, _bytes: &'static [u8]) -> std::sync::mpsc::Sender<()> { - let (tx, _rx) = std::sync::mpsc::channel(); - tx - } - - fn probe_mic(&self, _device: Option) -> Result<(), Error> { - Ok(()) - } - - fn probe_speaker(&self) -> Result<(), Error> { - Ok(()) - } - } - - #[tokio::test] - async fn capture_returns_samples_and_eof_reason() { - let audio = TestAudio { - frames: vec![Ok(CaptureFrame { - raw_mic: Arc::from([0.25_f32, -0.25, 0.5, -0.5]), - raw_speaker: Arc::from([]), - aec_mic: None, - })], - }; - let mut updates = Vec::new(); - - let result = capture(&audio, AudioMode::Input, 16_000, 4, |progress| { - updates.push(progress.sample_count); - Ok(()) - }) - .await - .unwrap(); - - assert_eq!(result.stop_reason.as_str(), "eof"); - assert_eq!(result.samples.len(), 4); - assert!(!updates.is_empty()); - } -} diff --git a/apps/cli/src/commands/skill/install.rs b/apps/cli/src/commands/skill/install.rs deleted file mode 100644 index 7a2b1bec22..0000000000 --- a/apps/cli/src/commands/skill/install.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::io::IsTerminal; -use std::path::PathBuf; -use std::time::Duration; - -use ratatui::style::{Color, Style}; -use ratatui::text::{Line, Span}; - -use crate::error::{CliError, CliResult}; -use crate::tui::InlineViewport; -use crate::tui::InputAction; - -const SKILL_CONTENT: &str = include_str!("../../../../../skills/cli/SKILL.md"); - -#[derive(Clone, Copy)] -enum SkillFormat { - ClaudeCode, - Codex, - GitHubCopilot, -} - -impl SkillFormat { - const ALL: [SkillFormat; 3] = [ - SkillFormat::ClaudeCode, - SkillFormat::Codex, - SkillFormat::GitHubCopilot, - ]; - - fn label(self) -> &'static str { - match self { - SkillFormat::ClaudeCode => "Claude Code", - SkillFormat::Codex => "Codex", - SkillFormat::GitHubCopilot => "GitHub Copilot", - } - } - - fn dir(self) -> &'static str { - match self { - SkillFormat::ClaudeCode => ".claude/skills/char", - SkillFormat::Codex => ".codex/skills/char", - SkillFormat::GitHubCopilot => ".github/skills/char", - } - } - - fn cli_value(self) -> &'static str { - match self { - SkillFormat::ClaudeCode => "claude", - SkillFormat::Codex => "codex", - SkillFormat::GitHubCopilot => "github-copilot", - } - } - - fn from_str(s: &str) -> Option { - match s { - "claude" => Some(SkillFormat::ClaudeCode), - "codex" => Some(SkillFormat::Codex), - "github-copilot" => Some(SkillFormat::GitHubCopilot), - _ => None, - } - } -} - -const HIGHLIGHT: Color = Color::Rgb(0xFD, 0xE6, 0xAE); - -fn selector_lines(selected: usize) -> Vec> { - let mut lines = vec![]; - - for (i, format) in SkillFormat::ALL.iter().enumerate() { - let marker = if i == selected { "> " } else { " " }; - let style = if i == selected { - Style::default().fg(HIGHLIGHT) - } else { - Style::default().fg(Color::DarkGray) - }; - - lines.push(Line::from(vec![ - Span::styled(marker, style), - Span::styled(format.label(), style), - Span::raw(" "), - Span::styled( - format!("./{}/", format.dir()), - Style::default().fg(Color::DarkGray), - ), - ])); - } - - lines -} - -fn write_skill(format: SkillFormat) -> CliResult { - let dir = PathBuf::from(format.dir()); - std::fs::create_dir_all(&dir) - .map_err(|e| CliError::operation_failed("create skill directory", e.to_string()))?; - - let path = dir.join("SKILL.md"); - std::fs::write(&path, SKILL_CONTENT) - .map_err(|e| CliError::operation_failed("write SKILL.md", e.to_string()))?; - - Ok(path) -} - -pub fn run(format_arg: Option<&str>) -> CliResult<()> { - if let Some(name) = format_arg { - let format = SkillFormat::from_str(name).ok_or_else(|| { - CliError::invalid_argument( - "--format", - name, - format!( - "expected one of: {}", - SkillFormat::ALL - .iter() - .map(|f| f.cli_value()) - .collect::>() - .join(", ") - ), - ) - })?; - let path = write_skill(format)?; - eprintln!("Installed char skill at {}", path.display()); - return Ok(()); - } - - if !std::io::stderr().is_terminal() { - return Err(CliError::operation_failed( - "skill install", - "interactive selection requires a terminal; use --format to specify".to_string(), - )); - } - - let mut selected: usize = 0; - let count = SkillFormat::ALL.len(); - - let mut viewport = InlineViewport::stderr_interactive(3, None, true) - .map_err(|e| CliError::operation_failed("create viewport", e.to_string()))?; - - viewport.draw(&selector_lines(selected)); - - loop { - std::thread::sleep(Duration::from_millis(30)); - - for action in viewport.poll_input() { - match action { - InputAction::Up => { - selected = if selected == 0 { - count - 1 - } else { - selected - 1 - }; - } - InputAction::Down => { - selected = (selected + 1) % count; - } - InputAction::Confirm => { - viewport - .clear() - .map_err(|e| CliError::operation_failed("clear viewport", e.to_string()))?; - - let format = SkillFormat::ALL[selected]; - let path = write_skill(format)?; - eprintln!("Installed char skill at {}", path.display()); - return Ok(()); - } - _ => {} - } - } - - viewport.draw(&selector_lines(selected)); - } -} diff --git a/apps/cli/src/commands/skill/mod.rs b/apps/cli/src/commands/skill/mod.rs deleted file mode 100644 index 8c5692826f..0000000000 --- a/apps/cli/src/commands/skill/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -mod install; - -use clap::Subcommand; - -use crate::app::AppContext; -use crate::error::CliResult; - -#[derive(Subcommand)] -pub enum Commands { - /// Install char skill for AI coding agents - Install { - /// Skip interactive selection: claude, codex, github-copilot - #[arg(short, long)] - format: Option, - }, -} - -pub async fn run(_ctx: &AppContext, command: Commands) -> CliResult<()> { - match command { - Commands::Install { format } => install::run(format.as_deref()), - } -} diff --git a/apps/cli/src/commands/todo/mod.rs b/apps/cli/src/commands/todo/mod.rs deleted file mode 100644 index bc6d8d54aa..0000000000 --- a/apps/cli/src/commands/todo/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -use clap::Subcommand; - -use crate::error::CliResult; - -#[derive(Subcommand)] -pub enum Commands { - /// Claude Code integration - Claude { - #[command(subcommand)] - command: crate::commands::integration::claude::Commands, - }, - /// Codex integration - Codex { - #[command(subcommand)] - command: crate::commands::integration::codex::Commands, - }, - /// OpenCode integration - Opencode { - #[command(subcommand)] - command: crate::commands::integration::opencode::Commands, - }, -} - -pub async fn run(command: Option) -> CliResult<()> { - match command { - Some(Commands::Claude { command }) => { - crate::commands::integration::claude::run(command).await - } - Some(Commands::Codex { command }) => { - crate::commands::integration::codex::run(command).await - } - Some(Commands::Opencode { command }) => { - crate::commands::integration::opencode::run(command).await - } - None => { - eprintln!("Todo is not ready yet."); - Ok(()) - } - } -} diff --git a/apps/cli/src/commands/transcribe/mod.rs b/apps/cli/src/commands/transcribe/mod.rs deleted file mode 100644 index c7dc610019..0000000000 --- a/apps/cli/src/commands/transcribe/mod.rs +++ /dev/null @@ -1,804 +0,0 @@ -mod output; - -use std::io::BufWriter; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::time::Duration; - -use serde::Serialize; -use tokio::sync::mpsc; - -use hypr_listener2_core::{BatchErrorCode, BatchEvent}; -use owhisper_interface::batch_stream::BatchStreamEvent; - -use crate::OptTraceBuffer; -use crate::app::AppContext; -use crate::cli::OutputFormat; -use crate::error::{CliError, CliResult}; -use crate::stt::{ChannelBatchRuntime, SttOverrides, resolve_config}; - -#[derive(clap::Args)] -pub struct Args { - /// Timestamp (e.g. 20260327_143022) or path to an audio file - #[arg(value_name = "INPUT")] - pub target: Option, - #[arg(short = 'i', long, value_name = "FILE", visible_alias = "file")] - pub input: Option, - #[arg(short = 'p', long, value_enum)] - pub provider: crate::stt::SttProvider, - #[arg(long = "keyword", short = 'k', value_name = "KEYWORD")] - pub keywords: Vec, - #[arg(short = 'o', long, value_name = "FILE")] - pub output: Option, - #[arg(short = 'f', long, value_enum, default_value = "pretty")] - pub format: OutputFormat, - #[arg(long, env = "CHAR_BASE", hide_env_values = true, value_name = "DIR")] - pub base: Option, - #[arg(long, env = "CHAR_BASE_URL", hide_env_values = true, value_parser = crate::cli::parse_base_url)] - pub base_url: Option, - #[arg(long, env = "CHAR_API_KEY", hide_env_values = true)] - pub api_key: Option, - #[arg(short = 'm', long, env = "CHAR_MODEL", hide_env_values = true)] - pub model: Option, - #[arg( - short = 'l', - long, - env = "CHAR_LANGUAGE", - hide_env_values = true, - default_value = "en" - )] - pub language: String, -} - -// -- JSONL event types (for --format json) -- - -#[derive(Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum TranscribeEvent { - Started { - input: String, - provider: String, - }, - Progress { - percentage: f64, - transcript: String, - }, - Completed { - elapsed_ms: u64, - audio_duration_secs: Option, - response: owhisper_interface::batch::Response, - }, - Failed { - code: String, - message: String, - }, -} - -use crate::output::EventWriter; - -// -- Batch handle -- - -struct BatchHandle { - rx: mpsc::UnboundedReceiver, - task: tokio::task::JoinHandle< - Result, - >, - started: std::time::Instant, - _normalized_input_dir: Option, - _server: crate::stt::ServerGuard, -} - -async fn start_batch( - input: &Path, - keywords: Vec, - stt: SttOverrides, - audio_dest: Option<&Path>, - on_normalize_progress: Option<&mut dyn FnMut(f64)>, -) -> CliResult { - let resolved = resolve_config(stt).await?; - let (temp_dir, normalized_input_path) = - normalize_input_file(input, audio_dest, on_normalize_progress)?; - let params = build_batch_params(&resolved, &normalized_input_path, keywords)?; - - let (batch_tx, batch_rx) = mpsc::unbounded_channel::(); - let runtime = Arc::new(ChannelBatchRuntime { tx: batch_tx }); - - let started = std::time::Instant::now(); - let task = tokio::spawn(async move { hypr_listener2_core::run_batch(runtime, params).await }); - - Ok(BatchHandle { - rx: batch_rx, - task, - started, - _normalized_input_dir: temp_dir, - _server: resolved.server, - }) -} - -fn normalize_input_file( - input: &Path, - dest: Option<&Path>, - on_progress: Option<&mut dyn FnMut(f64)>, -) -> CliResult<(Option, PathBuf)> { - let (temp_dir, tmp_path, target_path) = if let Some(dest) = dest { - let tmp_path = dest.with_extension("mp3.tmp"); - (None, tmp_path, dest.to_path_buf()) - } else { - let td = tempfile::Builder::new() - .prefix("char-transcribe-") - .tempdir() - .map_err(|e| { - CliError::operation_failed("create normalization tempdir", e.to_string()) - })?; - let tmp_path = td.path().join("input.mp3.tmp"); - let target_path = td.path().join("input.mp3"); - (Some(td), tmp_path, target_path) - }; - - hypr_audio_norm::normalize_file(input, &tmp_path, &target_path, None, on_progress) - .map_err(|e| CliError::operation_failed("normalize audio input", e.to_string()))?; - - Ok((temp_dir, target_path)) -} - -fn build_batch_params( - resolved: &crate::stt::ResolvedSttConfig, - input: &std::path::Path, - keywords: Vec, -) -> CliResult { - let file_path = input.to_str().ok_or_else(|| { - CliError::invalid_argument( - "--input", - input.display().to_string(), - "path must be valid utf-8", - ) - })?; - - Ok(resolved.to_batch_params( - uuid::Uuid::new_v4().to_string(), - file_path.to_string(), - keywords, - )) -} - -fn input_timestamp(path: &Path) -> String { - let system_time = std::fs::metadata(path) - .and_then(|m| m.modified()) - .unwrap_or_else(|_| std::time::SystemTime::now()); - format_timestamp(system_time) -} - -fn format_timestamp(t: std::time::SystemTime) -> String { - let secs = t - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - let days = secs / 86400; - let time_of_day = secs % 86400; - let hour = time_of_day / 3600; - let min = (time_of_day % 3600) / 60; - let sec = time_of_day % 60; - - // days since epoch to y/m/d (civil calendar) - let (y, m, d) = days_to_ymd(days as i64); - format!("{y:04}{m:02}{d:02}_{hour:02}{min:02}{sec:02}") -} - -fn days_to_ymd(days: i64) -> (i64, u32, u32) { - // Algorithm from http://howardhinnant.github.io/date_algorithms.html - let z = days + 719468; - let era = z.div_euclid(146097); - let doe = z.rem_euclid(146097) as u64; - let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; - let y = yoe as i64 + era * 400; - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - let mp = (5 * doy + 2) / 153; - let d = (doy - (153 * mp + 2) / 5 + 1) as u32; - let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32; - let y = if m <= 2 { y + 1 } else { y }; - (y, m, d) -} - -fn extract_stream_transcript(event: &BatchStreamEvent) -> Option<&str> { - event.text() -} - -struct CollectedBatch { - response: owhisper_interface::batch::Response, - elapsed: Duration, -} - -struct OutputTarget { - output_path: PathBuf, - audio_dest: Option, -} - -fn finish_batch( - task_result: Result< - Result, - tokio::task::JoinError, - >, - failure: Option<(BatchErrorCode, String)>, - started: std::time::Instant, -) -> CliResult { - let result = task_result - .map_err(|e| CliError::operation_failed("batch transcription", e.to_string()))?; - let output = if let Ok(output) = result { - output - } else { - let error = result.err().unwrap(); - let message = if let Some((code, message)) = failure { - format!("{code:?}: {message}") - } else { - error.to_string() - }; - return Err(CliError::operation_failed("batch transcription", message)); - }; - - Ok(CollectedBatch { - response: output.response, - elapsed: started.elapsed(), - }) -} - -fn resolve_input( - target: Option<&str>, - input: Option<&str>, - base: Option<&Path>, -) -> CliResult { - let raw = match (target, input) { - (Some(t), _) => t, - (None, Some(i)) => i, - (None, None) => { - return Err(CliError::invalid_argument( - "INPUT", - "(missing)", - "provide an audio file or session timestamp", - )); - } - }; - - let as_path = PathBuf::from(raw); - if as_path.is_file() { - return Ok(as_path); - } - - let base_dir = base.map(Path::to_path_buf).unwrap_or_else(|| { - dirs::data_dir() - .unwrap_or_else(std::env::temp_dir) - .join("char") - }); - - let session_audio = base_dir.join(raw).join("audio.mp3"); - if session_audio.is_file() { - return Ok(session_audio); - } - - Err(CliError::not_found( - format!("audio file for '{raw}'"), - Some(format!( - "Pass a file path or a session timestamp.\nLooked in: {}", - session_audio.display() - )), - )) -} - -fn resolve_output_target( - input_path: &Path, - output: Option, - base: Option<&Path>, -) -> CliResult { - if let Some(output_path) = output { - return Ok(OutputTarget { - output_path, - audio_dest: None, - }); - } - - let ts = input_timestamp(input_path); - let dir = super::resolve_session_dir(base, &ts)?; - - Ok(OutputTarget { - output_path: dir.join("transcript.json"), - audio_dest: Some(dir.join("audio.mp3")), - }) -} - -fn saves_json(output_path: &Path) -> bool { - output_path.file_name() == Some(std::ffi::OsStr::new("transcript.json")) -} - -async fn save_pretty_output( - output_path: Option<&Path>, - pretty: &str, - response: &owhisper_interface::batch::Response, -) -> CliResult<()> { - let Some(output_path) = output_path else { - return Ok(()); - }; - - if saves_json(output_path) { - crate::output::write_json(Some(output_path), response).await - } else { - crate::output::write_text(Some(output_path), pretty.to_string()).await - } -} - -// -- Entry point -- - -#[allow(clippy::unit_arg)] -pub async fn run(ctx: &AppContext, args: Args) -> CliResult<()> { - let input_path = resolve_input( - args.target.as_deref(), - args.input.as_deref(), - args.base.as_deref(), - )?; - let format = args.format; - let output_target = - resolve_output_target(&input_path, args.output.clone(), args.base.as_deref())?; - let output_path = Some(output_target.output_path.clone()); - let input_display = input_path.display().to_string(); - let provider_display = format!("{:?}", args.provider).to_lowercase(); - let stt = ctx.stt_overrides( - Some(args.provider), - args.base_url.clone(), - args.api_key.clone(), - args.model.clone(), - args.language.clone(), - ); - - match format { - OutputFormat::Json => { - run_json( - &input_path, - args, - stt, - output_path, - output_target.audio_dest, - input_display, - provider_display, - ) - .await - } - OutputFormat::Pretty => { - run_pretty( - &input_path, - args, - stt, - output_path, - output_target.audio_dest, - ctx.trace_buffer(), - ) - .await - } - } -} - -// -- JSON mode -- - -async fn run_json( - input_path: &Path, - args: Args, - stt: SttOverrides, - output_path: Option, - audio_dest: Option, - input_display: String, - provider_display: String, -) -> CliResult<()> { - let mut writer = EventWriter::new(BufWriter::new(std::io::stdout())); - - writer.emit(&TranscribeEvent::Started { - input: input_display, - provider: provider_display, - })?; - - let mut handle = match start_batch( - input_path, - args.keywords.clone(), - stt, - audio_dest.as_deref(), - None, - ) - .await - { - Ok(h) => h, - Err(e) => { - let _ = writer.emit(&TranscribeEvent::Failed { - code: "error".to_string(), - message: e.to_string(), - }); - return Err(e); - } - }; - - let mut failure: Option<(BatchErrorCode, String)> = None; - - while let Some(event) = handle.rx.recv().await { - match event { - BatchEvent::BatchStarted { .. } | BatchEvent::BatchCompleted { .. } => {} - BatchEvent::BatchResponseStreamed { - event: streamed, .. - } => { - let transcript = extract_stream_transcript(&streamed) - .unwrap_or("") - .to_string(); - writer.emit(&TranscribeEvent::Progress { - percentage: streamed.percentage(), - transcript, - })?; - } - BatchEvent::BatchResponse { .. } => {} - BatchEvent::BatchFailed { code, error, .. } => { - failure = Some((code, error)); - } - } - } - - let result = match finish_batch(handle.task.await, failure, handle.started) { - Ok(r) => r, - Err(e) => { - let _ = writer.emit(&TranscribeEvent::Failed { - code: "error".to_string(), - message: e.to_string(), - }); - return Err(e); - } - }; - - let audio_duration_secs = result - .response - .metadata - .get("duration") - .and_then(|v| v.as_f64()); - - writer.emit(&TranscribeEvent::Completed { - elapsed_ms: result.elapsed.as_millis() as u64, - audio_duration_secs, - response: result.response.clone(), - })?; - - if let Some(path) = &output_path { - crate::output::write_json(Some(path.as_path()), &result.response).await?; - } - - Ok(()) -} - -// -- Pretty mode -- - -async fn run_pretty( - input_path: &Path, - args: Args, - stt: SttOverrides, - output_path: Option, - audio_dest: Option, - _trace_buffer: OptTraceBuffer, -) -> CliResult<()> { - let update_check = super::update_check::UpdateHandle::spawn(); - - #[cfg(feature = "_cli-audio")] - let file_name = input_path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_else(|| input_path.display().to_string()); - - #[cfg(feature = "_cli-audio")] - let mut viewport = if _trace_buffer.is_some() { - crate::tui::InlineViewport::stderr(5, _trace_buffer).ok() - } else { - None - }; - - #[cfg(feature = "_cli-audio")] - let mut normalize_spinner_idx = 0usize; - #[cfg(feature = "_cli-audio")] - let mut normalize_progress = |percentage: f64| { - normalize_spinner_idx = (normalize_spinner_idx + 1) % crate::tui::SPINNER.len(); - draw_normalization( - &mut viewport, - &file_name, - normalize_spinner_idx, - percentage.clamp(0.0, 1.0), - ); - }; - - #[cfg(feature = "_cli-audio")] - let mut handle = match start_batch( - input_path, - args.keywords.clone(), - stt, - audio_dest.as_deref(), - Some(&mut normalize_progress), - ) - .await - { - Ok(handle) => handle, - Err(error) => { - if let Some(ref mut vp) = viewport { - let _ = vp.clear(); - } - return Err(error); - } - }; - - #[cfg(not(feature = "_cli-audio"))] - let mut handle = start_batch( - input_path, - args.keywords.clone(), - stt, - audio_dest.as_deref(), - None, - ) - .await?; - - let mut failure: Option<(BatchErrorCode, String)> = None; - - #[cfg(feature = "_cli-audio")] - let (mut spinner_idx, mut last_pct, mut last_transcript, mut tick) = ( - 0usize, - 0.0f64, - String::new(), - tokio::time::interval(Duration::from_millis(80)), - ); - - loop { - #[cfg(feature = "_cli-audio")] - let event = if viewport.is_some() { - tokio::select! { - ev = handle.rx.recv() => ev, - _ = tick.tick() => { - spinner_idx = (spinner_idx + 1) % crate::tui::SPINNER.len(); - if let Some(ref mut vp) = viewport { - vp.poll_input(); - let pct_str = format!("{:.0}%", last_pct * 100.0); - vp.draw_strings(&[ - format!("{} Transcribing {}... {}", crate::tui::SPINNER[spinner_idx], file_name, pct_str), - format!(" {}", crate::tui::truncate_line(&last_transcript, 76)), - ]); - } - continue; - } - } - } else { - handle.rx.recv().await - }; - - #[cfg(not(feature = "_cli-audio"))] - let event = handle.rx.recv().await; - - let Some(event) = event else { break }; - - match event { - BatchEvent::BatchStarted { .. } | BatchEvent::BatchCompleted { .. } => {} - BatchEvent::BatchResponseStreamed { - event: streamed, .. - } => { - #[cfg(feature = "_cli-audio")] - { - last_pct = streamed.percentage(); - if let Some(t) = extract_stream_transcript(&streamed) - && !t.is_empty() - { - last_transcript = t.to_string(); - } - } - #[cfg(not(feature = "_cli-audio"))] - { - let _ = streamed; - } - } - BatchEvent::BatchResponse { .. } => {} - BatchEvent::BatchFailed { code, error, .. } => { - failure = Some((code, error)); - } - } - } - - let result = finish_batch(handle.task.await, failure, handle.started)?; - let response = &result.response; - let pretty = output::format_pretty(response); - - crate::output::write_text(None, pretty.clone()).await?; - save_pretty_output(output_path.as_deref(), &pretty, response).await?; - - let audio_duration = response - .metadata - .get("duration") - .and_then(|v| v.as_f64()) - .unwrap_or(0.0); - - let mut parts = Vec::new(); - if audio_duration > 0.0 { - parts.push(format!("{:.1}s audio", audio_duration)); - } - parts.push(format!("in {:.1}s", result.elapsed.as_secs_f64())); - - #[cfg(feature = "_cli-audio")] - if let Some(ref mut vp) = viewport { - let mut lines = vec![format!("saved {}", parts.join(" "))]; - if let Some(path) = &output_path { - lines.push(format!("{}", path.display())); - } - vp.draw_strings(&lines); - vp.finish() - .map_err(|e| CliError::operation_failed("finish viewport", e.to_string()))?; - } else { - if let Some(path) = &output_path { - parts.push(format!("-> {}", path.display())); - } - use colored::Colorize; - eprintln!("{}", parts.join(", ").dimmed()); - } - - #[cfg(not(feature = "_cli-audio"))] - { - if let Some(path) = &output_path { - parts.push(format!("-> {}", path.display())); - } - use colored::Colorize; - eprintln!("{}", parts.join(", ").dimmed()); - } - - if let Some(version) = update_check.result().await { - eprintln!(" update available ({version}): char update"); - } - - Ok(()) -} - -#[cfg(feature = "_cli-audio")] -fn draw_normalization( - viewport: &mut Option, - file_name: &str, - spinner_idx: usize, - percentage: f64, -) { - if let Some(vp) = viewport { - vp.poll_input(); - let pct = (percentage * 100.0).round().clamp(0.0, 100.0) as u8; - vp.draw_strings(&[ - format!( - "{} Normalizing {}... {}%", - crate::tui::SPINNER[spinner_idx], - file_name, - pct - ), - format_gauge(pct), - ]); - } -} - -#[cfg(feature = "_cli-audio")] -fn format_gauge(pct: u8) -> String { - let width = 40; - let filled = (pct as usize * width) / 100; - let empty = width - filled; - format!(" [{}{}]", "█".repeat(filled), "░".repeat(empty)) -} - -#[cfg(test)] -mod tests { - use std::io::Cursor; - use std::path::Path; - - use owhisper_interface::batch; - - use super::*; - - #[test] - fn event_writer_serializes_jsonl_lines() { - let mut bytes = Cursor::new(Vec::new()); - let mut writer = EventWriter::new(&mut bytes); - - writer - .emit(&TranscribeEvent::Started { - input: "test.wav".to_string(), - provider: "deepgram".to_string(), - }) - .unwrap(); - - let output = String::from_utf8(bytes.into_inner()).unwrap(); - assert!(output.ends_with('\n')); - assert!(output.contains("\"type\":\"started\"")); - assert!(output.contains("\"input\":\"test.wav\"")); - } - - #[test] - fn build_batch_params_preserves_keywords() { - let resolved = crate::stt::ResolvedSttConfig { - provider: hypr_listener2_core::BatchProvider::Deepgram, - base_url: "https://example.com".to_string(), - api_key: "secret".to_string(), - model: "nova".to_string(), - language: "en".parse().unwrap(), - server: crate::stt::ServerGuard::default(), - }; - - let params = build_batch_params( - &resolved, - Path::new("/tmp/example.mp3"), - vec!["roadmap".to_string(), "planning".to_string()], - ) - .unwrap(); - - assert_eq!(params.keywords, vec!["roadmap", "planning"]); - } - - #[test] - fn resolve_output_target_uses_json_session_artifact_by_default() { - let dir = tempfile::tempdir().unwrap(); - let input = dir.path().join("input.mp3"); - std::fs::write(&input, b"audio").unwrap(); - - let target = resolve_output_target(&input, None, Some(dir.path())).unwrap(); - - assert_eq!(target.output_path.file_name().unwrap(), "transcript.json"); - assert_eq!( - target.audio_dest.as_ref().and_then(|path| path.file_name()), - Some(std::ffi::OsStr::new("audio.mp3")) - ); - } - - #[test] - fn saves_json_only_for_transcript_json() { - assert!(saves_json(Path::new("/tmp/transcript.json"))); - assert!(!saves_json(Path::new("/tmp/output.txt"))); - } - - #[tokio::test] - async fn save_pretty_output_persists_json_response_for_transcript_json() { - let dir = tempfile::tempdir().unwrap(); - let out = dir.path().join("transcript.json"); - let response = batch::Response { - metadata: serde_json::json!({ "duration": 1.2 }), - results: batch::Results { - channels: vec![batch::Channel { - alternatives: vec![batch::Alternatives { - transcript: "hello world".to_string(), - confidence: 1.0, - words: vec![batch::Word { - word: "hello".to_string(), - start: 0.0, - end: 0.5, - confidence: 1.0, - channel: 0, - speaker: None, - punctuated_word: Some("hello".to_string()), - }], - }], - }], - }, - }; - - save_pretty_output(Some(&out), "pretty text", &response) - .await - .unwrap(); - - let saved = std::fs::read_to_string(out).unwrap(); - let parsed: batch::Response = serde_json::from_str(&saved).unwrap(); - - assert_eq!( - parsed.results.channels[0].alternatives[0].transcript, - "hello world" - ); - assert!(saved.starts_with('{')); - } - - #[tokio::test] - async fn save_pretty_output_writes_pretty_text_for_non_json_path() { - let dir = tempfile::tempdir().unwrap(); - let out = dir.path().join("transcript.txt"); - let response = batch::Response { - metadata: serde_json::json!({}), - results: batch::Results { - channels: Vec::new(), - }, - }; - - save_pretty_output(Some(&out), "pretty text", &response) - .await - .unwrap(); - - assert_eq!(std::fs::read_to_string(out).unwrap(), "pretty text\n"); - } -} diff --git a/apps/cli/src/commands/transcribe/output.rs b/apps/cli/src/commands/transcribe/output.rs deleted file mode 100644 index 3ac5509c27..0000000000 --- a/apps/cli/src/commands/transcribe/output.rs +++ /dev/null @@ -1,237 +0,0 @@ -use colored::Colorize; - -use crate::output::format_timestamp_secs; - -const PAUSE_THRESHOLD_SECS: f64 = 0.5; - -const SPEAKER_COLORS: &[colored::Color] = &[ - colored::Color::Cyan, - colored::Color::Green, - colored::Color::Yellow, - colored::Color::Magenta, - colored::Color::Blue, - colored::Color::Red, -]; - -fn speaker_color(speaker: usize) -> colored::Color { - SPEAKER_COLORS[speaker % SPEAKER_COLORS.len()] -} - -struct Segment<'a> { - start: f64, - end: f64, - words: Vec<&'a str>, - identity: usize, -} - -pub(super) fn format_pretty(response: &owhisper_interface::batch::Response) -> String { - let mut segments: Vec = Vec::new(); - let num_channels = response.results.channels.len(); - - struct TaggedWord<'a> { - text: &'a str, - start: f64, - end: f64, - identity: usize, - } - - let mut all_words: Vec = Vec::new(); - for (channel_idx, channel) in response.results.channels.iter().enumerate() { - let Some(alt) = channel.alternatives.first() else { - continue; - }; - for word in &alt.words { - all_words.push(TaggedWord { - text: word - .punctuated_word - .as_deref() - .unwrap_or(word.word.as_str()), - start: word.start, - end: word.end, - identity: word_identity(word, channel_idx, num_channels), - }); - } - } - all_words.sort_by(|a, b| { - a.start - .partial_cmp(&b.start) - .unwrap_or(std::cmp::Ordering::Equal) - }); - - for word in &all_words { - let should_split = segments - .last() - .map(|seg| word.start - seg.end > PAUSE_THRESHOLD_SECS || word.identity != seg.identity) - .unwrap_or(true); - - if should_split { - segments.push(Segment { - start: word.start, - end: word.end, - words: vec![word.text], - identity: word.identity, - }); - } else if let Some(seg) = segments.last_mut() { - seg.end = word.end; - seg.words.push(word.text); - } - } - - if segments.is_empty() { - return extract_transcript(response); - } - - let term_width = textwrap::termwidth(); - let show_speaker = - num_channels > 1 || segments.iter().any(|s| s.identity != segments[0].identity); - - segments - .iter() - .map(|seg| { - let timestamp = format!( - "[{} \u{2192} {}]", - format_timestamp_secs(seg.start), - format_timestamp_secs(seg.end) - ) - .dimmed() - .to_string(); - - let label = format!("{} ", timestamp); - let text = seg.words.join(" "); - let text = if show_speaker { - text.color(speaker_color(seg.identity)).to_string() - } else { - text - }; - - let visible_prefix_len = 22; - let wrap_width = term_width.saturating_sub(visible_prefix_len); - - if wrap_width == 0 || text.len() <= wrap_width { - format!("{}{}", label, text) - } else { - let indent = " ".repeat(visible_prefix_len); - let wrapped = textwrap::fill( - &text, - textwrap::Options::new(wrap_width).subsequent_indent(""), - ); - let mut lines = wrapped.lines(); - let first = lines.next().unwrap_or(""); - let rest: Vec<&str> = lines.collect(); - if rest.is_empty() { - format!("{}{}", label, first) - } else { - format!( - "{}{}\n{}", - label, - first, - rest.iter() - .map(|l| format!("{}{}", indent, l)) - .collect::>() - .join("\n") - ) - } - } - }) - .collect::>() - .join("\n\n") -} - -fn word_identity( - word: &owhisper_interface::batch::Word, - channel_idx: usize, - total_channels: usize, -) -> usize { - if total_channels > 1 { - channel_idx - } else { - word.speaker.unwrap_or(word.channel.max(0) as usize) - } -} - -pub(super) fn extract_transcript(response: &owhisper_interface::batch::Response) -> String { - response - .results - .channels - .iter() - .filter_map(|c| c.alternatives.first()) - .map(|alt| alt.transcript.trim()) - .filter(|t| !t.is_empty()) - .collect::>() - .join("\n") -} - -#[cfg(test)] -mod tests { - use owhisper_interface::batch; - - use super::*; - - fn response_with_channels(channel_words: Vec>) -> batch::Response { - batch::Response { - metadata: serde_json::json!({}), - results: batch::Results { - channels: channel_words - .into_iter() - .map(|words| batch::Channel { - alternatives: vec![batch::Alternatives { - transcript: words - .iter() - .map(|word| word.word.as_str()) - .collect::>() - .join(" "), - confidence: 1.0, - words, - }], - }) - .collect(), - }, - } - } - - fn word(text: &str, start: f64, end: f64, channel: i32, speaker: Option) -> batch::Word { - batch::Word { - word: text.to_string(), - start, - end, - confidence: 1.0, - channel, - speaker, - punctuated_word: Some(text.to_string()), - } - } - - #[test] - fn pretty_output_splits_multichannel_words_by_channel() { - colored::control::set_override(false); - - let response = response_with_channels(vec![ - vec![word("left", 0.0, 0.4, 0, None)], - vec![word("right", 0.1, 0.5, 0, None)], - ]); - - let pretty = format_pretty(&response); - let blocks = pretty.split("\n\n").collect::>(); - - assert_eq!(blocks.len(), 2); - assert!(blocks[0].contains("left")); - assert!(blocks[1].contains("right")); - } - - #[test] - fn pretty_output_splits_single_channel_words_by_speaker() { - colored::control::set_override(false); - - let response = response_with_channels(vec![vec![ - word("hello", 0.0, 0.4, 0, Some(0)), - word("again", 0.45, 0.8, 0, Some(1)), - ]]); - - let pretty = format_pretty(&response); - let blocks = pretty.split("\n\n").collect::>(); - - assert_eq!(blocks.len(), 2); - assert!(blocks[0].contains("hello")); - assert!(blocks[1].contains("again")); - } -} diff --git a/apps/cli/src/commands/update.rs b/apps/cli/src/commands/update.rs deleted file mode 100644 index 38af4dbac3..0000000000 --- a/apps/cli/src/commands/update.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::process::Command; - -use crate::error::{CliError, CliResult}; - -pub fn run() -> CliResult<()> { - let status = Command::new("npm") - .args(["i", "-g", "char@latest"]) - .status() - .map_err(|e| CliError::operation_failed("run npm", e.to_string()))?; - - if !status.success() { - return Err(CliError::operation_failed( - "update char", - "npm install failed", - )); - } - - Ok(()) -} diff --git a/apps/cli/src/commands/update_check.rs b/apps/cli/src/commands/update_check.rs deleted file mode 100644 index 5eafac37e0..0000000000 --- a/apps/cli/src/commands/update_check.rs +++ /dev/null @@ -1,158 +0,0 @@ -const RELEASES_PER_PAGE: u32 = 100; - -#[derive(serde::Deserialize)] -struct Release { - tag_name: String, - prerelease: bool, -} - -pub struct UpdateHandle(tokio::task::JoinHandle>); - -impl UpdateHandle { - pub fn spawn() -> Self { - Self(tokio::spawn(async { - let current = option_env!("APP_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")); - let client = reqwest::Client::builder() - .user_agent("char-cli") - .timeout(std::time::Duration::from_secs(3)) - .build() - .ok()?; - let latest = fetch_latest_cli_release(&client).await?; - is_newer(&latest, current).then_some(latest) - })) - } - - pub async fn result(self) -> Option { - self.0.await.ok().flatten() - } -} - -async fn fetch_latest_cli_release(client: &reqwest::Client) -> Option { - let mut best: Option<((u64, u64, u64), String)> = None; - let mut page = 1; - - loop { - let releases = fetch_release_page(client, page).await?; - let is_last = releases.len() < RELEASES_PER_PAGE as usize; - - for release in &releases { - if let Some((triple, version)) = parse_cli_release(release) - && best.as_ref().is_none_or(|(b, _)| triple > *b) - { - best = Some((triple, version.to_string())); - } - } - - if is_last { - break; - } - page += 1; - } - - best.map(|(_, version)| version) -} - -async fn fetch_release_page(client: &reqwest::Client, page: u32) -> Option> { - client - .get("https://api.github.com/repos/fastrepl/char/releases") - .query(&[("per_page", RELEASES_PER_PAGE), ("page", page)]) - .send() - .await - .ok()? - .json() - .await - .ok() -} - -#[cfg(test)] -fn find_latest_cli_release_on_page(releases: &[Release]) -> Option { - releases - .iter() - .filter_map(parse_cli_release) - .max_by_key(|(triple, _)| *triple) - .map(|(_, version)| version.to_string()) -} - -fn parse_cli_release(release: &Release) -> Option<((u64, u64, u64), &str)> { - if release.prerelease { - return None; - } - - let version = release.tag_name.strip_prefix("cli_v")?; - Some((parse_triple(version)?, version)) -} - -fn parse_triple(s: &str) -> Option<(u64, u64, u64)> { - let s = s.split('-').next().unwrap_or(s); - let mut parts = s.split('.'); - let major = parts.next()?.parse().ok()?; - let minor = parts.next()?.parse().ok()?; - let patch = parts.next()?.parse().ok()?; - Some((major, minor, patch)) -} - -fn is_newer(latest: &str, current: &str) -> bool { - match (parse_triple(latest), parse_triple(current)) { - (Some(l), Some(c)) => l > c, - _ => false, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn release(tag_name: &str, prerelease: bool) -> Release { - Release { - tag_name: tag_name.to_string(), - prerelease, - } - } - - #[test] - fn newer_version_detected() { - assert!(is_newer("1.2.0", "1.1.0")); - assert!(is_newer("2.0.0", "1.9.9")); - assert!(!is_newer("1.0.0", "1.0.0")); - assert!(!is_newer("0.9.0", "1.0.0")); - } - - #[test] - fn handles_prerelease_suffix() { - assert!(is_newer("1.1.0-beta", "1.0.0")); - } - - #[test] - fn selects_highest_stable_cli_release_on_page() { - assert_eq!( - find_latest_cli_release_on_page(&[ - release("desktop_v9.9.9", false), - release("cli_v1.1.0-beta", true), - release("cli_v1.0.3", false), - release("cli_v1.0.2", false), - ]), - Some("1.0.3".to_string()) - ); - } - - #[test] - fn picks_highest_version_regardless_of_order() { - assert_eq!( - find_latest_cli_release_on_page(&[ - release("cli_v1.0.9", false), - release("cli_v1.0.8", false), - release("cli_v1.0.11", false), - release("cli_v1.0.10", false), - ]), - Some("1.0.11".to_string()) - ); - } - - #[test] - fn returns_none_when_page_has_no_stable_cli_release() { - assert_eq!( - find_latest_cli_release_on_page(&[release("desktop_v9.9.9", false)]), - None - ); - } -} diff --git a/apps/cli/src/config/mod.rs b/apps/cli/src/config/mod.rs deleted file mode 100644 index 8118b2968e..0000000000 --- a/apps/cli/src/config/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod paths; diff --git a/apps/cli/src/config/paths.rs b/apps/cli/src/config/paths.rs deleted file mode 100644 index b632daf6c2..0000000000 --- a/apps/cli/src/config/paths.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::path::{Path, PathBuf}; - -#[derive(Clone, Debug)] -pub struct AppPaths { - #[allow(dead_code)] - pub base: PathBuf, - pub models_base: PathBuf, -} - -pub fn resolve_paths(base_override: Option<&Path>) -> AppPaths { - let data_dir = dirs::data_dir().unwrap_or_else(std::env::temp_dir); - let base = base_override - .map(Path::to_path_buf) - .unwrap_or_else(|| data_dir.join("char")); - let models_base = base.join("models"); - - AppPaths { base, models_base } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn resolve_paths_uses_override() { - let base = Path::new("/tmp/char-cli-tests"); - let paths = resolve_paths(Some(base)); - - assert_eq!(paths.base, base); - assert_eq!(paths.models_base, base.join("models")); - } - - #[test] - fn resolve_paths_defaults_models_under_base() { - let paths = resolve_paths(None); - - assert!(paths.base.ends_with("char")); - assert_eq!(paths.models_base, paths.base.join("models")); - } -} diff --git a/apps/cli/src/config/settings.rs b/apps/cli/src/config/settings.rs deleted file mode 100644 index 98e98a4842..0000000000 --- a/apps/cli/src/config/settings.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; - -use sqlx::SqlitePool; - -#[derive(Clone, Debug)] -pub struct ProviderConfig { - pub base_url: Option, - pub api_key: Option, -} - -#[derive(Clone, Debug)] -pub struct Settings { - pub current_stt_provider: Option, - pub current_stt_model: Option, - pub stt_providers: HashMap, -} - -pub async fn migrate_json_settings_to_db(pool: &SqlitePool, base_path: &Path) { - let has_settings = hypr_db_app::load_all_settings(pool) - .await - .map(|v| !v.is_empty()) - .unwrap_or(false); - let has_connections = hypr_db_app::list_connections(pool, "stt") - .await - .map(|v| !v.is_empty()) - .unwrap_or(false); - - if has_settings || has_connections { - return; - } - - let json_path = base_path.join("settings.json"); - let Some(settings) = load_settings_from_json(&json_path) else { - return; - }; - - if let Some(ref v) = settings.current_stt_provider { - let _ = hypr_db_app::set_setting(pool, "current_stt_provider", v).await; - } - if let Some(ref v) = settings.current_stt_model { - let _ = hypr_db_app::set_setting(pool, "current_stt_model", v).await; - } - - for (provider_id, config) in &settings.stt_providers { - let _ = hypr_db_app::upsert_connection( - pool, - "stt", - provider_id, - config.base_url.as_deref().unwrap_or(""), - config.api_key.as_deref().unwrap_or(""), - ) - .await; - } -} - -fn load_settings_from_json(path: &Path) -> Option { - let content = std::fs::read_to_string(path).ok()?; - let json: serde_json::Value = serde_json::from_str(&content).ok()?; - let ai = json.get("ai")?.as_object()?; - - let current_stt_provider = get_string(ai.get("current_stt_provider")); - let current_stt_model = get_string(ai.get("current_stt_model")); - let stt_providers = parse_provider_map(ai.get("stt")); - - Some(Settings { - current_stt_provider, - current_stt_model, - stt_providers, - }) -} - -fn get_string(value: Option<&serde_json::Value>) -> Option { - value?.as_str().map(ToString::to_string) -} - -fn parse_provider_map(value: Option<&serde_json::Value>) -> HashMap { - let mut out = HashMap::new(); - let Some(obj) = value.and_then(serde_json::Value::as_object) else { - return out; - }; - - for (provider_id, config) in obj { - let Some(config_obj) = config.as_object() else { - continue; - }; - - let base_url = config_obj - .get("base_url") - .and_then(serde_json::Value::as_str) - .map(str::trim) - .filter(|v| !v.is_empty()) - .map(ToString::to_string); - let api_key = config_obj - .get("api_key") - .and_then(serde_json::Value::as_str) - .map(str::trim) - .filter(|v| !v.is_empty()) - .map(ToString::to_string); - out.insert(provider_id.clone(), ProviderConfig { base_url, api_key }); - } - - out -} diff --git a/apps/cli/src/error.rs b/apps/cli/src/error.rs deleted file mode 100644 index f398eb6426..0000000000 --- a/apps/cli/src/error.rs +++ /dev/null @@ -1,152 +0,0 @@ -use thiserror::Error; - -pub type CliResult = Result; - -#[derive(Debug, Error)] -pub enum CliError { - #[error("{0}")] - Message(String), - - #[error("{name} is required{}", hint_suffix(.hint))] - RequiredArgument { name: String, hint: Option }, - - #[error("invalid {name} '{value}': {reason}{}", hint_suffix(.hint))] - InvalidArgument { - name: &'static str, - value: String, - reason: String, - hint: Option, - }, - - #[error("{action} failed: {reason}")] - OperationFailed { - action: &'static str, - reason: String, - }, - - #[error("{what} not found{}", hint_suffix(.hint))] - NotFound { what: String, hint: Option }, -} - -fn hint_suffix(hint: &Option) -> String { - match hint { - Some(h) => format!("\n hint: {h}"), - None => String::new(), - } -} - -impl CliError { - pub fn msg(message: impl Into) -> Self { - Self::Message(message.into()) - } - - pub fn required_argument(name: impl Into) -> Self { - Self::RequiredArgument { - name: name.into(), - hint: None, - } - } - - pub fn required_argument_with_hint(name: impl Into, hint: impl Into) -> Self { - Self::RequiredArgument { - name: name.into(), - hint: Some(hint.into()), - } - } - - pub fn invalid_argument( - name: &'static str, - value: impl Into, - reason: impl Into, - ) -> Self { - Self::InvalidArgument { - name, - value: value.into(), - reason: reason.into(), - hint: None, - } - } - - pub fn operation_failed(action: &'static str, reason: impl Into) -> Self { - Self::OperationFailed { - action, - reason: reason.into(), - } - } - - pub fn not_found(what: impl Into, hint: Option) -> Self { - Self::NotFound { - what: what.into(), - hint, - } - } -} - -/// Returns the closest match from `candidates` if one exceeds a 0.7 Jaro-Winkler threshold. -pub fn did_you_mean<'a>(input: &str, candidates: &[&'a str]) -> Option<&'a str> { - candidates - .iter() - .filter_map(|c| { - let sim = strsim::jaro_winkler(input, c); - if sim > 0.7 { Some((*c, sim)) } else { None } - }) - .max_by(|a, b| a.1.total_cmp(&b.1)) - .map(|(c, _)| c) -} - -impl From for CliError { - fn from(message: String) -> Self { - Self::Message(message) - } -} - -impl From<&str> for CliError { - fn from(message: &str) -> Self { - Self::msg(message) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn invalid_argument_has_structured_fields() { - let error = CliError::invalid_argument("--language", "xx", "unknown code"); - - match error { - CliError::InvalidArgument { - name, - value, - reason, - .. - } => { - assert_eq!(name, "--language"); - assert_eq!(value, "xx"); - assert_eq!(reason, "unknown code"); - } - _ => panic!("expected invalid argument variant"), - } - } - - #[test] - fn did_you_mean_finds_close_match() { - let candidates = &["deepgram", "soniox", "whispercpp", "cactus"]; - assert_eq!(did_you_mean("deepgran", candidates), Some("deepgram")); - assert_eq!(did_you_mean("sonix", candidates), Some("soniox")); - assert_eq!(did_you_mean("whispr", candidates), Some("whispercpp")); - assert_eq!(did_you_mean("completely-wrong", candidates), None); - } - - #[test] - fn not_found_includes_hint_in_display() { - let error = CliError::not_found( - "model 'foo'", - Some("Run `char model list` to see available models.".to_string()), - ); - - let rendered = error.to_string(); - assert!(rendered.contains("model 'foo' not found")); - assert!(rendered.contains("hint:")); - } -} diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs deleted file mode 100644 index 88b4109b51..0000000000 --- a/apps/cli/src/main.rs +++ /dev/null @@ -1,159 +0,0 @@ -mod app; -mod cli; -mod commands; -mod config; -mod error; -mod output; -#[cfg(feature = "_cli-audio")] -mod stt; -#[cfg(feature = "_cli-tui")] -pub(crate) mod tui; - -use crate::cli::{Cli, Commands}; -use crate::error::CliResult; -use clap::Parser; - -#[allow(clippy::let_unit_value)] -fn main() { - let cli = Cli::parse(); - - if cli.no_color || std::env::var_os("NO_COLOR").is_some() { - colored::control::set_override(false); - } - - let trace_buffer = init_tracing(&cli); - - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .expect("failed to build tokio runtime"); - let result = runtime.block_on(run(cli, trace_buffer)); - - if let Err(error) = result { - eprintln!("error: {error}"); - std::process::exit(1); - } -} - -#[cfg(feature = "_cli-tui")] -type OptTraceBuffer = Option; -#[cfg(not(feature = "_cli-tui"))] -type OptTraceBuffer = (); - -fn init_tracing(cli: &Cli) -> OptTraceBuffer { - let level = cli.verbose.tracing_level_filter(); - - #[cfg(feature = "_cli-audio")] - let wants_json = matches!( - cli.command, - Some(Commands::Transcribe { - args: commands::transcribe::Args { - format: cli::OutputFormat::Json, - .. - }, - }) - ); - #[cfg(not(feature = "_cli-audio"))] - let wants_json = false; - - #[cfg(feature = "_cli-audio")] - let wants_json = wants_json - || matches!( - cli.command, - Some(Commands::Record { - args: commands::record::Args { - format: cli::OutputFormat::Json, - .. - }, - }) - ); - - #[cfg(feature = "_cli-audio")] - let wants_capture = !wants_json - && std::io::IsTerminal::is_terminal(&std::io::stderr()) - && matches!( - cli.command, - Some( - Commands::Transcribe { .. } - | Commands::Models { .. } - | Commands::Record { .. } - | Commands::Play { .. }, - ) - ); - - #[cfg(feature = "_cli-audio")] - if wants_capture { - let buf = tui::new_trace_buffer(); - init_tracing_capture(level, buf.clone()); - return Some(buf); - } - - if wants_json { - init_tracing_json(level); - } else { - init_tracing_stderr(level); - } - - #[cfg(feature = "_cli-tui")] - return None; - #[cfg(not(feature = "_cli-tui"))] - return; -} - -fn init_tracing_stderr(level: tracing_subscriber::filter::LevelFilter) { - use tracing_subscriber::EnvFilter; - let filter = EnvFilter::builder() - .with_default_directive(level.into()) - .from_env_lossy(); - tracing_subscriber::fmt() - .with_env_filter(filter) - .with_writer(std::io::stderr) - .init(); -} - -fn init_tracing_json(level: tracing_subscriber::filter::LevelFilter) { - use tracing_subscriber::EnvFilter; - let filter = EnvFilter::builder() - .with_default_directive(level.into()) - .from_env_lossy(); - tracing_subscriber::fmt() - .json() - .with_env_filter(filter) - .with_writer(std::io::stderr) - .init(); -} - -#[cfg(feature = "_cli-audio")] -fn init_tracing_capture(level: tracing_subscriber::filter::LevelFilter, buffer: tui::TraceBuffer) { - use tracing_subscriber::EnvFilter; - use tracing_subscriber::layer::SubscriberExt; - use tracing_subscriber::util::SubscriberInitExt; - - let filter = EnvFilter::builder() - .with_default_directive(level.into()) - .from_env_lossy(); - let capture = tui::CaptureLayer::new(buffer); - tracing_subscriber::registry() - .with(filter) - .with(capture) - .init(); -} - -async fn run(cli: Cli, trace_buffer: OptTraceBuffer) -> CliResult<()> { - let base = cli - .command - .as_ref() - .and_then(Commands::base_override) - .map(std::path::Path::to_path_buf); - let tracked = cli.command.as_ref().map(Into::into); - let Cli { - command, verbose, .. - } = cli; - let ctx = app::AppContext::new(base.as_deref(), verbose.is_silent(), trace_buffer); - - if let Some(subcommand) = tracked { - ctx.track_command(subcommand); - } - - commands::run(&ctx, command).await -} diff --git a/apps/cli/src/output.rs b/apps/cli/src/output.rs deleted file mode 100644 index 1bdd9904bd..0000000000 --- a/apps/cli/src/output.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::error::{CliError, CliResult}; -use std::io::{IsTerminal, Write}; -use std::path::Path; - -#[allow(dead_code)] -pub fn format_timestamp_ms(ms: i64) -> String { - let total_secs = (ms / 1000).max(0); - let mins = total_secs / 60; - let secs = total_secs % 60; - if mins >= 60 { - let hours = mins / 60; - let mins = mins % 60; - format!("{hours:02}:{mins:02}:{secs:02}") - } else { - format!("{mins:02}:{secs:02}") - } -} - -pub fn format_timestamp_secs(secs: f64) -> String { - let total_tenths = (secs.max(0.0) * 10.0).round() as u64; - let mins = total_tenths / 600; - let s = (total_tenths % 600) / 10; - let frac = total_tenths % 10; - format!("{mins:02}:{s:02}.{frac}") -} - -async fn ensure_parent_dirs(path: &Path) -> CliResult<()> { - if let Some(parent) = path.parent() { - tokio::fs::create_dir_all(parent) - .await - .map_err(|e| CliError::operation_failed("create output directory", e.to_string()))?; - } - Ok(()) -} - -async fn write_bytes_to(output: Option<&Path>, bytes: Vec) -> CliResult<()> { - if let Some(path) = output { - ensure_parent_dirs(path).await?; - tokio::fs::write(path, bytes) - .await - .map_err(|e| CliError::operation_failed("write output", e.to_string()))?; - return Ok(()); - } - - std::io::stdout() - .write_all(&bytes) - .map_err(|e| CliError::operation_failed("write output", e.to_string()))?; - Ok(()) -} - -pub async fn write_text(output: Option<&Path>, text: String) -> CliResult<()> { - write_bytes_to(output, (text + "\n").into_bytes()).await -} - -pub async fn write_json(output: Option<&Path>, value: &impl serde::Serialize) -> CliResult<()> { - let pretty = output.is_some() || std::io::stdout().is_terminal(); - let mut bytes: Vec = if pretty { - serde_json::to_vec_pretty(value) - } else { - serde_json::to_vec(value) - } - .map_err(|e| CliError::operation_failed("serialize response", e.to_string()))?; - bytes.push(b'\n'); - - write_bytes_to(output, bytes).await -} - -pub struct EventWriter { - writer: W, -} - -impl EventWriter { - pub fn new(writer: W) -> Self { - Self { writer } - } - - pub fn emit(&mut self, event: &impl serde::Serialize) -> CliResult<()> { - serde_json::to_writer(&mut self.writer, event) - .map_err(|e| CliError::operation_failed("serialize event", e.to_string()))?; - self.writer - .write_all(b"\n") - .map_err(|e| CliError::operation_failed("write event", e.to_string()))?; - self.writer - .flush() - .map_err(|e| CliError::operation_failed("flush event", e.to_string())) - } -} - -#[cfg(test)] -mod tests { - use super::format_timestamp_secs; - - #[test] - fn format_timestamp_secs_handles_rounding_boundaries() { - assert_eq!(format_timestamp_secs(5.94), "00:05.9"); - assert_eq!(format_timestamp_secs(5.95), "00:06.0"); - assert_eq!(format_timestamp_secs(59.95), "01:00.0"); - assert_eq!(format_timestamp_secs(0.0), "00:00.0"); - } -} diff --git a/apps/cli/src/stt/config.rs b/apps/cli/src/stt/config.rs deleted file mode 100644 index e8c02f458c..0000000000 --- a/apps/cli/src/stt/config.rs +++ /dev/null @@ -1,486 +0,0 @@ -use std::path::{Path, PathBuf}; - -use hypr_listener2_core::{BatchEvent, BatchParams, BatchProvider, BatchRuntime}; -use hypr_local_model::{CactusSttModel, LocalModel, WhisperModel}; -#[cfg(target_os = "macos")] -use hypr_local_stt_server::LocalSttServer; -use tokio::sync::mpsc; - -use crate::error::{CliError, CliResult, did_you_mean}; - -use super::SttProvider; - -#[cfg(target_os = "macos")] -pub type ServerGuard = Option; - -#[cfg(not(target_os = "macos"))] -pub type ServerGuard = (); - -pub struct SttOverrides { - pub provider: Option, - pub base_url: Option, - pub api_key: Option, - pub model: Option, - pub language: String, - pub models_base: PathBuf, -} - -pub struct ChannelBatchRuntime { - pub tx: mpsc::UnboundedSender, -} - -impl BatchRuntime for ChannelBatchRuntime { - fn emit(&self, event: BatchEvent) { - let _ = self.tx.send(event); - } -} - -#[cfg(target_os = "macos")] -pub struct LocalServerInfo { - pub server: LocalSttServer, - pub base_url: String, - pub model_name: String, -} - -pub struct ResolvedSttConfig { - pub provider: BatchProvider, - pub base_url: String, - pub api_key: String, - pub model: String, - pub language: hypr_language::Language, - pub server: ServerGuard, -} - -impl ResolvedSttConfig { - pub fn model_option(&self) -> Option { - if self.model.is_empty() { - None - } else { - Some(self.model.clone()) - } - } - - pub fn to_batch_params( - &self, - session_id: String, - file_path: String, - keywords: Vec, - ) -> BatchParams { - BatchParams { - session_id, - provider: self.provider.clone(), - file_path, - model: self.model_option(), - base_url: self.base_url.clone(), - api_key: self.api_key.clone(), - languages: vec![self.language.clone()], - keywords, - num_speakers: None, - min_speakers: None, - max_speakers: None, - } - } -} - -pub async fn resolve_config(overrides: SttOverrides) -> CliResult { - let language_code = overrides.language; - let language = language_code - .parse::() - .map_err(|e| CliError::invalid_argument("--language", language_code, e.to_string()))?; - - let provider = overrides.provider.ok_or_else(|| { - CliError::required_argument_with_hint("--provider", "Pass --provider explicitly") - })?; - let base_url = overrides.base_url; - let api_key = overrides.api_key; - let model = overrides.model; - - #[cfg(target_os = "macos")] - if provider.is_local() { - let info = match provider { - SttProvider::Whispercpp => { - resolve_and_spawn_whisper(&overrides.models_base, model.as_deref()).await? - } - #[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] - SttProvider::Cactus => { - resolve_and_spawn_cactus(&overrides.models_base, model.as_deref()).await? - } - _ => unreachable!("cloud providers are handled below"), - }; - - return Ok(ResolvedSttConfig { - provider: provider.to_batch_provider(), - base_url: info.base_url, - api_key: api_key.unwrap_or_default(), - model: info.model_name, - language, - server: Some(info.server), - }); - } - - if let Some(cloud) = provider.cloud_provider() { - let base_url = base_url.unwrap_or_else(|| cloud.default_api_base().to_string()); - let api_key = api_key - .or_else(|| std::env::var(cloud.env_key_name()).ok()) - .ok_or_else(|| { - CliError::required_argument_with_hint( - "STT API key", - format!("Pass --api-key or set {} env var", cloud.env_key_name()), - ) - })?; - return Ok(ResolvedSttConfig { - provider: provider.to_batch_provider(), - base_url, - api_key, - model: model.unwrap_or_default(), - language, - server: ServerGuard::default(), - }); - } - - let base_url = - base_url.ok_or_else(|| CliError::required_argument("--base-url (or CHAR_BASE_URL)"))?; - let api_key = - api_key.ok_or_else(|| CliError::required_argument("--api-key (or CHAR_API_KEY)"))?; - - Ok(ResolvedSttConfig { - provider: provider.to_batch_provider(), - base_url, - api_key, - model: model.unwrap_or_default(), - language, - server: ServerGuard::default(), - }) -} - -#[cfg(target_os = "macos")] -pub async fn resolve_and_spawn_whisper( - models_base: &Path, - model_name: Option<&str>, -) -> CliResult { - let (model, model_path) = resolve_whisper_model(models_base, model_name)?; - - let server = LocalSttServer::start_whisper(model_path) - .await - .map_err(|e| CliError::operation_failed("start local whisper server", e.to_string()))?; - - Ok(LocalServerInfo { - base_url: server.base_url().to_string(), - model_name: LocalModel::Whisper(model.clone()).cli_name().to_string(), - server, - }) -} - -#[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] -pub async fn resolve_and_spawn_cactus( - models_base: &Path, - model_name: Option<&str>, -) -> CliResult { - let (model, model_path) = resolve_cactus_model(models_base, model_name)?; - - let server = LocalSttServer::start_cactus(model_path) - .await - .map_err(|e| CliError::operation_failed("start local cactus server", e.to_string()))?; - - Ok(LocalServerInfo { - base_url: server.base_url().to_string(), - model_name: model.to_string(), - server, - }) -} - -fn whisper_enabled() -> bool { - cfg!(target_os = "macos") -} - -fn unsupported_whisper_error() -> CliError { - CliError::msg("whisper local models are only available on macOS") -} - -fn missing_whisper_model_error() -> CliError { - CliError::required_argument_with_hint( - "--model", - format!( - "Pass --model explicitly for --provider whispercpp. Valid models: {}", - whisper_model_names().join(", ") - ), - ) -} - -fn resolve_whisper_model( - models_base: &Path, - name: Option<&str>, -) -> CliResult<(WhisperModel, PathBuf)> { - if !whisper_enabled() { - return Err(unsupported_whisper_error()); - } - - let name = name.ok_or_else(missing_whisper_model_error)?; - let model = LocalModel::all() - .into_iter() - .find_map(|model| match model { - LocalModel::Whisper(whisper) if model.cli_name() == name => Some(whisper), - _ => None, - }) - .ok_or_else(|| not_found_whisper_model(models_base, name, false))?; - - let model_path = LocalModel::Whisper(model.clone()).install_path(models_base); - if !model_path.exists() { - return Err(CliError::not_found( - format!("whisper model file at '{}'", model_path.display()), - Some(format!( - "Download it first: char models download {}", - LocalModel::Whisper(model.clone()).cli_name() - )), - )); - } - - Ok((model, model_path)) -} - -fn not_found_whisper_model( - models_base: &Path, - name: &str, - include_downloaded_hint: bool, -) -> CliError { - if !whisper_enabled() { - return unsupported_whisper_error(); - } - - let names = whisper_model_names(); - let mut hint = String::new(); - if let Some(suggestion) = did_you_mean(name, &names) { - hint.push_str(&format!("Did you mean '{suggestion}'?\n\n")); - } - if include_downloaded_hint { - hint.push_str(&suggest_whisper_models(models_base)); - } else { - hint.push_str("Run `char models list` to see available models."); - } - - CliError::not_found(format!("whisper model '{name}'"), Some(hint)) -} - -fn whisper_model_names() -> Vec<&'static str> { - LocalModel::all() - .iter() - .filter_map(|model| match model { - LocalModel::Whisper(_) => Some(model.cli_name()), - _ => None, - }) - .collect() -} - -fn suggest_whisper_models(models_base: &Path) -> String { - if !whisper_enabled() { - return "Whisper local models are only available on macOS.".to_string(); - } - - let mut downloaded = Vec::new(); - let mut available = Vec::new(); - - for model in LocalModel::all() { - let LocalModel::Whisper(_) = &model else { - continue; - }; - - if model.install_path(models_base).exists() { - downloaded.push(model.cli_name()); - } else { - available.push(model.cli_name()); - } - } - - let mut hint = String::new(); - if !downloaded.is_empty() { - hint.push_str("Downloaded models:\n"); - for name in &downloaded { - hint.push_str(&format!(" {name}\n")); - } - } - if !available.is_empty() { - if !downloaded.is_empty() { - hint.push_str("Other models (not downloaded):\n"); - } else { - hint.push_str("No models downloaded. Available models:\n"); - } - for name in &available { - hint.push_str(&format!(" {name}\n")); - } - hint.push_str("Download with: char models download "); - } - if hint.is_empty() { - hint.push_str("No whisper models found. Run `char models list` to check."); - } - hint -} - -fn cactus_enabled() -> bool { - cfg!(target_os = "macos") && cfg!(any(target_arch = "arm", target_arch = "aarch64")) -} - -fn unsupported_cactus_error() -> CliError { - CliError::msg("cactus local models are only available on Apple Silicon Macs") -} - -fn canonical_cactus_name(name: &str) -> String { - if name.starts_with("cactus-") { - name.to_string() - } else { - format!("cactus-{name}") - } -} - -fn missing_cactus_model_error() -> CliError { - CliError::required_argument_with_hint( - "--model", - format!( - "Pass --model explicitly for --provider cactus. Valid models: {}", - cactus_model_names().join(", ") - ), - ) -} - -fn cactus_model_names() -> Vec<&'static str> { - LocalModel::all() - .iter() - .filter_map(|model| match model { - LocalModel::Cactus(_) => Some(model.cli_name()), - _ => None, - }) - .collect() -} - -fn resolve_cactus_model( - models_base: &Path, - name: Option<&str>, -) -> CliResult<(CactusSttModel, PathBuf)> { - if !cactus_enabled() { - return Err(unsupported_cactus_error()); - } - - let name = name.ok_or_else(missing_cactus_model_error)?; - let canonical = canonical_cactus_name(name); - let model = LocalModel::all() - .into_iter() - .find_map(|model| match model { - LocalModel::Cactus(cactus) - if model.cli_name() == name || model.cli_name() == canonical => - { - Some(cactus) - } - _ => None, - }) - .ok_or_else(|| not_found_cactus_model(models_base, name, false))?; - - let model_path = LocalModel::Cactus(model.clone()).install_path(models_base); - if !model_path.exists() { - return Err(CliError::not_found( - format!("cactus model files at '{}'", model_path.display()), - Some(format!( - "Download it first: char models download {}", - LocalModel::Cactus(model.clone()).cli_name() - )), - )); - } - - Ok((model, model_path)) -} - -fn not_found_cactus_model( - models_base: &Path, - name: &str, - include_downloaded_hint: bool, -) -> CliError { - if !cactus_enabled() { - return unsupported_cactus_error(); - } - - let names: Vec<&str> = LocalModel::all() - .iter() - .filter_map(|model| { - if matches!(model, LocalModel::Cactus(_)) { - Some(model.cli_name()) - } else { - None - } - }) - .collect(); - - let mut hint = String::new(); - if let Some(suggestion) = did_you_mean(name, &names) { - hint.push_str(&format!("Did you mean '{suggestion}'?\n\n")); - } - if include_downloaded_hint { - hint.push_str(&suggest_cactus_models(models_base)); - } else { - hint.push_str("Run `char models list` to see available models."); - } - - CliError::not_found(format!("cactus model '{name}'"), Some(hint)) -} - -fn suggest_cactus_models(models_base: &Path) -> String { - if !cactus_enabled() { - return "Cactus local models are only available on Apple Silicon Macs.".to_string(); - } - let mut downloaded = Vec::new(); - let mut available = Vec::new(); - - for model in LocalModel::all() { - let LocalModel::Cactus(_) = &model else { - continue; - }; - - if model.install_path(models_base).exists() { - downloaded.push(model.cli_name()); - } else { - available.push(model.cli_name()); - } - } - - let mut hint = String::new(); - if !downloaded.is_empty() { - hint.push_str("Downloaded models:\n"); - for name in &downloaded { - hint.push_str(&format!(" {name}\n")); - } - } - if !available.is_empty() { - if !downloaded.is_empty() { - hint.push_str("Other models (not downloaded):\n"); - } else { - hint.push_str("No models downloaded. Available models:\n"); - } - for name in &available { - hint.push_str(&format!(" {name}\n")); - } - hint.push_str("Download with: char models download "); - } - if hint.is_empty() { - hint.push_str("No cactus models found. Run `char models list` to check."); - } - hint -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn missing_whisper_model_error_mentions_model_flag() { - let error = missing_whisper_model_error(); - let rendered = error.to_string(); - - assert!(rendered.contains("--model")); - assert!(rendered.contains("whisper-small")); - } - - #[test] - fn whisper_not_found_suggests_close_match() { - let error = not_found_whisper_model(Path::new("/tmp"), "whisper-smal", false); - let rendered = error.to_string(); - - assert!(rendered.contains("Did you mean 'whisper-small'?")); - } -} diff --git a/apps/cli/src/stt/mod.rs b/apps/cli/src/stt/mod.rs deleted file mode 100644 index 248aa06af5..0000000000 --- a/apps/cli/src/stt/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod config; -mod provider; - -#[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] -#[allow(unused_imports)] -pub use config::resolve_and_spawn_cactus; -pub use config::{ - ChannelBatchRuntime, ResolvedSttConfig, ServerGuard, SttOverrides, resolve_config, -}; -pub use provider::SttProvider; diff --git a/apps/cli/src/stt/provider.rs b/apps/cli/src/stt/provider.rs deleted file mode 100644 index fa51c1cbfd..0000000000 --- a/apps/cli/src/stt/provider.rs +++ /dev/null @@ -1,160 +0,0 @@ -use clap::ValueEnum; - -use hypr_listener2_core::BatchProvider; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] -pub enum SttProvider { - Deepgram, - Soniox, - Assemblyai, - Fireworks, - Openai, - Gladia, - Elevenlabs, - Mistral, - Pyannote, - #[cfg(target_os = "macos")] - Whispercpp, - #[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] - Cactus, -} - -impl SttProvider { - fn meta(self) -> ProviderMeta { - match self { - Self::Deepgram => ProviderMeta::cloud( - "deepgram", - owhisper_client::Provider::Deepgram, - BatchProvider::Deepgram, - ), - Self::Soniox => ProviderMeta::cloud( - "soniox", - owhisper_client::Provider::Soniox, - BatchProvider::Soniox, - ), - Self::Assemblyai => ProviderMeta::cloud( - "assemblyai", - owhisper_client::Provider::AssemblyAI, - BatchProvider::AssemblyAI, - ), - Self::Fireworks => ProviderMeta::cloud( - "fireworks", - owhisper_client::Provider::Fireworks, - BatchProvider::Fireworks, - ), - Self::Openai => ProviderMeta::cloud( - "openai", - owhisper_client::Provider::OpenAI, - BatchProvider::OpenAI, - ), - Self::Gladia => ProviderMeta::cloud( - "gladia", - owhisper_client::Provider::Gladia, - BatchProvider::Gladia, - ), - Self::Elevenlabs => ProviderMeta::cloud( - "elevenlabs", - owhisper_client::Provider::ElevenLabs, - BatchProvider::ElevenLabs, - ), - Self::Mistral => ProviderMeta::cloud( - "mistral", - owhisper_client::Provider::Mistral, - BatchProvider::Mistral, - ), - Self::Pyannote => ProviderMeta::cloud( - "pyannote", - owhisper_client::Provider::Pyannote, - BatchProvider::Pyannote, - ), - #[cfg(target_os = "macos")] - Self::Whispercpp => ProviderMeta::local("whispercpp", BatchProvider::WhisperLocal), - #[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] - Self::Cactus => ProviderMeta::local("cactus", BatchProvider::Cactus), - } - } - - pub fn id(self) -> &'static str { - self.meta().id - } - - pub fn from_id(id: &str) -> Option { - providers() - .iter() - .find(|provider| provider.id() == id) - .copied() - } - - pub fn is_local(&self) -> bool { - self.meta().is_local - } - - pub(crate) fn cloud_provider(&self) -> Option { - self.meta().cloud_provider - } - - pub(crate) fn to_batch_provider(self) -> BatchProvider { - self.meta().batch_provider - } -} - -struct ProviderMeta { - id: &'static str, - cloud_provider: Option, - batch_provider: BatchProvider, - is_local: bool, -} - -impl ProviderMeta { - fn cloud( - id: &'static str, - cloud_provider: owhisper_client::Provider, - batch_provider: BatchProvider, - ) -> Self { - Self { - id, - cloud_provider: Some(cloud_provider), - batch_provider, - is_local: false, - } - } - - fn local(id: &'static str, batch_provider: BatchProvider) -> Self { - Self { - id, - cloud_provider: None, - batch_provider, - is_local: true, - } - } -} - -fn providers() -> &'static [SttProvider] { - &[ - SttProvider::Deepgram, - SttProvider::Soniox, - SttProvider::Assemblyai, - SttProvider::Fireworks, - SttProvider::Openai, - SttProvider::Gladia, - SttProvider::Elevenlabs, - SttProvider::Mistral, - SttProvider::Pyannote, - #[cfg(target_os = "macos")] - SttProvider::Whispercpp, - #[cfg(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64")))] - SttProvider::Cactus, - ] -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn provider_ids_round_trip() { - for provider in providers() { - assert_eq!(SttProvider::from_id(provider.id()), Some(*provider)); - } - } -} diff --git a/apps/cli/src/tui/capture.rs b/apps/cli/src/tui/capture.rs deleted file mode 100644 index 8981ffa4cd..0000000000 --- a/apps/cli/src/tui/capture.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::collections::VecDeque; -use std::fmt::Write; -use std::sync::{Arc, Mutex}; - -use tracing_subscriber::Layer; - -const TRACE_CAPACITY: usize = 64; - -pub type TraceBuffer = Arc>>; - -pub fn new_trace_buffer() -> TraceBuffer { - Arc::new(Mutex::new(VecDeque::with_capacity(TRACE_CAPACITY))) -} - -pub struct CaptureLayer { - buffer: TraceBuffer, -} - -impl CaptureLayer { - pub fn new(buffer: TraceBuffer) -> Self { - Self { buffer } - } -} - -impl Layer for CaptureLayer { - fn on_event( - &self, - event: &tracing::Event<'_>, - _ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let meta = event.metadata(); - let level = meta.level(); - let target = meta.target(); - - let mut message = String::new(); - let mut visitor = MessageVisitor(&mut message); - event.record(&mut visitor); - - let line = format!("{level:>5} {target}: {message}"); - if let Ok(mut buf) = self.buffer.lock() { - if buf.len() >= TRACE_CAPACITY { - buf.pop_front(); - } - buf.push_back(line); - } - } -} - -struct MessageVisitor<'a>(&'a mut String); - -impl tracing::field::Visit for MessageVisitor<'_> { - fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { - if field.name() == "message" { - let _ = write!(self.0, "{:?}", value); - } else { - if !self.0.is_empty() { - self.0.push(' '); - } - let _ = write!(self.0, "{}={:?}", field.name(), value); - } - } - - fn record_str(&mut self, field: &tracing::field::Field, value: &str) { - if field.name() == "message" { - let _ = write!(self.0, "{}", value); - } else { - if !self.0.is_empty() { - self.0.push(' '); - } - let _ = write!(self.0, "{}={}", field.name(), value); - } - } -} diff --git a/apps/cli/src/tui/input.rs b/apps/cli/src/tui/input.rs deleted file mode 100644 index 6db22e18d1..0000000000 --- a/apps/cli/src/tui/input.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc; -use std::thread::JoinHandle; -use std::time::Duration; - -use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; - -const INPUT_POLL_INTERVAL: Duration = Duration::from_millis(50); - -#[derive(Clone, Copy, PartialEq, Eq)] -pub(crate) enum View { - Progress, - Traces, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum InputAction { - ToggleView, - Interrupt, - SeekForward, - SeekBackward, - TogglePause, - Up, - Down, - Confirm, -} - -pub(crate) struct BackgroundInput { - rx: mpsc::Receiver, - running: Arc, - handle: Option>, -} - -impl BackgroundInput { - pub fn spawn() -> Self { - let (tx, rx) = mpsc::channel(); - let running = Arc::new(AtomicBool::new(true)); - let thread_running = Arc::clone(&running); - let handle = std::thread::spawn(move || { - while thread_running.load(Ordering::Relaxed) { - if !event::poll(INPUT_POLL_INTERVAL).unwrap_or(false) { - continue; - } - - let Ok(Event::Key(key)) = event::read() else { - continue; - }; - - match action_for_key(key) { - Some(InputAction::Interrupt) => { - #[cfg(unix)] - unsafe { - libc::kill(libc::getpid(), libc::SIGINT); - } - #[cfg(not(unix))] - std::process::exit(130); - } - Some(action) => { - let _ = tx.send(action); - } - None => {} - } - } - }); - - Self { - rx, - running, - handle: Some(handle), - } - } - - pub fn try_recv(&self) -> Result { - self.rx.try_recv() - } - - pub fn shutdown(&mut self) { - self.running.store(false, Ordering::Relaxed); - if let Some(handle) = self.handle.take() { - let _ = handle.join(); - } - } -} - -fn action_for_key(key: KeyEvent) -> Option { - match key { - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::ToggleView), - KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - .. - } => Some(InputAction::Interrupt), - KeyEvent { - code: KeyCode::Right, - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::SeekForward), - KeyEvent { - code: KeyCode::Left, - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::SeekBackward), - KeyEvent { - code: KeyCode::Char(' '), - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::TogglePause), - KeyEvent { - code: KeyCode::Up, - modifiers: KeyModifiers::NONE, - .. - } - | KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::Up), - KeyEvent { - code: KeyCode::Down, - modifiers: KeyModifiers::NONE, - .. - } - | KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::Down), - KeyEvent { - code: KeyCode::Enter, - modifiers: KeyModifiers::NONE, - .. - } => Some(InputAction::Confirm), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ctrl_c_maps_to_interrupt_action() { - let key = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL); - assert_eq!(action_for_key(key), Some(InputAction::Interrupt)); - } - - #[test] - fn plain_d_maps_to_toggle_action() { - let key = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE); - assert_eq!(action_for_key(key), Some(InputAction::ToggleView)); - } - - #[test] - fn arrow_keys_map_to_seek() { - let right = KeyEvent::new(KeyCode::Right, KeyModifiers::NONE); - assert_eq!(action_for_key(right), Some(InputAction::SeekForward)); - let left = KeyEvent::new(KeyCode::Left, KeyModifiers::NONE); - assert_eq!(action_for_key(left), Some(InputAction::SeekBackward)); - } - - #[test] - fn space_maps_to_toggle_pause() { - let key = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE); - assert_eq!(action_for_key(key), Some(InputAction::TogglePause)); - } -} diff --git a/apps/cli/src/tui/logo.rs b/apps/cli/src/tui/logo.rs deleted file mode 100644 index 3844396a12..0000000000 --- a/apps/cli/src/tui/logo.rs +++ /dev/null @@ -1,12 +0,0 @@ -use ratatui::style::{Color, Style}; -use ratatui::text::{Line, Span}; - -pub const LOGO_COLOR: Color = Color::Rgb(0xFD, 0xE6, 0xAE); - -const LOGO: [&str; 3] = [" █▌ ▐█ ", "▐▌ ▐▌", " █▌ ▐█ "]; - -pub fn logo_lines<'a>() -> Vec> { - LOGO.iter() - .map(|line| Line::from(Span::styled(*line, Style::default().fg(LOGO_COLOR)))) - .collect() -} diff --git a/apps/cli/src/tui/mod.rs b/apps/cli/src/tui/mod.rs deleted file mode 100644 index 08bd2127c7..0000000000 --- a/apps/cli/src/tui/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod capture; -mod input; -mod logo; -mod viewport; -pub mod waveform; - -pub use capture::{CaptureLayer, TraceBuffer, new_trace_buffer}; -pub(crate) use input::InputAction; -pub use viewport::InlineViewport; - -pub const SPINNER: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - -pub fn truncate_line(s: &str, max: usize) -> &str { - let char_count = s.chars().count(); - if char_count <= max { - return s; - } - let skip = char_count - max; - match s.char_indices().nth(skip) { - Some((byte_idx, _)) => &s[byte_idx..], - None => s, - } -} diff --git a/apps/cli/src/tui/viewport.rs b/apps/cli/src/tui/viewport.rs deleted file mode 100644 index d172e58812..0000000000 --- a/apps/cli/src/tui/viewport.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::io::{self, Stderr}; - -use ratatui::backend::CrosstermBackend; -use ratatui::layout::{Constraint, Flex, Layout}; -use ratatui::style::{Color, Style}; -use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, BorderType, Paragraph}; -use ratatui::{Terminal, TerminalOptions, Viewport}; - -use super::capture::TraceBuffer; -use super::input::{BackgroundInput, InputAction, View}; -use super::logo::logo_lines; - -const MAX_BOX_WIDTH: u16 = 60; - -pub struct InlineViewport { - terminal: Terminal>, - traces: Option, - view: View, - raw_mode: bool, - input: Option, -} - -impl InlineViewport { - pub fn stderr(height: u16, traces: Option) -> io::Result { - Self::stderr_interactive(height, traces, false) - } - - pub fn stderr_interactive( - height: u16, - traces: Option, - interactive: bool, - ) -> io::Result { - let raw_mode = interactive || traces.is_some(); - let mut input = None; - if raw_mode { - crossterm::terminal::enable_raw_mode()?; - input = Some(BackgroundInput::spawn()); - } - let backend = CrosstermBackend::new(io::stderr()); - let terminal = match Terminal::with_options( - backend, - TerminalOptions { - viewport: Viewport::Inline(height), - }, - ) { - Ok(terminal) => terminal, - Err(error) => { - if let Some(mut input) = input { - input.shutdown(); - } - if raw_mode { - let _ = crossterm::terminal::disable_raw_mode(); - } - return Err(error); - } - }; - Ok(Self { - terminal, - traces, - view: View::Progress, - raw_mode, - input, - }) - } - - /// Poll keyboard input. Handles view-toggle internally and returns - /// any actions the caller should handle (seek, pause, etc.). - pub fn poll_input(&mut self) -> Vec { - if !self.raw_mode { - return Vec::new(); - } - let Some(input) = self.input.as_ref() else { - return Vec::new(); - }; - let mut passthrough = Vec::new(); - while let Ok(action) = input.try_recv() { - if action == InputAction::ToggleView { - self.view = match self.view { - View::Progress => View::Traces, - View::Traces => View::Progress, - }; - } else { - passthrough.push(action); - } - } - passthrough - } - - fn shutdown_input(&mut self) { - if let Some(mut input) = self.input.take() { - input.shutdown(); - } - } - - fn teardown_raw_mode(&mut self) -> io::Result<()> { - if !self.raw_mode { - return Ok(()); - } - self.shutdown_input(); - crossterm::terminal::disable_raw_mode()?; - self.raw_mode = false; - Ok(()) - } - - pub fn draw(&mut self, lines: &[Line]) { - match self.view { - View::Progress => self.draw_lines(lines), - View::Traces => self.draw_traces(), - } - } - - pub fn draw_strings(&mut self, lines: &[String]) { - let styled: Vec = lines.iter().map(|s| Line::from(s.clone())).collect(); - self.draw(&styled); - } - - fn draw_lines(&mut self, lines: &[Line]) { - let logo = logo_lines(); - let logo_width = 10; - - let row_count = lines.len().max(logo.len()); - let mut content: Vec = Vec::with_capacity(row_count + 1); - for i in 0..row_count { - let mut spans: Vec = Vec::new(); - if i < logo.len() { - spans.extend(logo[i].spans.iter().cloned()); - spans.push(Span::raw(" ")); - } else { - spans.push(Span::raw(" ".repeat(logo_width + 2))); - } - if i < lines.len() { - spans.extend(lines[i].spans.iter().cloned()); - } - content.push(Line::from(spans)); - } - - if self.traces.is_some() { - content.push(Line::from(vec![ - Span::raw(" ".repeat(logo_width + 2)), - Span::styled( - "press 'd' to toggle traces", - Style::default().fg(Color::DarkGray), - ), - ])); - } - - let _ = self.terminal.draw(|frame| { - let full = frame.area(); - let capped_width = full.width.min(MAX_BOX_WIDTH); - let area = Layout::horizontal([Constraint::Length(capped_width)]) - .flex(Flex::Start) - .split(full)[0]; - let block = Block::bordered() - .border_type(BorderType::Rounded) - .border_style(Style::default().fg(Color::DarkGray)); - let inner = block.inner(area); - frame.render_widget(block, area); - - let chunks = - Layout::vertical(vec![Constraint::Length(1); inner.height as usize]).split(inner); - for (i, line) in content.iter().enumerate() { - if i < chunks.len() { - frame.render_widget(Paragraph::new(line.clone()), chunks[i]); - } - } - }); - } - - fn draw_traces(&mut self) { - let traces = match self.traces { - Some(ref buf) => buf, - None => return, - }; - - let _ = self.terminal.draw(|frame| { - let full = frame.area(); - let capped_width = full.width.min(MAX_BOX_WIDTH); - let area = Layout::horizontal([Constraint::Length(capped_width)]) - .flex(Flex::Start) - .split(full)[0]; - let block = Block::bordered() - .border_type(BorderType::Rounded) - .border_style(Style::default().fg(Color::DarkGray)); - let inner = block.inner(area); - frame.render_widget(block, area); - - let inner_height = inner.height as usize; - let trace_lines: Vec = if let Ok(buf) = traces.lock() { - buf.iter() - .rev() - .take(inner_height.saturating_sub(1)) - .cloned() - .collect::>() - .into_iter() - .rev() - .collect() - } else { - vec![] - }; - - let chunks = Layout::vertical(vec![Constraint::Length(1); inner_height]).split(inner); - - let header = Paragraph::new(Line::from("[traces] press 'd' to toggle")) - .style(Style::default().fg(Color::DarkGray)); - if !chunks.is_empty() { - frame.render_widget(header, chunks[0]); - } - - for (i, line) in trace_lines.iter().enumerate() { - if i + 1 < chunks.len() { - let p = Paragraph::new(Line::from(line.as_str())) - .style(Style::default().fg(Color::DarkGray)); - frame.render_widget(p, chunks[i + 1]); - } - } - }); - } - - /// Tear down raw mode but leave the last-drawn content visible. - /// The caller must have called `draw()` with the final content - /// immediately before calling this. - pub fn finish(&mut self) -> io::Result<()> { - self.teardown_raw_mode()?; - // After an inline draw, ratatui leaves the cursor on the last - // viewport row (the bottom border). We need to move past it - // so the shell prompt doesn't overwrite the border. - let mut stderr = io::stderr(); - crossterm::execute!(stderr, crossterm::cursor::MoveDown(1))?; - eprintln!(); - Ok(()) - } - - pub fn clear(&mut self) -> io::Result<()> { - use crossterm::{cursor, execute, terminal}; - - let area = self.terminal.get_frame().area(); - let height = area.height; - - self.terminal.draw(|frame| { - let area = frame.area(); - frame.render_widget(ratatui::widgets::Clear, area); - frame.render_widget(Paragraph::new(""), area); - })?; - - let mut stderr = io::stderr(); - execute!( - stderr, - cursor::MoveUp(height), - terminal::Clear(terminal::ClearType::FromCursorDown) - )?; - - self.teardown_raw_mode()?; - Ok(()) - } -} - -impl Drop for InlineViewport { - fn drop(&mut self) { - let _ = self.teardown_raw_mode(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ratatui::backend::TestBackend; - - fn draw_card(terminal: &mut Terminal, lines: &[String]) { - let content: Vec = lines.iter().map(|s| Line::from(s.as_str())).collect(); - terminal - .draw(|frame| { - let area = frame.area(); - let block = Block::bordered().border_type(BorderType::Rounded); - let inner = block.inner(area); - frame.render_widget(block, area); - - let chunks = Layout::vertical(vec![Constraint::Length(1); inner.height as usize]) - .split(inner); - for (i, line) in content.iter().enumerate() { - if i < chunks.len() { - frame.render_widget(Paragraph::new(line.clone()), chunks[i]); - } - } - }) - .unwrap(); - } - - #[test] - fn render_draws_card_with_three_lines() { - let backend = TestBackend::new(40, 5); - let mut terminal = Terminal::with_options( - backend, - TerminalOptions { - viewport: Viewport::Inline(5), - }, - ) - .unwrap(); - - let lines = [ - "recording mic 00:05".to_string(), - "16000 Hz 1 ch 5.0s audio".to_string(), - "out.wav lvl ||||....".to_string(), - ]; - draw_card(&mut terminal, &lines); - - let buf = terminal.backend().buffer().clone(); - let content_line: String = (0..buf.area.width) - .map(|x| buf[(x, 1)].symbol().chars().next().unwrap_or(' ')) - .collect(); - assert!(content_line.contains("recording mic")); - } -} diff --git a/apps/cli/src/tui/waveform.rs b/apps/cli/src/tui/waveform.rs deleted file mode 100644 index 5334b00958..0000000000 --- a/apps/cli/src/tui/waveform.rs +++ /dev/null @@ -1,407 +0,0 @@ -use std::collections::VecDeque; - -use ratatui::buffer::Buffer; -use ratatui::layout::Rect; -use ratatui::style::{Color, Style}; -use ratatui::text::{Line, Span}; -use ratatui::widgets::{StatefulWidget, Widget}; - -const BLOCKS: [char; 9] = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; -pub const MIC_COLOR: Color = Color::Rgb(0xFD, 0xE6, 0xAE); -pub const SYS_COLOR: Color = Color::Rgb(0xE0, 0xC0, 0x80); - -fn to_perceptual(level: f32) -> f32 { - if level <= 0.0 { - 0.0 - } else { - let db = 20.0 * level.log10(); - ((db + 48.0) / 48.0).clamp(0.0, 1.0) - } -} - -fn block_char(perceptual: f32) -> char { - let idx = (perceptual * 8.0).round() as usize; - BLOCKS[idx.min(8)] -} - -#[derive(Clone, Copy)] -pub enum WaveformMode { - Mono, - Dual, -} - -pub struct LiveWaveformState { - left: VecDeque, - right: VecDeque, - width: usize, -} - -impl LiveWaveformState { - pub fn new(width: usize) -> Self { - Self { - left: VecDeque::with_capacity(width + 1), - right: VecDeque::with_capacity(width + 1), - width, - } - } - - pub fn push(&mut self, left: f32, right: f32) { - push_level(&mut self.left, left, self.width); - push_level(&mut self.right, right, self.width); - } -} - -pub struct LiveWaveform { - pub mode: WaveformMode, -} - -impl StatefulWidget for LiveWaveform { - type State = LiveWaveformState; - - fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - let width = area.width as usize; - let spans = match self.mode { - WaveformMode::Mono => mono_spans(&state.left, MIC_COLOR, width), - WaveformMode::Dual => overlaid_spans(&state.left, &state.right, width), - }; - let line = Line::from(spans); - buf.set_line(area.x, area.y, &line, area.width); - } -} - -impl LiveWaveform { - pub fn spans( - state: &LiveWaveformState, - mode: WaveformMode, - width: usize, - ) -> Vec> { - match mode { - WaveformMode::Mono => mono_spans(&state.left, MIC_COLOR, width), - WaveformMode::Dual => overlaid_spans(&state.left, &state.right, width), - } - } -} - -pub struct PlaybackWaveform<'a> { - pub peaks: &'a [f32], - pub fraction: f64, - pub color: Color, -} - -impl Widget for PlaybackWaveform<'_> { - fn render(self, area: Rect, buf: &mut Buffer) { - let width = area.width as usize; - let spans = playback_spans(self.peaks, self.fraction, self.color, width); - let line = Line::from(spans); - buf.set_line(area.x, area.y, &line, area.width); - } -} - -impl<'a> PlaybackWaveform<'a> { - pub fn lines( - peaks: &[f32], - fraction: f64, - color: Color, - width: usize, - rows: usize, - ) -> Vec> { - playback_multirow(peaks, fraction, color, width, rows) - } - - pub fn lines_dual( - left_peaks: &[f32], - right_peaks: &[f32], - fraction: f64, - left_color: Color, - right_color: Color, - width: usize, - rows: usize, - ) -> Vec> { - playback_multirow_dual( - left_peaks, - right_peaks, - fraction, - left_color, - right_color, - width, - rows, - ) - } -} - -/// Compute peak amplitudes for `width` segments from f32 samples (range -1.0..1.0). -pub fn compute_peaks(samples: &[f32], width: usize) -> Vec { - if samples.is_empty() || width == 0 { - return vec![0.0; width]; - } - let chunk_size = (samples.len() + width - 1) / width; - let mut peaks = Vec::with_capacity(width); - for chunk in samples.chunks(chunk_size) { - let peak = chunk - .iter() - .map(|s| s.abs()) - .fold(0.0_f32, f32::max) - .clamp(0.0, 1.0); - peaks.push(peak); - } - peaks.truncate(width); - while peaks.len() < width { - peaks.push(0.0); - } - peaks -} - -// --------------------------------------------------------------------------- -// Internal helpers -// --------------------------------------------------------------------------- - -fn push_level(history: &mut VecDeque, level: f32, width: usize) { - if history.len() >= width { - history.pop_front(); - } - history.push_back(level); -} - -fn mono_spans(history: &VecDeque, color: Color, width: usize) -> Vec> { - let mut spans = Vec::with_capacity(width); - let start = width.saturating_sub(history.len()); - for i in 0..width { - if i < start { - spans.push(Span::raw(" ")); - } else { - let level = to_perceptual(history[i - start]); - let ch = block_char(level); - spans.push(Span::styled(String::from(ch), Style::default().fg(color))); - } - } - spans -} - -fn overlaid_spans(mic: &VecDeque, sys: &VecDeque, width: usize) -> Vec> { - let mut spans = Vec::with_capacity(width + 4); - spans.push(Span::styled("mic", Style::default().fg(MIC_COLOR))); - spans.push(Span::raw("/")); - spans.push(Span::styled("sys", Style::default().fg(SYS_COLOR))); - spans.push(Span::raw(" ")); - - let mic_start = width.saturating_sub(mic.len()); - let sys_start = width.saturating_sub(sys.len()); - - for i in 0..width { - let m = if i >= mic_start { - to_perceptual(mic[i - mic_start]) - } else { - 0.0 - }; - let s = if i >= sys_start { - to_perceptual(sys[i - sys_start]) - } else { - 0.0 - }; - - let level = m.max(s); - if level <= 0.0 { - spans.push(Span::raw(" ")); - } else { - let color = if m >= s { MIC_COLOR } else { SYS_COLOR }; - spans.push(Span::styled( - String::from(block_char(level)), - Style::default().fg(color), - )); - } - } - spans -} - -const MIN_BAR: f32 = 0.125; - -fn normalize_peaks(peaks: &[f32], width: usize) -> (Vec, usize) { - let resampled = resample_peaks(peaks, width); - let max_peak = resampled.iter().cloned().fold(0.0_f32, f32::max); - let norm = if max_peak > 0.0 { 1.0 / max_peak } else { 1.0 }; - let normalized: Vec = resampled - .iter() - .map(|&p| { - let n = (p * norm).clamp(0.0, 1.0); - if n > 0.0 { n.max(MIN_BAR) } else { n } - }) - .collect(); - (normalized, width) -} - -fn playback_spans(peaks: &[f32], fraction: f64, color: Color, width: usize) -> Vec> { - let (normalized, width) = normalize_peaks(peaks, width); - let played_cols = (fraction * width as f64).round() as usize; - let mut spans = Vec::with_capacity(width); - for (i, &n) in normalized.iter().enumerate() { - let ch = block_char(n); - let fg = if i < played_cols { - color - } else { - Color::DarkGray - }; - spans.push(Span::styled(String::from(ch), Style::default().fg(fg))); - } - spans -} - -fn playback_multirow( - peaks: &[f32], - fraction: f64, - color: Color, - width: usize, - rows: usize, -) -> Vec> { - let (normalized, width) = normalize_peaks(peaks, width); - let played_cols = (fraction * width as f64).round() as usize; - let total_levels = rows as f32 * 8.0; - - let mut result = Vec::with_capacity(rows); - for row in 0..rows { - let row_base = (rows - 1 - row) as f32 * 8.0; - let mut spans = Vec::with_capacity(width); - for (i, &n) in normalized.iter().enumerate() { - let filled = (n * total_levels - row_base).clamp(0.0, 8.0); - let ch = block_char(filled / 8.0); - let fg = if i < played_cols { - color - } else { - Color::DarkGray - }; - spans.push(Span::styled(String::from(ch), Style::default().fg(fg))); - } - result.push(Line::from(spans)); - } - result -} - -fn playback_multirow_dual( - left_peaks: &[f32], - right_peaks: &[f32], - fraction: f64, - left_color: Color, - right_color: Color, - width: usize, - rows: usize, -) -> Vec> { - let (left_norm, width) = normalize_peaks(left_peaks, width); - let (right_norm, _) = normalize_peaks(right_peaks, width); - let played_cols = (fraction * width as f64).round() as usize; - let total_levels = rows as f32 * 8.0; - - let mut result = Vec::with_capacity(rows); - for row in 0..rows { - let row_base = (rows - 1 - row) as f32 * 8.0; - let mut spans = Vec::with_capacity(width); - for i in 0..width { - let l = *left_norm.get(i).unwrap_or(&0.0); - let r = *right_norm.get(i).unwrap_or(&0.0); - let level = l.max(r); - let filled = (level * total_levels - row_base).clamp(0.0, 8.0); - let ch = block_char(filled / 8.0); - let fg = if i < played_cols { - if l >= r { left_color } else { right_color } - } else { - Color::DarkGray - }; - spans.push(Span::styled(String::from(ch), Style::default().fg(fg))); - } - result.push(Line::from(spans)); - } - result -} - -/// Deinterleave stereo samples into separate left and right channel vectors. -pub fn deinterleave_stereo(samples: &[f32]) -> (Vec, Vec) { - let len = samples.len() / 2; - let mut left = Vec::with_capacity(len); - let mut right = Vec::with_capacity(len); - for pair in samples.chunks_exact(2) { - left.push(pair[0]); - right.push(pair[1]); - } - (left, right) -} - -fn resample_peaks(peaks: &[f32], width: usize) -> Vec { - if peaks.is_empty() || width == 0 { - return vec![0.0; width]; - } - if peaks.len() == width { - return peaks.to_vec(); - } - let mut out = Vec::with_capacity(width); - for i in 0..width { - let pos = i as f64 * (peaks.len() as f64 - 1.0) / (width as f64 - 1.0).max(1.0); - let lo = pos.floor() as usize; - let hi = (lo + 1).min(peaks.len() - 1); - let t = pos - lo as f64; - out.push(peaks[lo] * (1.0 - t as f32) + peaks[hi] * t as f32); - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn compute_peaks_basic() { - let samples: Vec = vec![0.03, -0.06, 0.09, -0.12, 0.15, -0.18, 0.21, -0.24]; - let peaks = compute_peaks(&samples, 4); - assert_eq!(peaks.len(), 4); - assert!(peaks[3] > peaks[0]); - } - - #[test] - fn compute_peaks_empty() { - assert_eq!(compute_peaks(&[], 4), vec![0.0; 4]); - } - - #[test] - fn live_waveform_state_push() { - let mut state = LiveWaveformState::new(4); - for i in 0..6 { - state.push(i as f32 * 0.1, 0.0); - } - assert_eq!(state.left.len(), 4); - assert_eq!(state.right.len(), 4); - } - - #[test] - fn playback_spans_split_color() { - let peaks = vec![0.5; 10]; - let spans = playback_spans(&peaks, 0.5, MIC_COLOR, 10); - assert_eq!(spans.len(), 10); - assert_eq!(spans[0].style.fg, Some(MIC_COLOR)); - assert_eq!(spans[9].style.fg, Some(Color::DarkGray)); - } - - #[test] - fn overlaid_mic_dominant() { - let mut mic = VecDeque::new(); - let mut sys = VecDeque::new(); - mic.push_back(0.5); - sys.push_back(0.01); - let spans = overlaid_spans(&mic, &sys, 1); - let block_span = spans - .iter() - .find(|s| s.content.chars().any(|c| BLOCKS[1..].contains(&c))); - assert!(block_span.is_some()); - assert_eq!(block_span.unwrap().style.fg, Some(MIC_COLOR)); - } - - #[test] - fn overlaid_sys_dominant() { - let mut mic = VecDeque::new(); - let mut sys = VecDeque::new(); - mic.push_back(0.01); - sys.push_back(0.5); - let spans = overlaid_spans(&mic, &sys, 1); - let block_span = spans - .iter() - .find(|s| s.content.chars().any(|c| BLOCKS[1..].contains(&c))); - assert!(block_span.is_some()); - assert_eq!(block_span.unwrap().style.fg, Some(SYS_COLOR)); - } -} diff --git a/apps/cli/tests/pty.rs b/apps/cli/tests/pty.rs deleted file mode 100644 index 60e29430f0..0000000000 --- a/apps/cli/tests/pty.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::io::{Read, Write}; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; - -use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; -use vt100::Parser; - -const PTY_ROWS: u16 = 24; -const PTY_COLS: u16 = 80; -const POLL_INTERVAL: Duration = Duration::from_millis(30); - -struct PtyApp { - child: Box, - writer: Box, - output: Arc>>, - screen: Arc>, - reader_handle: Option>, - _temp_dir: PathBuf, -} - -impl PtyApp { - #[allow(deprecated)] - fn spawn(args: &[&str]) -> Self { - let temp_dir = tempfile::tempdir().expect("failed to create temp dir"); - let temp_path = temp_dir.into_path(); - - let pty_system = NativePtySystem::default(); - let pair = pty_system - .openpty(PtySize { - rows: PTY_ROWS, - cols: PTY_COLS, - pixel_width: 0, - pixel_height: 0, - }) - .expect("failed to open pty"); - - let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_char")); - for arg in args { - cmd.arg(arg); - } - - cmd.env("TERM", "xterm-256color"); - cmd.env("NO_COLOR", "1"); - cmd.env("XDG_CONFIG_HOME", temp_path.join("config")); - cmd.env("XDG_DATA_HOME", temp_path.join("data")); - cmd.env("XDG_CACHE_HOME", temp_path.join("cache")); - cmd.env("HYPR_ANALYTICS_DISABLED", "1"); - cmd.env("HYPR_ENV", "test"); - - let child = pair.slave.spawn_command(cmd).expect("failed to spawn"); - drop(pair.slave); - - let writer = pair.master.take_writer().expect("failed to take writer"); - let mut reader = pair - .master - .try_clone_reader() - .expect("failed to clone reader"); - - let output: Arc>> = Arc::new(Mutex::new(Vec::new())); - let output_clone = Arc::clone(&output); - - let screen = Arc::new(Mutex::new(Parser::new(PTY_ROWS, PTY_COLS, 0))); - let screen_clone = Arc::clone(&screen); - - let reader_handle = std::thread::spawn(move || { - let mut buf = [0u8; 4096]; - loop { - match reader.read(&mut buf) { - Ok(0) => break, - Ok(n) => { - { - let mut out = output_clone.lock().unwrap(); - out.extend_from_slice(&buf[..n]); - } - { - let mut scr = screen_clone.lock().unwrap(); - scr.process(&buf[..n]); - } - } - Err(_) => break, - } - } - }); - - PtyApp { - child, - writer, - output, - screen, - reader_handle: Some(reader_handle), - _temp_dir: temp_path, - } - } - - fn screen_text(&self) -> String { - let scr = self.screen.lock().unwrap(); - scr.screen().contents() - } - - fn raw_output(&self) -> String { - let out = self.output.lock().unwrap(); - String::from_utf8_lossy(&out).into_owned() - } - - fn wait_for_screen(&self, needle: &str, timeout: Duration) -> bool { - let start = Instant::now(); - while start.elapsed() < timeout { - let text = self.screen_text(); - if !text.is_empty() && text.contains(needle) { - return true; - } - std::thread::sleep(POLL_INTERVAL); - } - false - } -} - -impl Drop for PtyApp { - fn drop(&mut self) { - let _ = self.writer.write_all(b"\x03"); - let _ = self.writer.flush(); - - let start = Instant::now(); - while start.elapsed() < Duration::from_millis(500) { - if let Ok(Some(_)) = self.child.try_wait() { - break; - } - std::thread::sleep(POLL_INTERVAL); - } - - let _ = self.child.kill(); - let _ = self.child.wait(); - - if let Some(handle) = self.reader_handle.take() { - let _ = handle.join(); - } - - let _ = std::fs::remove_dir_all(&self._temp_dir); - - std::thread::sleep(Duration::from_millis(100)); - } -} - -#[cfg_attr(not(feature = "e2e"), ignore)] -#[test] -fn root_help_renders() { - let app = PtyApp::spawn(&[]); - assert!( - app.wait_for_screen("Docs:", Duration::from_secs(5)), - "root help should render.\nScreen:\n{}\nRaw:\n{}", - app.screen_text(), - app.raw_output() - ); -} - -#[cfg_attr(not(feature = "e2e"), ignore)] -#[test] -fn transcribe_help_renders() { - let app = PtyApp::spawn(&["transcribe", "--help"]); - assert!( - app.wait_for_screen("Usage: char transcribe", Duration::from_secs(5)), - "transcribe help should render.\nScreen:\n{}\nRaw:\n{}", - app.screen_text(), - app.raw_output() - ); -} diff --git a/apps/desktop/index.html b/apps/desktop/index.html index 4b3d60054b..732bbd48b8 100644 --- a/apps/desktop/index.html +++ b/apps/desktop/index.html @@ -6,7 +6,7 @@ name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> - Char + Anarlog - - - - - diff --git a/apps/unsigned-web/src/pages/index.mdx b/apps/unsigned-web/src/pages/index.mdx deleted file mode 100644 index a0949b184f..0000000000 --- a/apps/unsigned-web/src/pages/index.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: "../layouts/Base.astro" ---- - -
- - Unsigned Char - - - -
diff --git a/apps/unsigned-web/src/styles.css b/apps/unsigned-web/src/styles.css deleted file mode 100644 index 50b9d2eda2..0000000000 --- a/apps/unsigned-web/src/styles.css +++ /dev/null @@ -1,56 +0,0 @@ -@import "tailwindcss"; - -@theme { - --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace; - --font-serif: "Instrument Serif", Georgia, serif; -} - -@keyframes fade-up { - from { - opacity: 0; - transform: translateY(14px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.stagger-1 { - animation: fade-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0s both; -} - -.stagger-2 { - animation: fade-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both; -} - -.stagger-3 { - animation: fade-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both; -} - -.stagger-4 { - animation: fade-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.5s both; -} - -::selection { - background: rgba(23, 23, 23, 0.14); - color: #171717; -} - -::-webkit-scrollbar { - width: 5px; - height: 5px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: #2a2a2a; - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: #404040; -} diff --git a/apps/unsigned-web/tsconfig.json b/apps/unsigned-web/tsconfig.json deleted file mode 100644 index ddbee4f2ac..0000000000 --- a/apps/unsigned-web/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "astro/tsconfigs/strict", - "compilerOptions": { - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true - } -} diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts index 221610645f..d5664a4d0e 100644 --- a/apps/web/content-collections.ts +++ b/apps/web/content-collections.ts @@ -1,144 +1,11 @@ import { defineCollection, defineConfig } from "@content-collections/core"; import { compileMDX } from "@content-collections/mdx"; -import * as fs from "fs"; -import GithubSlugger from "github-slugger/index.js"; import mdxMermaid from "mdx-mermaid"; -import * as path from "path"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypeSlug from "rehype-slug"; import remarkGfm from "remark-gfm"; -import { createHighlighter, type Highlighter } from "shiki"; import { z } from "zod"; -import { VersionPlatform } from "@/scripts/versioning"; - -const EXT_TO_LANG: Record = { - ".rs": "rust", - ".ts": "typescript", - ".tsx": "tsx", - ".js": "javascript", - ".jsx": "jsx", - ".json": "json", - ".toml": "toml", - ".yaml": "yaml", - ".yml": "yaml", - ".py": "python", - ".go": "go", - ".css": "css", - ".html": "html", - ".md": "markdown", - ".sh": "bash", - ".sql": "sql", - ".swift": "swift", -}; - -let highlighterPromise: Promise | null = null; -function getHighlighter(): Promise { - if (!highlighterPromise) { - highlighterPromise = createHighlighter({ - themes: ["github-light"], - langs: Object.values(EXT_TO_LANG), - }); - } - return highlighterPromise; -} - -function parseLineRange(url: string): { startLine?: number; endLine?: number } { - const lineMatch = url.match(/#L(\d+)(?:-L(\d+))?$/); - if (!lineMatch) return {}; - return { - startLine: parseInt(lineMatch[1], 10), - endLine: lineMatch[2] ? parseInt(lineMatch[2], 10) : undefined, - }; -} - -function extractLines( - content: string, - startLine?: number, - endLine?: number, -): string { - if (!startLine) return content; - const lines = content.split("\n"); - const start = startLine - 1; - const end = endLine ?? lines.length; - return lines.slice(start, end).join("\n"); -} - -async function embedGithubCode(content: string): Promise { - const githubCodeRegex = //g; - let result = content; - - const matches = [...content.matchAll(githubCodeRegex)]; - for (const match of matches) { - const [fullMatch, url] = match; - - const repoMatch = url.match( - /github\.com\/fastrepl\/(hyprnote|char)\/blob\/[^/]+\/(.+?)(?:#L\d+(?:-L\d+)?)?$/, - ); - if (repoMatch) { - const filePath = repoMatch[2]; - const fileName = path.basename(filePath); - const localPath = path.resolve(process.cwd(), "..", "..", filePath); - const { startLine, endLine } = parseLineRange(url); - - try { - const fileContent = fs.readFileSync(localPath, "utf-8"); - - const codeBlockMatch = fileContent.match(/```(\w+)\n([\s\S]*?)```/); - const rawCode = codeBlockMatch ? codeBlockMatch[2] : fileContent; - const lang = codeBlockMatch - ? codeBlockMatch[1] - : EXT_TO_LANG[path.extname(fileName)]; - - const slicedCode = extractLines(rawCode.trimEnd(), startLine, endLine); - const escapedCode = JSON.stringify(slicedCode); - const lineNum = startLine ?? 1; - - let highlightedAttr = ""; - if (lang) { - const highlighter = await getHighlighter(); - const html = highlighter.codeToHtml(slicedCode, { - lang, - theme: "github-light", - }); - highlightedAttr = ` highlightedHtml={${JSON.stringify(html)}}`; - } - - const langAttr = lang ? ` language="${lang}"` : ""; - result = result.replace( - fullMatch, - ``, - ); - } catch { - console.warn(`Failed to read local file: ${localPath}`); - } - } - } - - return result; -} - -function extractToc( - content: string, -): Array<{ id: string; text: string; level: number }> { - const toc: Array<{ id: string; text: string; level: number }> = []; - const slugger = new GithubSlugger(); - const lines = content.split("\n"); - - for (const line of lines) { - const match = line.match(/^(#{2,4})\s+(.+)$/); - if (match) { - const level = match[1].length; - const text = match[2].trim(); - const id = slugger.slug(text); - - toc.push({ id, text, level }); - } - } - - return toc; -} - const articles = defineCollection({ name: "articles", directory: "content/articles", @@ -150,7 +17,6 @@ const articles = defineCollection({ meta_description: z.string().default(""), author: z.union([z.string(), z.array(z.string())]), date: z.string(), - coverImage: z.string().optional(), featured: z.boolean().optional(), ready_for_review: z.boolean().default(false), category: z @@ -160,13 +26,10 @@ const articles = defineCollection({ "Engineering", "Founders' notes", "Guides", - "Char Weekly", ]) .optional(), }), transform: async (document, context) => { - const toc = extractToc(document.content); - const mdx = await compileMDX(context, document, { remarkPlugins: [remarkGfm, mdxMermaid], rehypePlugins: [ @@ -185,7 +48,7 @@ const articles = defineCollection({ const slug = document._meta.path.replace(/\.mdx$/, ""); - const rawAuthor = document.author || "Char Team"; + const rawAuthor = document.author || "Anarlog Team"; const author = Array.isArray(rawAuthor) ? rawAuthor : [rawAuthor]; const title = document.display_title || document.meta_title; @@ -195,108 +58,6 @@ const articles = defineCollection({ slug, author, title, - toc, - }; - }, -}); - -const changelog = defineCollection({ - name: "changelog", - directory: "../../packages/changelog/content", - include: "*.md", - exclude: "AGENTS.md", - schema: z.object({ - date: z - .string() - .trim() - .transform((value) => (value === "" ? undefined : value)) - .optional(), - }), - transform: async (document, { skip }) => { - if (!document.date) { - return skip("missing changelog date"); - } - - const version = document._meta.path.replace(/\.md$/, ""); - const baseUrl = `https://github.com/fastrepl/char/releases/download/desktop_v${version}`; - const downloads: Record = { - "dmg-aarch64": `${baseUrl}/char-macos-aarch64.dmg`, - "appimage-x86_64": `${baseUrl}/char-linux-x86_64.AppImage`, - "deb-x86_64": `${baseUrl}/char-linux-x86_64.deb`, - "appimage-aarch64": `${baseUrl}/char-linux-aarch64.AppImage`, - "deb-aarch64": `${baseUrl}/char-linux-aarch64.deb`, - }; - - return { - ...document, - slug: version, - version, - downloads, - }; - }, -}); - -const docs = defineCollection({ - name: "docs", - directory: "content/docs", - include: "**/*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - title: z.string(), - section: z.string(), - description: z.string().optional(), - summary: z.string().optional(), - }), - transform: async (document, context) => { - const processedContent = await embedGithubCode(document.content); - const processedDocument = { ...document, content: processedContent }; - - const toc = extractToc(processedContent); - - const mdx = await compileMDX(context, processedDocument, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const pathParts = document._meta.path.split("/"); - const fileName = pathParts.pop()!.replace(/\.mdx$/, ""); - - const sectionFolder = pathParts[0] || "general"; - - const isIndex = fileName === "index"; - - const orderMatch = fileName.match(/^(\d+)\./); - const order = orderMatch ? parseInt(orderMatch[1], 10) : 999; - - const cleanFileName = fileName.replace(/^\d+\./, ""); - const filteredPathParts = pathParts.filter((part) => part !== "_"); - const cleanPath = - filteredPathParts.length > 0 - ? `${filteredPathParts.join("/")}/${cleanFileName}` - : cleanFileName; - const slug = cleanPath; - - return { - ...document, - description: document.description || document.summary, - summary: document.summary || document.description, - mdx, - slug, - sectionFolder, - isIndex, - order, - toc, }; }, }); @@ -305,485 +66,21 @@ const legal = defineCollection({ name: "legal", directory: "content/legal", include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - title: z.string(), - summary: z.string(), - date: z.string(), - }), - transform: async (document, context) => { - const toc = extractToc(document.content); - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - toc, - }; - }, -}); - -const templates = defineCollection({ - name: "templates", - directory: "content/templates", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - title: z.string(), - description: z.string(), - category: z.string(), - targets: z.array(z.string()), - banner: z.string().optional(), - sections: z.array( - z.object({ - title: z.string(), - description: z.string().optional(), - }), - ), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const hooks = defineCollection({ - name: "hooks", - directory: "content/hooks", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - name: z.string(), - description: z.string().nullable(), - args: z - .array( - z.object({ - name: z.string(), - type_name: z.string(), - description: z.string().nullable(), - optional: z.boolean().default(false), - }), - ) - .optional(), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const deeplinks = defineCollection({ - name: "deeplinks", - directory: "content/deeplinks", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - path: z.string(), - description: z.string().nullable(), - params: z - .array( - z.object({ - name: z.string(), - type_name: z.string(), - description: z.string().nullable(), - }), - ) - .optional(), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const vs = defineCollection({ - name: "vs", - directory: "content/vs", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - name: z.string(), - icon: z.string(), - headline: z.string(), - description: z.string(), - metaDescription: z.string(), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const integrations = defineCollection({ - name: "integrations", - directory: "content/integrations", - include: "**/*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - platform: z.string(), - icon: z.string(), - headline: z.string(), - description: z.string(), - metaDescription: z.string(), - features: z.array(z.string()).optional(), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const pathParts = document._meta.path.split("/"); - const fileName = pathParts.pop()!.replace(/\.mdx$/, ""); - const category = pathParts[0] || "general"; - const slug = fileName; - - return { - ...document, - mdx, - slug, - category, - }; - }, -}); - -const shortcuts = defineCollection({ - name: "shortcuts", - directory: "content/shortcuts", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - title: z.string(), - description: z.string(), - category: z.string(), - prompt: z.string(), - banner: z.string().optional(), - targets: z.array(z.string()).optional(), - }), - transform: async (document, context) => { - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const ossFriends = defineCollection({ - name: "ossFriends", - directory: "content/oss-friends", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - name: z.string(), - description: z.string(), - href: z.string(), - image: z.string().optional(), - github: z.string(), - }), - transform: async (document) => { - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - slug, - }; - }, -}); - -const handbook = defineCollection({ - name: "handbook", - directory: "content/handbook", - include: "**/*.mdx", - exclude: "AGENTS.md", schema: z.object({ title: z.string(), - section: z.string(), summary: z.string().optional(), - }), - transform: async (document, context) => { - const toc = extractToc(document.content); - - const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], - }); - - const pathParts = document._meta.path.split("/"); - const fileName = pathParts.pop()!.replace(/\.mdx$/, ""); - - const sectionFolder = pathParts[0] || "general"; - - const isIndex = fileName === "index"; - - const orderMatch = fileName.match(/^(\d+)\./); - const order = orderMatch ? parseInt(orderMatch[1], 10) : 999; - - const cleanFileName = fileName.replace(/^\d+\./, ""); - const cleanPath = - pathParts.length > 0 - ? `${pathParts.join("/")}/${cleanFileName}` - : cleanFileName; - const slug = cleanPath; - - return { - ...document, - mdx, - slug, - sectionFolder, - isIndex, - order, - toc, - }; - }, -}); - -const updates = defineCollection({ - name: "updates", - directory: "content/updates", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - title: z.string(), date: z.string(), }), transform: async (document, context) => { const mdx = await compileMDX(context, document, { - remarkPlugins: [remarkGfm, mdxMermaid], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "wrap", - properties: { - className: ["anchor"], - }, - }, - ], - ], + remarkPlugins: [remarkGfm], + rehypePlugins: [rehypeSlug], }); - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - mdx, - slug, - }; - }, -}); - -const solutions = defineCollection({ - name: "solutions", - directory: "content/solutions", - include: "*.mdx", - exclude: "AGENTS.md", - schema: z.object({ - label: z.string(), - icon: z.string(), - order: z.number().default(0), - metaTitle: z.string(), - metaDescription: z.string(), - hero: z.object({ - badgeText: z.string(), - title: z.string(), - description: z.string(), - primaryCTA: z.object({ label: z.string(), to: z.string() }), - secondaryCTA: z.object({ label: z.string(), to: z.string() }).optional(), - }), - features: z.object({ - title: z.string(), - description: z.string(), - items: z.array( - z.object({ - icon: z.string(), - title: z.string(), - description: z.string(), - }), - ), - }), - useCases: z.object({ - title: z.string(), - description: z.string(), - items: z.array( - z.object({ - title: z.string(), - description: z.string(), - }), - ), - }), - cta: z.object({ - title: z.string(), - description: z.string(), - }), - }), - transform: async (document) => { - const slug = document._meta.path.replace(/\.mdx$/, ""); - - return { - ...document, - slug, - }; + return { ...document, mdx, slug }; }, }); export default defineConfig({ - collections: [ - articles, - changelog, - docs, - legal, - templates, - shortcuts, - hooks, - deeplinks, - vs, - integrations, - handbook, - ossFriends, - updates, - solutions, - ], + collections: [articles, legal], }); diff --git a/apps/web/content/AGENTS.md b/apps/web/content/AGENTS.md index dcff04e8b1..10e603fe8c 100644 --- a/apps/web/content/AGENTS.md +++ b/apps/web/content/AGENTS.md @@ -2,17 +2,19 @@ All content created under this directory must follow the positioning below. -## What Char Is +## What Anarlog Is -Char is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. +Anarlog is the open-source AI meeting notetaker. Free forever, MIT-licensed, runs on your machine. The app you've been using as "Char" — same product, same files, renamed and relicensed. + +> Note: This blog lives at anarlog.so. The brand "Char" still exists, but it's a different product now (an agentic todo notepad — see char.com). All content under this directory is for **Anarlog**, the meeting notetaker. ## Core Philosophy -**Zero lock-in.** Char gives users ownership at the foundational level—your files, your AI, your workflow. No lock-in. No compromises. +**Zero lock-in.** Anarlog gives users ownership at the foundational level — your files, your AI, your workflow. No lock-in. No compromises. **Files over apps.** Everything is stored as plain markdown files on the user's device, not in proprietary databases or cloud servers. -**Secure by design.** IT team can audit the code and you can use the AI provider your security team approves. +**Secure by design.** IT teams can audit the code, and you can use the AI provider your security team approves. **Simple but powerful.** Complete control without complexity. The tool doesn't get in your way. @@ -36,7 +38,7 @@ Char is an open-source AI notepad for meetings built for high-agency people who - User controls which AI processes their data **Your choice of AI stack** -- Managed cloud service (easiest, works out of the box) +- Managed cloud service (kept through the Char→Anarlog migration window) - Bring your own API keys (OpenAI, Deepgram, Anthropic, others) - Run local models (via Ollama or LM Studio) @@ -55,23 +57,24 @@ Char is an open-source AI notepad for meetings built for high-agency people who - Import existing recordings/transcripts - 45+ language support -## What Makes Char Different +## What Makes Anarlog Different -**vs. Other AI note-takers:** +**vs. Other AI notetakers (Granola, Otter, Fireflies, Fathom, tldv):** - Plain markdown files instead of proprietary databases - System audio capture instead of meeting bots - User's choice of AI provider instead of vendor lock-in +- Open source — audit, fork, run forever - Complete file ownership instead of platform dependency **Privacy approach:** -- Char doesn't store conversations -- Every audio file, transcript, and note lives on user's computer +- Anarlog doesn't store conversations +- Every audio file, transcript, and note lives on the user's computer - User decides if data ever leaves their device - Option to deploy fully local - No data used for AI training **Works With** -All meeting types: Zoom, Teams, phone calls, in-person conversations +All meeting types: Zoom, Teams, Google Meet, phone calls, in-person conversations ## Brand Voice @@ -90,19 +93,22 @@ All meeting types: Zoom, Teams, phone calls, in-person conversations ## Key Messaging Themes -1. **Complete control** - over AI stack, data, and workflow -2. **True ownership** - files on your device, not someone's database -3. **No lock-in** - portable format, works with any tool -4. **Simple + powerful** - control without complexity -5. **For high-agency people** - built for those who refuse to compromise +1. **Complete control** — over AI stack, data, and workflow +2. **True ownership** — files on your device, not someone's database +3. **No lock-in** — portable format, works with any tool +4. **Open source** — audit it, fork it, trust it +5. **Simple + powerful** — control without complexity +6. **For high-agency people** — built for those who refuse to compromise ## What We're Building Toward -A notepad that gives complete control without getting in your way. Clean, simple, aesthetic—but with full ownership underneath. +A meeting notetaker that gives complete control without getting in your way. Clean, simple, aesthetic — but with full ownership underneath. ## Critical Reminders -- **Name:** Always use "Char" (not "Hyprnote" - that's the old name) +- **Name:** Always use "Anarlog" for the meeting notetaker. "Char" is the legacy name (the app pre-rename) AND a different product (the agentic todo notepad at char.com). Don't conflate them in content. +- **Hyprnote** is the original product name — only mention in historical context. +- **Don't recommend Char as a "managed Anarlog"** — Char is a different product (delegation/todos), not a managed cloud version of the meeting notetaker. - **Tone:** Direct, engineering-minded, respects user intelligence -- **Focus:** Zero lock-in, true ownership, complete control +- **Focus:** Zero lock-in, true ownership, complete control, open source - **Avoid:** Generic productivity language, corporate marketing speak, fear-based messaging diff --git a/apps/web/content/articles/ai-meeting-summary-tools.mdx b/apps/web/content/articles/ai-meeting-summary-tools.mdx index 1fbe3f0f33..eebda6adf7 100644 --- a/apps/web/content/articles/ai-meeting-summary-tools.mdx +++ b/apps/web/content/articles/ai-meeting-summary-tools.mdx @@ -4,7 +4,6 @@ display_title: "Best AI Meeting Summary Tools in 2026" meta_description: "Find the best AI meeting summary tool for your team. Compare 9 top solutions, including free options, bot-free tools, and enterprise platforms." author: - "John Jeong" -coverImage: "/api/assets/blog/ai-meeting-summary-tools/cover.png" featured: false category: "Comparisons" date: "2025-10-06" @@ -18,21 +17,19 @@ This list is about tools that actually compress information: decisions, action i ## AI meeting summary tools comparison: quick overview -Here's a quick comparison of the best AI meeting summary tools to help you decide: +A quick comparison of the tools below: - -| Tool | Best For | Starting Price | +| Tool | Best For | Starting Price | | --------- | -------------------------------------------- | ----------------------------------------- | -| Char | Zero lock-in, complete control | Free forever (local + BYOK). Cloud: $25/mo | -| Read AI | Unified search across meetings & emails | Free (Pro: $19.75/mo) | -| Fathom | Small teams wanting free unlimited recording | Free (Premium: $19/mo) | -| Jamie AI | Bot-free professional meetings | €24/mo (~$26) | -| Fellow | Security-focused enterprises | Free (Enterprise: $25/mo) | -| Fireflies | Conversation analytics & insights | Free (Pro: $18/mo) | -| Otter AI | Established mainstream solution | Free (Pro: $16.99/mo) | -| Avoma | Revenue teams & sales operations | $19/mo | -| MeetGeek | Automated workflows & team analytics | Free (Pro: $19/mo) | - +| Anarlog | Zero lock-in, complete control | Free forever, fully local + BYOK. | +| Read AI | Unified search across meetings & emails | Free (Pro: $19.75/mo) | +| Fathom | Small teams wanting free unlimited recording | Free (Premium: $19/mo) | +| Jamie AI | Bot-free professional meetings | €24/mo (~$26) | +| Fellow | Security-focused enterprises | Free (Enterprise: $25/mo) | +| Fireflies | Conversation analytics & insights | Free (Pro: $18/mo) | +| Otter AI | Established mainstream solution | Free (Pro: $16.99/mo) | +| Avoma | Revenue teams & sales operations | $19/mo | +| MeetGeek | Automated workflows & team analytics | Free (Pro: $19/mo) | ## What makes great AI meeting summary tools? @@ -46,31 +43,31 @@ What matters for summary generation: - **Customization for different meeting types.** Sales calls need different structures than customer interviews or team standups. Templates should adapt to your use case. - **Speed.** You need summaries within seconds of the meeting ending, not 10 minutes later when you're already in the next call. - **Data control and privacy.** Some organizations can't send conversations to the cloud. Understand where your data goes and who processes it before you commit. -- **Useful AI.** The summary should distill key points, not merely reiterate the transcript in bullet points. +- **Useful AI.** The summary should distill the meeting, not just reiterate the transcript in bullet points. That is the standard we used for the tools below. ## Best AI Meeting Summary Tools in 2026: Detailed Reviews -### 1. Char: AI Notepad for Meetings with Zero Lock-in +### 1. Anarlog: AI Notepad for Meetings with Zero Lock-in -Char is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, BYOK, or run local models. +Anarlog is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: bring your own keys or run local models. -**Full disclosure:** I'm a co-founder of Char, so I'll keep this as objective as possible. +**Full disclosure:** I'm a co-founder of Anarlog, so I'll keep this as objective as possible. ![ai meeting summary platforms with customizable templates](/api/assets/blog/ai-meeting-summary-tools/summary-1.webp) -#### How Char's summary generation works +#### How Anarlog's summary generation works -Char sits quietly on your computer and captures audio directly from your system — no bots joining your calls, no calendar integrations needed. While you're in a meeting, it generates a live transcript as you take your own notes alongside it. +Anarlog sits quietly on your computer and captures audio directly from your system — no bots joining your calls, no calendar integrations needed. While you're in a meeting, it generates a live transcript as you take your own notes alongside it. When the meeting ends, it combines your notes with the transcript and uses AI to produce a structured summary. You choose which AI processes that data — their managed service, your own API keys (OpenAI, Anthropic, etc.), or a fully local model running on your machine via Ollama or LM Studio. #### Summary features -![How Char works](/api/assets/blog/ai-meeting-summary-tools/summary-2.webp) +![How Anarlog works](/api/assets/blog/ai-meeting-summary-tools/summary-2.webp) -- **Instant summary generation** delivers summaries within seconds of the meeting ending. No waiting around. The moment you stop recording, Char processes everything locally and delivers your summary. +- **Instant summary generation** delivers summaries within seconds of the meeting ending. No waiting around. The moment you stop recording, Anarlog processes everything locally and delivers your summary. - **Customizable templates** let you create summary formats for any meeting type, including sales calls, therapy sessions, legal consultations, and team standups. Your summaries adapt to your workflow, not the other way around. - **Custom vocabulary support** lets you add industry-specific terms, company names, or technical jargon to improve transcription and summary accuracy for your specific context. - **AI Chat for follow-up questions** lets you ask specific questions about your meetings. "What did Sarah say about the budget?" or "List all action items from yesterday's calls." @@ -95,7 +92,7 @@ When the meeting ends, it combines your notes with the transcript and uses AI to #### Pricing -Char's core features, including unlimited AI summaries and transcription, are **free forever**. +Anarlog's core features, including unlimited AI summaries and transcription, are **free forever**. Free forever for local transcription, BYOK, and all core features. **Managed cloud ($25/month)** for the easiest setup. @@ -103,7 +100,7 @@ Free forever for local transcription, BYOK, and all core features. **Managed clo ### 2. Read AI: the unified meeting intelligence platform -Read AI is a cloud-based AI meeting assistant that goes beyond individual meetings to provide unified search across meetings, emails, and messages. Think of it as an intelligence layer connecting all your communication platforms. +Read AI is a cloud-based AI meeting assistant with unified search across meetings, emails, and messages — an intelligence layer connecting your communication platforms. ![read ai summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-3.webp) @@ -222,7 +219,7 @@ Paid plans start at **€24/user/month** (approximately $26 USD) for unlimited A ### 5. Fellow: the privacy-focused AI meeting assistant -Fellow is a cloud-based meeting workflow platform that emphasizes security and granular privacy controls, making it popular with organizations that need rigorous data protection. +Fellow is a cloud-based meeting workflow platform built around security and granular privacy controls, popular with organizations that need rigorous data protection. ![Fellow AI meeting summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-6.webp) @@ -230,7 +227,7 @@ Fellow is a cloud-based meeting workflow platform that emphasizes security and g Fellow joins meetings as a bot to record and transcribe conversations. It generates AI summaries that cover key takeaways, action items, and decisions, then links everything directly to your meeting notes and calendar events. -The platform stands out with its focus on internal meetings with recording permissions that give managers fine-grained control over what gets captured. +It focuses on internal meetings, with recording permissions that give managers fine-grained control over what gets captured. #### Summary features @@ -266,7 +263,7 @@ The platform stands out with its focus on internal meetings with recording permi ### 6. Fireflies: the conversation intelligence platform -Fireflies.ai positions itself as more than just a transcription tool. It's a conversation intelligence solution focused on analytics, searchability, and deep insights across all your meetings. +Fireflies.ai is more than a transcription tool. It's a conversation intelligence platform built around analytics, search, and insights across all your meetings. ![fireflies ai meeting summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-7.webp) @@ -274,7 +271,7 @@ Fireflies.ai positions itself as more than just a transcription tool. It's a con Fireflies joins your meetings as a bot participant called "Fred" that records and transcribes conversations. After the meeting, it automatically generates AI summaries and can push them to designated channels like Slack or your CRM. -The platform emphasizes analytics, tracking speaker talk time, sentiment, and conversation themes to give you insights beyond basic summaries. +It tracks speaker talk time, sentiment, and conversation themes for insights beyond a basic summary. #### Summary features @@ -305,7 +302,7 @@ The platform emphasizes analytics, tracking speaker talk time, sentiment, and co ### 7. Otter AI: AI summary tool with real-time collaboration -Otter AI is probably the most well-known AI meeting assistant, offering cloud-based meeting intelligence with real-time collaboration features and established cross-platform support. +Otter AI is probably the most well-known AI meeting assistant. Cloud-based, with real-time collaboration and broad cross-platform support. ![Otter ai meeting summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-8.webp) @@ -345,7 +342,7 @@ The summaries are organized into chapters with timestamps, making it easy to jum ### 8. Avoma: the revenue team meeting assistant -Avoma is an AI-powered meeting platform specifically built for sales and customer success teams. It combines meeting intelligence with revenue operations, automatically extracting insights that matter for deal progression. +Avoma is an AI meeting platform built for sales and customer success teams. It combines meeting intelligence with revenue operations and pulls out the insights that matter for deal progression. ![avoma ai summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-9.webp) @@ -386,7 +383,7 @@ After meetings, summaries sync directly to your CRM with automatic field updates ### 9. MeetGeek: the automated meeting workflow platform -MeetGeek is a cloud-based conversation intelligence platform that goes beyond basic summaries with sophisticated automation, meeting type detection, and team-level analytics. +MeetGeek is a cloud-based conversation intelligence platform with automation, meeting type detection, and team-level analytics on top of basic summaries. ![Meetgeek ai summaries review](/api/assets/blog/ai-meeting-summary-tools/summary-10.webp) @@ -430,12 +427,12 @@ Summaries are delivered immediately after meetings with key topics, decisions, a ## My take -This category is getting crowded, but most products still make the same assumption: your meeting data should live in their cloud and your summary format should adapt to them. +This category is crowded, but most products still make the same assumption: your meeting data lives in their cloud and your summary format adapts to them. If that trade-off works for you, there are decent options here. -If it does not, Char is the one that breaks the pattern. The notes stay on your device, the AI stack stays flexible, and the summary layer is something you control instead of rent. +If it doesn't, Anarlog breaks the pattern. The notes stay on your device, the AI stack stays flexible, and the summary layer is something you control instead of rent. -[Download Char for macOS](/download) if you want meeting summaries without handing over the whole system. +[Download Anarlog for macOS](https://anarlog.so) if you want meeting summaries without handing over the whole system.   diff --git a/apps/web/content/articles/anthropic-data-retention-policy.mdx b/apps/web/content/articles/anthropic-data-retention-policy.mdx index 12a1f7fa38..c807a7b2d5 100644 --- a/apps/web/content/articles/anthropic-data-retention-policy.mdx +++ b/apps/web/content/articles/anthropic-data-retention-policy.mdx @@ -16,7 +16,7 @@ If you're evaluating AI providers right now, this is the context you need. ## What Does Claude Store by Default? -For consumer accounts, that are Claude Free, Pro, and Max, conversations are saved to your account until you delete them. Once deleted, they're removed from your chat history immediately but remain on Anthropic's back-end systems for up to 30 days before being permanently deleted. +For consumer accounts (Claude Free, Pro, and Max), conversations are saved to your account until you delete them. Once deleted, they're removed from your chat history immediately but stay on Anthropic's back-end systems for up to 30 days before being permanently deleted. That's the standard case. A few exceptions matter: @@ -28,15 +28,15 @@ Incognito mode. Conversations in Claude's incognito mode are never used for mode ## The September 2025 Training Policy Change -Anthropic's previous stance was clean: consumer chats would not be used for training. That was the explicit promise. In August 2025, that changed. [According to Anthropic's own announcement](https://www.anthropic.com/news/updates-to-our-consumer-terms), they introduced an opt-in toggle, "You can help improve Claude", and gave users until September 28 to make their choice. +Anthropic's previous stance was clean: consumer chats would not be used for training. That was the explicit promise. In August 2025, that changed. [According to Anthropic's own announcement](https://www.anthropic.com/news/updates-to-our-consumer-terms), they introduced an opt-in toggle, "You can help improve Claude", and gave users until September 28 to make their choice. If you opted in, Anthropic could retain your conversations in de-identified form for up to 5 years and use them for model training. If you opted out, nothing changed. There's still a 30-day retention, no training use. -The reaction was immediate. Security researchers and privacy advocates flagged it as a "privacy pivot." The opt-in training setting extends data retention from 30 days to 5 years, a 60x increase in how long your conversations can sit in Anthropic's training pipeline. +The reaction was immediate. Security researchers and privacy advocates flagged it as a "privacy pivot". The opt-in training setting extends data retention from 30 days to 5 years — a 60x increase in how long your conversations can sit in Anthropic's training pipeline. ## How to Check and Change Your Settings -To see where your account stands: Claude.ai → Settings → Privacy → "Improve Claude for everyone." +To see where your account stands: Claude.ai → Settings → Privacy → "Improve Claude for everyone". If the toggle is on, your new conversations are eligible for training and retained for up to 5 years. Turning it off returns you to the 30-day retention standard. @@ -48,7 +48,7 @@ The consumer product and the API have meaningfully different data policies, and As of September 14, 2025, Anthropic reduced API log retention from 30 days to 7 days. API inputs and outputs are automatically deleted after 7 days. They are never used for model training. -If your organization needs longer retention for auditing purposes, you can opt in to the 30-day window via your Data Processing Addendum. But the default is 7 days, which is stricter than most providers. +If your organization needs longer retention for auditing, you can opt in to the 30-day window via your Data Processing Addendum. The default of 7 days is stricter than most providers. For enterprise API customers who qualify, Anthropic also offers a Zero Data Retention agreement. Under ZDR, inputs and outputs are not stored at all beyond what's needed to screen for abuse. One caveat: Anthropic still retains User Safety classifier results even under ZDR, to enforce their usage policy. @@ -68,26 +68,24 @@ GDPR: Anthropic supports GDPR compliance for commercial customers through a Data In June 2025, [Reddit filed a lawsuit against Anthropic](https://ppc.land/reddit-files-lawsuit-against-anthropic-over-unauthorized-claude-ai-training/) alleging that Anthropic scraped more than 100,000 Reddit posts and comments without authorization to train Claude. Reddit presented evidence that Claude reproduced deleted Reddit posts with near-perfect accuracy. -This isn't about your personal data directly. But it's relevant context for how Anthropic has approached training data acquisition, and it matters when evaluating whether a company's stated privacy values match their actual behavior. +This isn't about your personal data directly. But it's context for how Anthropic has approached training data acquisition, and it matters when evaluating whether a company's stated privacy values match its behavior. -## How Claude.ai Compares to Using Claude Through Char +## How Claude.ai Compares to Using Claude Through Anarlog - -| | | | +| | | | | ---------------------------- | ---------------------------------- | ----------------------------------- | -| | **Claude.ai (Consumer)** | **Anthropic API via Char** | -| **Where notes are stored** | Anthropic's servers | Your device (plain markdown) | -| **Retention after deletion** | Up to 30 days on backend | 7 days (API), then deleted | -| **Used for training** | Yes — unless opted out (Sept 2025) | Never | -| **If opted into training** | Up to 5 years, de-identified | Not applicable | -| **HIPAA BAA available** | No (consumer) | Yes (enterprise API) | -| **Can switch AI providers** | No | Yes — OpenAI, Mistral, local models | - +| | **Claude.ai (Consumer)** | **Anthropic API via Anarlog** | +| **Where notes are stored** | Anthropic's servers | Your device (plain markdown) | +| **Retention after deletion** | Up to 30 days on backend | 7 days (API), then deleted | +| **Used for training** | Yes — unless opted out (Sept 2025) | Never | +| **If opted into training** | Up to 5 years, de-identified | Not applicable | +| **HIPAA BAA available** | No (consumer) | Yes (enterprise API) | +| **Can switch AI providers** | No | Yes — OpenAI, Mistral, local models | -[Char](https://char.com/) is an open-source AI notepad for meetings that lets you bring your own Anthropic API key. When you connect it, your meeting data goes through the API i.e. 7-day retention, never used for training, rather than through the consumer Claude.ai product where training opt-ins and longer retention windows apply. +[Anarlog](https://anarlog.so/) is an open-source AI notepad for meetings that lets you bring your own Anthropic API key. When you connect it, your meeting data goes through the API i.e. 7-day retention, never used for training, rather than through the consumer Claude.ai product where training opt-ins and longer retention windows apply. -And if Anthropic's policy trajectory gives you pause, you're not locked in. Char supports OpenAI, Mistral, Google Gemini, and local models via Ollama. Your notes stay on your device regardless of which AI processes them. Switching providers doesn't mean starting over. +And if Anthropic's policy trajectory gives you pause, you're not locked in. Anarlog supports OpenAI, Mistral, Google Gemini, and local models via Ollama. Your notes stay on your device regardless of which AI processes them. Switching providers doesn't mean starting over. -That's what actual control looks like. It's not just a privacy toggle that defaults to whatever Anthropic decides next quarter. +That's what actual control looks like — not a privacy toggle that defaults to whatever Anthropic decides next quarter. -[Download Char for macOS](https://char.com/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file +[Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file diff --git a/apps/web/content/articles/azure-open-ai-data-retention-policy.mdx b/apps/web/content/articles/azure-open-ai-data-retention-policy.mdx index 55540a2698..f3a247451a 100644 --- a/apps/web/content/articles/azure-open-ai-data-retention-policy.mdx +++ b/apps/web/content/articles/azure-open-ai-data-retention-policy.mdx @@ -10,7 +10,7 @@ date: "2026-03-19" Azure OpenAI runs the same underlying models as the consumer ChatGPT product. GPT-4o, GPT-4 Turbo, o1, and others are all available through both. But the data policy is not the same. -When you access OpenAI's models through Microsoft Azure, your data stays within your Azure tenant, Microsoft does not use it to train any models, and you gain access to enterprise compliance frameworks that the consumer product does not offer. The models are identical. The data handling is not. +When you access OpenAI's models through Microsoft Azure, your data stays within your Azure tenant, Microsoft does not use it to train any models, and you get enterprise compliance frameworks the consumer product does not offer. The models are identical. The data handling is not. Here is what that means in practice. @@ -22,9 +22,9 @@ Your prompts and completions are retained for up to 30 days for abuse monitoring ## Who at Microsoft Can Actually See Your Data? -Microsoft employs automated systems for abuse monitoring. Human reviewers can access flagged content, but only content that has been specifically flagged by the automated system, and only through Secure Access Workstations with Just-In-Time approval from team managers. It is not open-access review. +Microsoft uses automated systems for abuse monitoring. Human reviewers can access flagged content, but only content the automated system has flagged, and only through Secure Access Workstations with Just-In-Time approval from team managers. It is not open-access review. -This is a materially different setup from [Google Gemini's human review policy](https://char.com/blog/google-gemini-data-retention-policy/#human-review-in-plain-terms), where reviewers can access conversations more broadly for quality evaluation. +This is a materially different setup from [Google Gemini's human review policy](https://anarlog.so/blog/google-gemini-data-retention-policy/#human-review-in-plain-terms), where reviewers can access conversations more broadly for quality evaluation. ## Modified Abuse Monitoring and Zero Data Retention @@ -44,9 +44,9 @@ For GDPR, Microsoft offers an EU Data Zone option that processes and stores your ## How Azure OpenAI Differs from OpenAI's API -Both Azure OpenAI and [OpenAI's direct API](https://char.com/blog/chatgpt-data-retention-policy/#the-openai-api-has-meaningfully-better-data-controls) offer enterprise-grade data controls including ZDR. The practical differences come down to infrastructure and compliance coverage. +Both Azure OpenAI and [OpenAI's direct API](https://anarlog.so/blog/chatgpt-data-retention-policy/#the-openai-api-has-meaningfully-better-data-controls) offer enterprise-grade data controls including ZDR. The practical differences come down to infrastructure and compliance coverage. -Azure OpenAI sits inside your existing Azure environment, which means it integrates with your Azure Active Directory, your private virtual networks, your logging infrastructure, and your existing Microsoft compliance agreements. If your organization is already on Azure, adding Azure OpenAI does not introduce a new vendor relationship. +Azure OpenAI sits inside your existing Azure environment. It integrates with your Azure Active Directory, private virtual networks, logging infrastructure, and existing Microsoft compliance agreements. If your organization is already on Azure, adding Azure OpenAI does not introduce a new vendor relationship. ## What This Means for Your Evaluation @@ -54,10 +54,10 @@ Consumer [ChatGPT trains on your conversations by default](/blog/chatgpt-data-re The models are the same. The compliance posture is not. -## Using Azure OpenAI Through Char +## Using Azure OpenAI Through Anarlog -[Char](https://char.com) supports custom API endpoints, which means you can connect your Azure OpenAI deployment directly. Your meeting data goes through your Azure subscription under your enterprise data policy. +[Anarlog](https://anarlog.so) supports custom API endpoints, which means you can connect your Azure OpenAI deployment directly. Your meeting data goes through your Azure subscription under your enterprise data policy. -If your organization has a Modified Abuse Monitoring or ZDR agreement in place, those protections apply to requests routed through Char as well. Your notes are stored on your device regardless of which provider processes them. +If your organization has a Modified Abuse Monitoring or ZDR agreement in place, those protections apply to requests routed through Anarlog as well. Your notes are stored on your device regardless of which provider processes them. -[Download Char for macOS](https://char.com/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file +[Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file diff --git a/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx b/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx index 1186fad2cf..65edc88775 100644 --- a/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx +++ b/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx @@ -2,7 +2,6 @@ meta_title: "7 Best AI Meeting Assistants for Taking Notes in 2026" meta_description: "Discover the best AI meeting assistants for automated note-taking in 2026. Compare privacy, features & pricing to choose the right tool for your needs." author: "Harshika" -coverImage: "/api/assets/blog/best-ai-meeting-assistant-for-taking-notes/cover.png" category: "Comparisons" date: "2025-08-04" --- @@ -20,10 +19,10 @@ The gap between products shows up in the second step. Some tools stop at transcr Core capabilities include: - **Real-time transcription** with speaker identification -- **Automated note-taking** that extracts key points and decisions +- **Automated note-taking** that extracts the points and decisions - **Action item extraction** that identifies tasks and deadlines -- **Intelligent summarization** that distills hours of discussion into digestible insights -- **Searchable archives** that make historical meeting data instantly accessible +- **Summarization** that compresses an hour of discussion into something scannable +- **Searchable archives** for getting back to historical meeting data quickly ## Why This Article Focuses on AI Note-Taking Capabilities @@ -45,29 +44,29 @@ _If you're specifically looking for budget-friendly options, check out our compr ## Our Top Picks for Best AI Note-taking apps for meetings -| Tool | Best For | Meeting Platforms Supported | +| Tool | Best For | Meeting Platforms Supported | | ------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| Char | Zero lock-in, complete control over data and AI | Universal compatibility - works with all platforms without joining as bot, supports offline/in-person | -| Granola | Active note-takers who want AI enhancement | Zoom, Google Meet, Teams, Slack, WebEx, Skype, WhatsApp calls - captures device audio | -| Otter.ai | Transcription accuracy and real-time features | Zoom, Google Meet, Microsoft Teams + phone systems (Dialpad, JustCall, RingCentral, Zoom Phone) | -| Fireflies.ai | Team analytics and conversation intelligence | Zoom, Microsoft Teams, Google Meet, WebEx, GoToMeeting, UberConference, Skype, Lifesize + phone dialers (Aircall, RingCentral) | -| Krisp | Audio quality enhancement with noise cancellation | Universal compatibility (Zoom, Google Meet, Teams) - works at device audio level | -| Jamie AI | Bot-free meeting experience | Universal compatibility - works with all platforms without joining as bot, supports offline/in-person | -| Avoma | Sales teams and revenue intelligence | Zoom, Google Meet, Microsoft Teams, GoToMeeting, BlueJeans | +| Anarlog | Zero lock-in, complete control over data and AI | Universal compatibility - works with all platforms without joining as bot, supports offline/in-person | +| Granola | Active note-takers who want AI enhancement | Zoom, Google Meet, Teams, Slack, WebEx, Skype, WhatsApp calls - captures device audio | +| Otter.ai | Transcription accuracy and real-time features | Zoom, Google Meet, Microsoft Teams + phone systems (Dialpad, JustCall, RingCentral, Zoom Phone) | +| Fireflies.ai | Team analytics and conversation intelligence | Zoom, Microsoft Teams, Google Meet, WebEx, GoToMeeting, UberConference, Skype, Lifesize + phone dialers (Aircall, RingCentral) | +| Krisp | Audio quality enhancement with noise cancellation | Universal compatibility (Zoom, Google Meet, Teams) - works at device audio level | +| Jamie AI | Bot-free meeting experience | Universal compatibility - works with all platforms without joining as bot, supports offline/in-person | +| Avoma | Sales teams and revenue intelligence | Zoom, Google Meet, Microsoft Teams, GoToMeeting, BlueJeans | ## Best AI Meeting Assistants for Taking Notes in 2026: In-depth Review -### 1. Char: Best for Zero Lock-in and Complete Control +### 1. Anarlog: Best for Zero Lock-in and Complete Control -[Char](/) is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. +[Anarlog](/) is an open-source AI notepad for meetings, built for people who want complete control over their data and AI stack. -Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, bring your own keys, or run local models. +Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: bring your own keys or run local models. -Char app mockup +Anarlog app mockup -#### About Char's AI notetaker: +#### About Anarlog's AI notetaker: -Char has an Apple Notes-like interface with powerful AI running locally in the background. When you launch a meeting, the AI assistant transcribes speech in real-time without any bots joining your call. After the meeting ends, it presents a structured summary with key decisions, action items, and discussion themes automatically extracted. +Anarlog has an Apple Notes-like interface with powerful AI running locally in the background. When you launch a meeting, the AI assistant transcribes speech in real-time without any bots joining your call. After the meeting ends, it presents a structured summary with key decisions, action items, and discussion themes automatically extracted. The AI can work completely hands-off, generating organized notes without input. You can also collaborate by jotting down key points, and the AI will expand your rough notes with context you might have missed while actively participating. @@ -93,9 +92,7 @@ The AI can work completely hands-off, generating organized notes without input. #### Pricing: -Char is free forever for local transcription, BYOK, and all core features. The managed cloud service is $25/month for the easiest setup. An Enterprise plan provides on-premises deployment, SSO, custom branding, and admin controls. - - +Anarlog is free forever, fully local, BYOK, and open source. ### 2. Granola AI: Best for Active Note-Takers Who Want AI Enhancement @@ -105,7 +102,7 @@ Char is free forever for local transcription, BYOK, and all core features. The m #### About Granola's note-taking capabilities: -Granola captures audio directly from your device like Char, offering a familiar notepad interface, custom templates, and intelligent note enhancement. However, it sends data to the cloud for AI processing, which can create privacy concerns for organizations with strict compliance requirements. +Granola captures audio directly from your device like Anarlog, offering a familiar notepad interface, custom templates, and intelligent note enhancement. However, it sends data to the cloud for AI processing, which can create privacy concerns for organizations with strict compliance requirements. **Other notable features:** @@ -146,7 +143,7 @@ Also check: [Best Granola AI Alternatives](/blog/granola-ai-alternatives) #### About Otter.ai's note-taking capabilities: -Unlike Char and Granola's bot-free approach, Otter.ai sends an AI assistant (OtterPilot) that automatically joins your Zoom, Google Meet, and Microsoft Teams meetings to capture everything. When meetings end, Otter condenses hour-long discussions into 30-second summaries and automatically emails participants with structured notes. +Unlike Anarlog and Granola's bot-free approach, Otter.ai sends an AI assistant (OtterPilot) that automatically joins your Zoom, Google Meet, and Microsoft Teams meetings to capture everything. When meetings end, Otter condenses hour-long discussions into 30-second summaries and automatically emails participants with structured notes. Other notable features include: @@ -181,13 +178,13 @@ Also check: [Top Otter AI's Alternatives](/blog/otter-ai-alternatives) ### 4. Fireflies: Best for Conversation Analytics and Team Insights -[Fireflies](https://fireflies.ai) is a comprehensive AI meeting assistant that offers deep conversation analytics and team collaboration features beyond basic transcription. With over 16 million users and a recent $1 billion valuation, it positions itself as an enterprise-grade solution. +[Fireflies](https://fireflies.ai) is an AI meeting assistant with conversation analytics and team collaboration features on top of transcription. With over 16 million users and a recent $1 billion valuation, it pitches itself as an enterprise-grade solution. Fireflies #### About Fireflies.ai's note-taking capabilities: -Its AI assistant "Fred" automatically joins your meetings and creates detailed transcripts, then generates structured summaries with action items and decisions automatically extracted. Fireflies excels in conversation analytics by tracking speaker talk time, monitoring sentiment, identifying key topics, and providing team performance insights. +Its AI assistant "Fred" joins your meetings, creates detailed transcripts, then generates summaries with action items and decisions extracted. Fireflies tracks speaker talk time, sentiment, topics, and team performance. **Other notable features:** @@ -219,7 +216,7 @@ Also check: [Top Fireflies AI Alternatives](/blog/fireflies-ai-alternatives) ### 5. Krisp: Best for Audio Quality Plus AI Note-Taking -[Krisp](https://krisp.ai/) is primarily an AI-powered noise cancellation tool that has expanded into meeting note-taking, making it unique among AI assistants. +[Krisp](https://krisp.ai/) started as an AI-powered noise cancellation tool and expanded into meeting note-taking, which makes it unusual in this category. Krisp @@ -298,7 +295,7 @@ Jamie AI offers a generous free plan with all premium features and reasonable us #### About Avoma's AI note-taking capabilities: -Avoma's AI automatically records, transcribes, and analyzes meetings while organizing discussions into "Smart Chapters" that break down conversations by topic. These chapters identify key themes like business needs, pain points, and competitor mentions with timestamps, allowing you to jump directly to specific segments. +Avoma records, transcribes, and analyzes meetings, organizing discussions into "Smart Chapters" by topic. The chapters surface themes like business needs, pain points, and competitor mentions with timestamps, so you can jump directly to specific segments. **Other notable features:** @@ -329,8 +326,7 @@ Most of the tools on this list can save time. That part is no longer rare. The harder decision is whether you want convenience inside someone else's system or notes you actually control. -If you want the second option, Char is the outlier here. Your meetings become files. You choose the AI layer. You can run local, use your own keys, or use the managed cloud without rebuilding your workflow around a proprietary format. +If you want the second option, Anarlog is the outlier here. Your meetings become files. You choose the AI layer. You can run fully local or use your own keys without rebuilding your workflow around a proprietary format. -[**Download Char for macOS**](/download) if that is the kind of setup you want to keep long term. +[**Download Anarlog for macOS**](https://anarlog.so) if that is the kind of setup you want to keep long term. - diff --git a/apps/web/content/articles/best-ai-notetaker-for-in-person-meetings.mdx b/apps/web/content/articles/best-ai-notetaker-for-in-person-meetings.mdx index 6e53a10f56..4b43915e81 100644 --- a/apps/web/content/articles/best-ai-notetaker-for-in-person-meetings.mdx +++ b/apps/web/content/articles/best-ai-notetaker-for-in-person-meetings.mdx @@ -3,7 +3,6 @@ meta_title: "7 Best AI Notetakers for In-person Meetings" meta_description: "Compare the top AI notetakers for in-person meetings. Find tools with mobile apps, offline recording, speaker ID, and real-time transcription features" author: - "Harshika" -coverImage: "/api/assets/blog/best-ai-notetaker-for-in-person-meetings/cover.png" featured: false category: "Comparisons" date: "2025-09-03" @@ -11,21 +10,19 @@ date: "2025-09-03" In-person meetings are where a lot of AI notetakers fall apart. -Most of these products were designed around video calls, calendar links, separate audio channels, and stable internet. Real conversations happen in conference rooms, cafes, hallways, cars, and client offices. The constraints are different. +Most of these products were designed around video calls: calendar links, separate audio channels, stable internet. Real conversations happen in conference rooms, cafes, hallways, cars, and client offices. The constraints are different. We looked for tools that could survive that mess. - -| Tool | Best For | Skip If | +| Tool | Best For | Skip If | | ------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------ | -| Char | Zero lock-in, complete control over data and AI stack | You need a mobile recording (iOS app coming soon) | -| Jamie AI | International teams needing multilingual support with European data compliance | You need mobile recording or real-time transcription | -| Fireflies AI | Teams needing comprehensive conversation analytics with mobile flexibility | You prioritize data privacy or need real-time mobile transcription | -| MeetGeek | Teams requiring comprehensive analytics and workflow automation | You need real-time mobile transcription or have budget constraints | -| Otter AI | Teams needing real-time transcription with strong collaboration features | You handle sensitive information or have privacy concerns | -| Granola AI | Mac/iOS users who prefer manual note-taking with AI enhancement | You need automatic speaker identification or Android support | -| Read AI | Teams seeking detailed meeting analytics and communication coaching | You need Android support or extensive free usage | - +| Anarlog | Zero lock-in, complete control over data and AI stack | You need a mobile recording (iOS app coming soon) | +| Jamie AI | International teams needing multilingual support with European data compliance | You need mobile recording or real-time transcription | +| Fireflies AI | Teams needing comprehensive conversation analytics with mobile flexibility | You prioritize data privacy or need real-time mobile transcription | +| MeetGeek | Teams requiring comprehensive analytics and workflow automation | You need real-time mobile transcription or have budget constraints | +| Otter AI | Teams needing real-time transcription with strong collaboration features | You handle sensitive information or have privacy concerns | +| Granola AI | Mac/iOS users who prefer manual note-taking with AI enhancement | You need automatic speaker identification or Android support | +| Read AI | Teams seeking detailed meeting analytics and communication coaching | You need Android support or extensive free usage | ## What Makes In-Person AI Notetakers Work? @@ -55,15 +52,15 @@ How many languages can it handle accurately? International teams and diverse wor ## In-Person AI Notetakers: Detailed Comparison -### 1. Char +### 1. Anarlog -[Char](/) is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, bring your own keys, or run local models. +[Anarlog](/) is an open-source AI notepad for meetings, built for people who want complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: bring your own keys or run local models. -The interface feels like Apple Notes but with AI superpowers. Start a meeting, and it transcribes in real-time. When you hang up, you get structured summaries with decisions and action items automatically extracted. +The interface feels like Apple Notes with an AI layer. Start a meeting and it transcribes in real-time. When you hang up, you get structured summaries with decisions and action items extracted. Zero lock-in—your files work with any tool (Obsidian, Notion, VS Code) and you can switch AI providers anytime. -![Char for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-1.webp) +![Anarlog for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-1.webp) #### Pros: @@ -85,13 +82,11 @@ Includes customizable templates for different meeting types, and offers conversa #### Pricing -Free forever for local transcription, BYOK, and all core features. Managed cloud service is $25/month for the easiest setup. - -Char also offers an Enterprise plan for organizations that need on-premise deployment, SSO integration, or smart consent management for compliance requirements. [Contact sales for a quote](/founders). +Free forever for local transcription, BYOK, and all core features. ### 2. Jamie AI -[Jamie](https://www.meetjamie.ai) records audio locally on Windows or Mac computers, then uploads recordings to German servers for AI processing. The system automatically deletes audio files after generating transcripts and summaries, emphasizing privacy through data minimization rather than local processing. +[Jamie](https://www.meetjamie.ai) records audio locally on Windows or Mac, then uploads recordings to German servers for AI processing. It deletes audio files after generating transcripts and summaries, betting on data minimization rather than local processing. ![jamie ai for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-2.webp) @@ -119,7 +114,7 @@ Free plan with 10 meetings/month. Paid plans start at €24/month (~$25) with hi ### 3. Fireflies AI -Fireflies uses a bot system for online meetings but shifts to mobile-first recording for in-person conversations. The platform emphasizes team analytics and workflow automation alongside transcription. +Fireflies uses a bot for online meetings but shifts to mobile-first recording for in-person conversations. It pushes team analytics and workflow automation alongside transcription. ![Fireflies ai for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-3.webp) @@ -177,7 +172,7 @@ Basic plan offers 300 free minutes monthly. Pro costs $19 monthly, Business $39 ### 5. Otter AI -Otter specializes in live transcription as meetings occur. However, the platform faces significant privacy concerns, including ongoing federal litigation. +Otter does live transcription as meetings happen. It also faces real privacy concerns, including ongoing federal litigation. ![Otter ai for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-5.webp) @@ -205,7 +200,7 @@ Free plan provides 300 monthly minutes with conversation limits. Paid plans star ### 6. Granola AI -Granola encourages users to take notes manually during meetings, then uses AI to enhance and structure those notes afterward. The platform records locally but processes content in the cloud for intelligent analysis. +Granola encourages you to take notes manually during meetings, then uses AI to clean them up afterward. It records locally but processes content in the cloud. ![granola ai for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-6.webp) @@ -232,7 +227,7 @@ Free trial with up to 25 meetings to test the full feature set. Paid plans start ### 7. Read AI -Read AI focuses on analyzing meeting dynamics and participant engagement alongside transcription. The platform recently introduced mobile capabilities for in-person meeting recording and analysis. +Read AI analyzes meeting dynamics and participant engagement alongside transcription. It recently added mobile capabilities for in-person meeting recording. ![read ai for in person meetings](/api/assets/blog/best-ai-notetaker-for-in-person-meetings/inperson-7.webp) @@ -263,8 +258,8 @@ Free plan includes 5 monthly meeting reports. Paid plans start at $19.75/user/mo If you mostly care about mobile capture, a few cloud products here will do the job. -If you care about ownership, offline use, and not building your workflow around a vendor's app, Char is the better long-term setup. It treats the meeting as yours, not as something that has to pass through a platform first. +If you care about ownership, offline use, and not building your workflow around a vendor's app, Anarlog is the better long-term setup. It treats the meeting as yours, not as something that has to pass through a platform first. -[Download Char for macOS](/download) if that is the direction you want to optimize for. +[Download Anarlog for macOS](https://anarlog.so) if that is the direction you want to optimize for.   diff --git a/apps/web/content/articles/best-ai-notetaker-for-microsoft-teams.mdx b/apps/web/content/articles/best-ai-notetaker-for-microsoft-teams.mdx index 0651ae52f3..1b05cb6554 100644 --- a/apps/web/content/articles/best-ai-notetaker-for-microsoft-teams.mdx +++ b/apps/web/content/articles/best-ai-notetaker-for-microsoft-teams.mdx @@ -3,7 +3,6 @@ meta_title: "7 Best AI Notetakers for Microsoft Teams in 2026" meta_description: "Looking for the best AI notetaker for Microsoft Teams? Compare the top 7 solutions, including native and third-party options." author: - "John Jeong" -coverImage: "/api/assets/blog/best-ai-notetaker-for-microsoft-teams/cover.png" featured: false category: "Comparisons" date: "2025-09-09" @@ -40,7 +39,7 @@ Microsoft doesn't offer a free way to get AI-powered meeting notes, summaries, o ![Microsoft 365 Copilot](/api/assets/blog/best-ai-notetaker-for-microsoft-teams/teams-2.webp) -[Microsoft 365 Copilot](https://support.microsoft.com/en-us/office/use-copilot-in-microsoft-teams-meetings-0bf9dd3c-96f7-44e2-8bb8-790bedf066b1) is an AI assistant that works across Word, Excel, PowerPoint, Outlook, and Teams, powered by large language models. For Teams specifically, it transforms how you handle meeting preparation, participation, and follow-up. +[Microsoft 365 Copilot](https://support.microsoft.com/en-us/office/use-copilot-in-microsoft-teams-meetings-0bf9dd3c-96f7-44e2-8bb8-790bedf066b1) is an AI assistant that works across Word, Excel, PowerPoint, Outlook, and Teams. In Teams, it handles meeting preparation, participation, and follow-up. **Important Note:** Microsoft 365 Copilot is different from the [free Copilot](https://copilot.microsoft.com) available in browsers and Bing. Copilot does not record meetings itself—you need to provide it with a meeting transcription to generate a summary and AI notes. @@ -109,15 +108,15 @@ Teams Premium is priced at $10 per user per month. ## Best Third-Party AI Notetakers for Microsoft Teams -While Microsoft's native solutions work well for many organizations, third-party tools often provide specialized features, better privacy controls, or more competitive pricing. +Microsoft's native options work well for many organizations, but third-party tools often offer specialized features, better privacy controls, or more competitive pricing. -### 1. Char: The Zero Lock-in AI Notepad +### 1. Anarlog: The Zero Lock-in AI Notepad -Char is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. +Anarlog is an open-source AI notepad for meetings, built for people who want complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. -**Full Disclosure:** I'm the co-founder of Char, so I'll try to be as objective as possible in this review. +**Full Disclosure:** I'm the co-founder of Anarlog, so I'll try to be as objective as possible in this review. -**How It Works with Microsoft Teams:** Char doesn't join your Teams meetings as a bot. Instead, it captures both your microphone input and your computer's system audio, so it hears both sides of the conversation. It transcribes your call in real-time. After the meeting ends, you get structured summaries with decisions and action items automatically extracted. No bots to invite, no meeting links to manage. +**How It Works with Microsoft Teams:** Anarlog doesn't join your Teams meetings as a bot. It captures both your microphone input and your computer's system audio, so it hears both sides of the conversation. The call is transcribed in real-time. When the meeting ends, you get structured summaries with decisions and action items extracted. No bots to invite, no meeting links to manage. ![best ai notetaker for microsoft teams](/api/assets/blog/best-ai-notetaker-for-microsoft-teams/teams-4.webp) @@ -147,11 +146,11 @@ Char is an open-source AI notepad for meetings built for high-agency people who #### Pricing -Char is [free forever](/blog/free-ai-notetakers) for local transcription, BYOK, and all core features. The managed cloud service is $25/month for the easiest setup. Enterprise applies when your organization needs on-premises deployment, SSO integration, or consent management for compliance requirements. [Contact sales for a quote](/founders). +Anarlog is [free forever](/blog/free-ai-notetakers) for local transcription, BYOK, and all core features. ### 2. Otter AI: The Established Cloud-Based Solution -[Otter AI](https://otter.ai) is one of the most well-known names in AI transcription, offering cloud-based meeting intelligence that works across Microsoft Teams, [Zoom](/blog/best-ai-notetaker-for-zoom/), and Google Meet. +[Otter AI](https://otter.ai) is one of the most well-known names in AI transcription. It's cloud-based and works across Microsoft Teams, [Zoom](/blog/best-ai-notetaker-for-zoom/), and Google Meet. **How It Works with Microsoft Teams:** Otter joins your Teams meetings as a participant. It records the audio, generates real-time transcripts, and creates meeting summaries automatically. Setup involves account creation, calendar connection, and bot invitation. The bot can be invited manually or set to auto-join meetings based on your calendar integration. Recent privacy concerns indicate that Otter sometimes joins meetings without proper consent. Also check: [Is Otter AI Safe?](/blog/is-otter-ai-safe) @@ -229,7 +228,7 @@ Free plan: 10 meetings/month and 5 AI credits. Paid plans start at $12/user/mont ### 4. Fireflies AI: The Conversation Intelligence Platform -[Fireflies](https://fireflies.ai) positions itself as a full conversation intelligence solution for sales teams and growing businesses. +[Fireflies](https://fireflies.ai) pitches itself as a full conversation intelligence platform for sales teams and growing businesses. **How It Works with Microsoft Teams:** Fireflies joins your Teams meetings as a bot. You can invite it manually to specific meetings or set it to auto-join based on your calendar integration. The bot records, transcribes, and processes the entire conversation, then automatically sends meeting summaries to designated Teams channels. Setup involves creating a Fireflies account, verifying email, connecting your Microsoft calendar, configuring auto-join preferences, setting up Teams channel integrations for receiving summaries, and potentially getting IT approval for the bot. @@ -308,8 +307,8 @@ Free forever plan with unlimited recordings. Pro plans start at $29/month for ad If your company is already deep in Microsoft procurement and wants the most familiar path, start with Copilot. -If you want something that is not trapped inside Teams, start with Char. It works with Teams, but it is not dependent on Teams. That matters if you care about keeping the notes as files, keeping the AI stack flexible, or keeping the same workflow across Zoom, Meet, and in-person conversations. +If you want something that isn't trapped inside Teams, start with Anarlog. It works with Teams but doesn't depend on it. That matters if you care about keeping notes as files, keeping the AI stack flexible, or running the same workflow across Zoom, Meet, and in-person conversations. -You can [download and start using Char](/download) without calendar connections, account setup, or a meeting bot. +You can [download and start using Anarlog](https://anarlog.so) without calendar connections, account setup, or a meeting bot.   diff --git a/apps/web/content/articles/best-ai-notetaker-for-zoom.mdx b/apps/web/content/articles/best-ai-notetaker-for-zoom.mdx index 6e01360d14..13024fed9c 100644 --- a/apps/web/content/articles/best-ai-notetaker-for-zoom.mdx +++ b/apps/web/content/articles/best-ai-notetaker-for-zoom.mdx @@ -2,7 +2,6 @@ meta_title: "Best AI Notetaker for Zoom: Top 10 Options" meta_description: "Find the best AI notetaker for Zoom with our 2026 guide. Compare real-time transcription, privacy options, CRM integrations, and pricing plans." author: "Harshika" -coverImage: "/api/assets/blog/best-ai-notetaker-for-zoom/cover.png" category: "Comparisons" date: "2025-09-06" --- @@ -13,7 +12,7 @@ The real test is not whether a tool can join a call. It is whether it still feel These are the tools that held up best when we evaluated them through that lens. -- **Char** → Zero lock-in, complete control over data and AI stack +- **Anarlog** → Zero lock-in, complete control over data and AI stack - **Zoom AI Companion** → Teams already invested in Zoom's ecosystem - **Fathom** → Sales teams wanting instant CRM integration - **Otter AI** → Teams comfortable with established cloud automation @@ -43,17 +42,17 @@ We prioritized: ## What Are the Best AI Notetakers for Zoom? -### 1. Char +### 1. Anarlog **Best for:** High-agency professionals who demand complete control over their data, AI stack, and workflow. Best ai notetaker for zoom -[Char](/) is an open-source AI notepad for meetings built for high-agency people who demand complete control. Everything is stored as plain markdown files on your device—not in proprietary databases. You choose your AI stack: managed cloud, bring your own keys, or run local models. +[Anarlog](/) is an open-source AI notepad for meetings, built for people who want complete control. Everything is stored as plain markdown files on your device—not in proprietary databases. You choose your AI: bring your own keys or run local models. -The interface works like Apple Notes with AI superpowers. Start a meeting and it transcribes in real-time. When you hang up, you get structured summaries with decisions and action items automatically extracted. +The interface works like Apple Notes with an AI layer. Start a meeting and it transcribes in real-time. When you hang up, you get structured summaries with decisions and action items extracted. -The AI can work completely hands-off, generating organized notes without any input from you. Or you can collaborate by jotting down key points, and the AI will expand your rough notes with context you might have missed while actively participating. +The AI can work completely hands-off, generating organized notes without any input from you. Or you can collaborate by jotting down key points, and the AI will expand your rough notes with context you missed while actively participating. #### Key features @@ -71,7 +70,7 @@ The AI can work completely hands-off, generating organized notes without any inp - Zero lock-in—plain markdown files, open source, portable format - [Free forever](/blog/free-ai-notetakers) for local transcription, BYOK, and all core features -- Your choice of AI stack—managed cloud ($25/mo), bring your own keys, or run local +- Your choice of AI stack: bring your own keys or run local. - Works without internet—great for secure environments - True file ownership—works with Obsidian, Notion, VS Code, anything - IT teams can audit the code and approve the AI provider @@ -83,9 +82,7 @@ The AI can work completely hands-off, generating organized notes without any inp #### Pricing -Char is free forever for local transcription, BYOK, and all core features. Managed cloud service is $25/month for the easiest setup. - -Char also offers an Enterprise plan for organizations that need on-premise deployment, SSO integration, or smart consent management for compliance requirements. [Contact sales for a quote](/founders). +Anarlog is free forever, fully local, BYOK, and open source. ### 2. Zoom AI Companion @@ -97,7 +94,7 @@ Char also offers an Enterprise plan for organizations that need on-premise deplo Compared to specialized tools, it's fairly basic. You need a paid Zoom plan to access it. All features are disabled by default, and the core functionality is limited to meeting summaries, smart recording, in-meeting questions, and whiteboard assistance. -Unlike tools like Char that work offline and provide real-time transcription, Zoom AI Companion requires cloud processing and doesn't offer live transcription during meetings. +Unlike tools like Anarlog that work offline and provide real-time transcription, Zoom AI Companion requires cloud processing and doesn't offer live transcription during meetings. Any meaningful customization—like custom dictionaries, meeting templates, or third-party integrations—costs an extra $12/user/month. @@ -135,7 +132,7 @@ Also, check out our detailed [Zoom AI Companion review](/blog/zoom-ai-companion- Fathom for zoom -[Fathom](https://www.fathom.ai) joins your Zoom calls as a bot and emphasizes speed—you get meeting summaries within 30 seconds of hanging up. Its main strength is handling the post-meeting workflow, automatically pushing notes and action items into your CRM without manual work. +[Fathom](https://www.fathom.ai) joins your Zoom calls as a bot and is built around speed — you get meeting summaries within 30 seconds of hanging up. Its main strength is the post-meeting workflow: it pushes notes and action items into your CRM without manual work. During calls, you can click buttons to highlight important moments or mark action items. After the meeting, everything gets organized into a summary you can immediately forward to colleagues or sync with Salesforce. @@ -173,9 +170,9 @@ Free plan includes unlimited recordings and basic summaries. Premium costs $19/m [Otter AI](https://otter.ai) is probably the most well-known AI meeting assistant—though not always for the right reasons, which we'll address. -Otter pioneered many features that are now standard in the industry. It joins your Zoom, Google Meet, and Teams calls as a visible bot and delivers solid real-time transcription with speaker identification. You can assign action items directly in the transcript, and everyone gets access to searchable meeting notes immediately after calls end. +Otter pioneered features that are now standard in the category. It joins your Zoom, Google Meet, and Teams calls as a visible bot and delivers solid real-time transcription with speaker identification. You can assign action items directly in the transcript, and everyone gets access to searchable meeting notes immediately after calls end. -However, Otter has developed a reputation for privacy issues that have made headlines. Users have reported the bot joining meetings without proper consent and spamming all participants with meeting notes. An [ongoing class-action lawsuit](/blog/is-otter-ai-safe) addresses these privacy practices. +Otter has also developed a reputation for privacy issues. Users have reported the bot joining meetings without proper consent and spamming all participants with meeting notes. An [ongoing class-action lawsuit](/blog/is-otter-ai-safe) addresses these practices. #### Key features @@ -203,8 +200,6 @@ However, Otter has developed a reputation for privacy issues that have made head Free plan with 300 monthly minutes. Paid plans start at $16.99/user/month. - - ### 5. tl;dv **Best for:** Sales teams and customer-facing roles who need detailed video analysis and CRM automation. @@ -213,7 +208,7 @@ Free plan with 300 monthly minutes. Paid plans start at $16.99/user/month. [tl;dv](https://tldv.io) is a video-focused meeting assistant that records your Zoom, Google Meet, and Teams meetings and creates shareable video clips from longer recordings. -What makes tl;dv different is its emphasis on post-meeting workflow automation. It can automatically update your CRM with call details, draft follow-up emails based on conversation content, and generate aggregated insights across multiple meetings. For sales teams, it tracks playbook adherence and objection handling to improve performance. +What sets tl;dv apart is its post-meeting workflow automation. It updates your CRM with call details, drafts follow-up emails based on conversation content, and generates aggregated insights across multiple meetings. For sales teams, it tracks playbook adherence and objection handling. #### Key features @@ -249,7 +244,7 @@ Free forever plan with unlimited recordings. Pro plans start at $29/month for ad [Fireflies.ai](https://fireflies.ai) works similarly to Otter AI—its AI assistant "Fred" automatically joins your meetings and creates detailed transcripts. When meetings end, it generates structured summaries with clear action items, decisions, and next steps automatically extracted. -Fireflies stands out for multilingual support across 100+ languages and conversation analytics. It tracks speaker talk time, monitors sentiment, identifies key topics, and provides team performance insights to optimize meeting effectiveness. +Fireflies stands out for multilingual support across 100+ languages and its conversation analytics. It tracks speaker talk time, sentiment, topics, and team performance. #### Key features @@ -284,7 +279,7 @@ Free plan with 800 minutes of storage and limited AI credits. Paid plans start a meetgeek for zoom -[MeetGeek](https://meetgeek.ai/?r=0) automatically joins your meetings, records them, and syncs content directly into tools like Slack, HubSpot, or project management apps. The focus is reducing the manual work that happens after meetings—no more copying notes between systems or forgetting to update your CRM. +[MeetGeek](https://meetgeek.ai/?r=0) joins your meetings, records them, and syncs content directly into tools like Slack, HubSpot, or project management apps. The focus is reducing post-meeting busywork — no more copying notes between systems or forgetting to update your CRM. #### Key features @@ -317,7 +312,7 @@ Free plan with 300 minutes monthly. Pro at $19/month for unlimited transcription avoma for zoom -[Avoma](https://www.avoma.com) focuses on sales coaching and deal intelligence. The platform provides AI-powered scorecards for call performance, tracks talk patterns and engagement, and offers deal risk analysis. It can automatically update CRM fields based on conversation content and provides win-loss analysis to improve sales strategies. +[Avoma](https://www.avoma.com) focuses on sales coaching and deal intelligence. It provides AI scorecards for call performance, tracks talk patterns and engagement, and offers deal risk analysis. It updates CRM fields based on conversation content and runs win-loss analysis to inform sales strategy. #### Key features @@ -349,9 +344,9 @@ Offers a 14-day free trial, with paid plans starting at $29/month per recorder. tactiq for zoom -Unlike the standalone apps we've covered so far, [Tactiq](https://tactiq.io) works as a Chrome extension. This means no separate software to install—it just lives in your browser and captures audio directly from Google Meet, Zoom, and Teams tabs. +Unlike the standalone apps above, [Tactiq](https://tactiq.io) works as a Chrome extension. No separate software to install — it lives in your browser and captures audio directly from Google Meet, Zoom, and Teams tabs. -The integration ecosystem is where Tactiq shines. It pushes meeting insights to Slack, HubSpot, Jira, and everywhere you actually work. You can turn frequent AI prompts into one-click actions, so you don't have to start from scratch every time you need follow-up tasks. +The integration ecosystem is where Tactiq shines. It pushes meeting insights to Slack, HubSpot, Jira, and everywhere you actually work. You can turn frequent AI prompts into one-click actions instead of starting from scratch every time. #### Key features @@ -369,7 +364,7 @@ The integration ecosystem is where Tactiq shines. It pushes meeting insights to #### Considerations -- Chrome-only limits flexibility compared to universal tools like Char +- Chrome-only limits flexibility compared to universal tools like Anarlog - No audio or video capture capabilities for teams needing meeting recordings - Free plan restrictions with only 5 AI credits per month @@ -383,9 +378,9 @@ Free plan with 10 meetings/month and 5 AI credits. Paid plans start at $12/user/ krisp ai for zoom -[Krisp](https://krisp.ai) cancels background noise AND takes meeting notes. The platform removes background noises, voices, and echoes from both sides of calls while transcribing meetings in real-time across all communication apps. +[Krisp](https://krisp.ai) cancels background noise and takes meeting notes. It removes noise, voices, and echoes from both sides of calls while transcribing in real-time across communication apps. -The AI meeting notes are more basic than enhanced summaries from other tools, but they capture the essentials without fuss. +The AI notes are more basic than what other tools produce, but they capture the essentials. #### Key features @@ -414,8 +409,7 @@ Offers a free plan with unlimited transcripts and 2 daily meeting notes. Paid pl If you already live entirely inside Zoom and want the path of least resistance, Zoom AI Companion may be enough. -If you want notes you can keep, move, audit, and run outside Zoom's ecosystem, the list gets much shorter. That is why Char is the one I would start with. The notes stay as files, the AI layer stays flexible, and the workflow does not collapse if you change providers later. +If you want notes you can keep, move, audit, and run outside Zoom's ecosystem, the list gets much shorter. That's why Anarlog is the one I'd start with. The notes stay as files, the AI layer stays flexible, and the workflow doesn't collapse if you change providers later. -[Download Char for macOS](/download) if that is the setup you want to own. +[Download Anarlog for macOS](https://anarlog.so) if that is the setup you want to own. - diff --git a/apps/web/content/articles/best-ai-notetaker.mdx b/apps/web/content/articles/best-ai-notetaker.mdx index 79cf491f64..4ef61a80bf 100644 --- a/apps/web/content/articles/best-ai-notetaker.mdx +++ b/apps/web/content/articles/best-ai-notetaker.mdx @@ -1,7 +1,7 @@ --- meta_title: "What Are the Best AI Note-Taker Apps in 2026?" display_title: "What Are the Best AI Note-Taker Apps in 2026?" -meta_description: "Overwhelmed by messy notes? I tested the top AI note takers in 2026 so you don't have to. From Char to Obsidian, find the best tool for your workflow here." +meta_description: "Overwhelmed by messy notes? I tested the top AI note takers in 2026 so you don't have to. From Anarlog to Obsidian, find the best tool for your workflow here." author: "John Jeong" featured: false category: "Comparisons" @@ -12,30 +12,30 @@ I am a big believer in jotting things down. If I have an idea, I capture it. The problem is I capture everything. At one point, my note-taking app had so many half-baked ideas that nothing made sense anymore. Then AI changed that. -Those random notes could suddenly be summarized, searched, and connected. I could ask questions across everything I'd written. Turn messy thoughts into clear insights. Find an idea from three months ago in seconds. It's made a real difference. +Those random notes could suddenly be summarized, searched, and connected. I could ask questions across everything I'd written. Find an idea from three months ago in seconds. It's made a real difference. -But AI note-taking apps aren't all built the same. +But AI note-taking apps aren't built the same. -I've spent considerable time testing these tools. I even built one myself. If you're looking for an AI note-taking app, I've done the research. This guide breaks down the popular options so you can pick the right one. +I've spent a lot of time testing these tools. I even built one myself. This guide breaks down the popular options so you can pick the right one. ## What Are the Best AI Note Taker Apps? A Quick Comparison -| Tool | Platform | Best For | Pricing | +| Tool | Platform | Best For | Pricing | | ------------- | ----------------------------------- | ------------------------------------- | ------------------------------------------ | -| Char | macOS | Zero lock-in, complete control | Free forever (local + BYOK). Cloud $25/mo | -| Obsidian + AI | Windows, macOS, Linux, iOS, Android | Knowledge management | Free (Sync $5/mo, Publish $10/mo) | -| Granola | macOS, iPhone | Active note takers during meetings | Free trial, then $14/mo | -| Notion | Web, Windows, macOS, iOS, Android | Teams already using Notion | Free (Business $24/mo for AI) | -| Fathom | Web, Chrome | Sales teams needing CRM integration | Free (Business $28/mo) | -| Reflect | Web, iOS | Daily notes with networked thinking | $10/mo ($120/year only) | -| Otter AI | Web, iOS, Android | Teams needing a feature-rich solution | Free 300 min (Pro $17/mo, Business $30/mo) | -| Mem.ai | Mac, Windows, iOS, web | Self-organizing notes with AI | Free 25 notes (Pro $12/mo) | +| Anarlog | macOS | Zero lock-in, complete control | Free forever, fully local + BYOK. | +| Obsidian + AI | Windows, macOS, Linux, iOS, Android | Knowledge management | Free (Sync $5/mo, Publish $10/mo) | +| Granola | macOS, iPhone | Active note takers during meetings | Free trial, then $14/mo | +| Notion | Web, Windows, macOS, iOS, Android | Teams already using Notion | Free (Business $24/mo for AI) | +| Fathom | Web, Chrome | Sales teams needing CRM integration | Free (Business $28/mo) | +| Reflect | Web, iOS | Daily notes with networked thinking | $10/mo ($120/year only) | +| Otter AI | Web, iOS, Android | Teams needing a feature-rich solution | Free 300 min (Pro $17/mo, Business $30/mo) | +| Mem.ai | Mac, Windows, iOS, web | Self-organizing notes with AI | Free 25 notes (Pro $12/mo) | ## What Is an AI Note Taker? -An AI note taker uses artificial intelligence to help you capture, organize, and make sense of information. It applies large language models to turn raw input—typed notes, voice recordings, or transcribed conversations—into something useful. +An AI note taker uses AI to help you capture, organize, and make sense of information. It applies large language models to turn raw input—typed notes, voice recordings, or transcribed conversations—into something useful. -The best ones do more than transcribe or summarize. They let you search across everything you've captured, connect related ideas, answer questions about your notes, and surface insights you'd find difficult to locate manually. The AI extends your note-taking workflow rather than replacing it. +The good ones do more than transcribe or summarize. They let you search across everything you've captured, connect related ideas, answer questions about your notes, and surface things you'd struggle to find manually. The AI extends your workflow instead of replacing it. ## Types of AI Note Takers @@ -43,25 +43,25 @@ The best ones do more than transcribe or summarize. They let you search across e These tools target calls and meetings. They use speech-to-text models like Whisper to transcribe conversations in real-time, then apply AI to generate summaries, action items, and key takeaways. -Some join meetings as bots (Otter), while others record your system audio directly (Char, Granola, Fathom). The output includes transcripts, summaries, and searchable recordings of what was discussed. +Some join meetings as bots (Otter), while others record your system audio directly (Anarlog, Granola, Fathom). The output includes transcripts, summaries, and searchable recordings of what was discussed. ### PKM Note-Taking Apps with AI -Notion and Obsidian were built for personal knowledge management, not meetings—a place to externalize your thinking. They've added AI features to help you write, organize, search, and connect ideas across your notes. +Notion and Obsidian were built for personal knowledge management, not meetings—a place to externalize your thinking. They've bolted on AI features to help you write, organize, search, and connect ideas across your notes. -The AI assists with drafting, summarizing long documents, answering questions about your knowledge base, and surfacing related notes. These tools work with whatever you type, not just meeting audio. +The AI helps with drafting, summarizing long documents, answering questions about your knowledge base, and surfacing related notes. These tools work with whatever you type, not just meeting audio. ## Reviews of the Best AI Note Taker Apps -### 1. Char - Best Free AI Note-Taking App +### 1. Anarlog - Best Free AI Note-Taking App ![](/api/assets/blog/articles/best-ai-notetaker/1768756074373-image-1.png) -I'm Char's founder. I'm putting my tool first, though I'll explain why rather than asking you to trust me. +I'm Anarlog's founder. I'm putting my tool first, though I'll explain why rather than asking you to trust me. -Every AI note-taking tool I tested before building [Char](https://char.com/) forced the same choice: use their cloud, their models, their rules. My data went where they decided, stored how they decided. No way to switch, no way out. +Every AI note-taking tool I tested before building [Anarlog](https://anarlog.so/) forced the same choice: use their cloud, their models, their rules. My data went where they decided, stored how they decided. No way to switch, no way out. -Char exists because of that. It's an open-source AI notepad for meetings that stores everything as plain markdown files on your device. Zero lock-in. You choose your AI stack: managed cloud, bring your own API keys, or run local models. No bots. No vendor dependency. +Anarlog exists because of that. It's an open-source AI notepad for meetings that stores everything as plain markdown files on your device. Zero lock-in. You choose your AI: bring your own API keys or run local models. No bots. No vendor dependency. Want better AI? Plug in your own OpenAI, Mistral, or any endpoint you prefer. Your files, your AI, your workflow. @@ -96,7 +96,7 @@ This works best for engineers and developers who want files over apps, companies #### Pricing: -Char is free forever for local transcription, BYOK, and all core features. Managed cloud service is $25/month for the easiest setup without managing API keys. +Anarlog is free forever, fully local, BYOK, and open source. ### 2. Obsidian with AI Plugins - Best for Knowledge Management @@ -145,7 +145,7 @@ Free core app with optional paid services (Sync $5/month, Publish $10/month) [Granola](https://www.google.com/url?q=https://www.granola.ai/&sa=D&source=editors&ust=1768759307559871&usg=AOvVaw3Ys71lW9lb82dHCgYguOEj) is an AI-powered notepad for people who want to take their own notes while getting intelligent assistance to make them comprehensive. -Like Char, it captures audio directly from your device. Unlike Char, it sends your data to the cloud for AI processing, which creates privacy concerns for organizations with strict compliance requirements. +Like Anarlog, it captures audio directly from your device. Unlike Anarlog, it sends your data to the cloud for AI processing, which creates privacy concerns for organizations with strict compliance requirements. #### Key Features: @@ -248,9 +248,9 @@ A generous free tier includes basic CRM sync. Team $18/month/user (minimum 2 use ![](/api/assets/blog/articles/best-ai-notetaker/1768756076581-image-6.png) -[Reflect](https://www.google.com/url?q=https://reflect.app/&sa=D&source=editors&ust=1768759307567541&usg=AOvVaw1PCJnKZNiP0tu7xN0mhzXX) is a daily notes app with backlinks and AI. You get a new note each day. You write. It links. The AI (GPT-4 and Whisper) can transcribe voice notes, generate summaries, answer questions about your notes. Google Calendar integration puts your meetings in the sidebar, ready for notes. +[Reflect](https://www.google.com/url?q=https://reflect.app/&sa=D&source=editors&ust=1768759307567541&usg=AOvVaw1PCJnKZNiP0tu7xN0mhzXX) is a daily notes app with backlinks and AI. You get a new note each day. You write. It links. The AI (GPT-4 and Whisper) transcribes voice notes, generates summaries, and answers questions about your notes. Google Calendar integration puts your meetings in the sidebar, ready for notes. -It's built for people who think in connections rather than folders. Everything is chronological by default. Backlinks surface automatically. End-to-end encryption means only you read your notes. Fast, minimal, opinionated—there's essentially one way to use it. +It's built for people who think in connections rather than folders. Everything is chronological by default. Backlinks surface automatically. End-to-end encryption means only you read your notes. Fast, minimal, opinionated — there's basically one way to use it. #### Key Features: @@ -285,9 +285,9 @@ $10/month (must pay $120 annually). 14-day free trial. ![](/api/assets/blog/articles/best-ai-notetaker/1768756076836-image-7.png) -[Otter](https://www.google.com/url?q=https://otter.ai/&sa=D&source=editors&ust=1768759307570156&usg=AOvVaw1ShKuIeVNtQTONf80caVdW) has been in the AI note-taking space longer than most competitors. The product is feature-rich with extensive integrations (Salesforce, HubSpot, Slack, Notion) and solid team collaboration tools. The bot joins your meetings, transcribes, learns speaker voices, captures slides automatically. AI chat lets you query transcripts. +[Otter](https://www.google.com/url?q=https://otter.ai/&sa=D&source=editors&ust=1768759307570156&usg=AOvVaw1ShKuIeVNtQTONf80caVdW) has been in the AI note-taking space longer than most competitors. The product is feature-rich, with integrations (Salesforce, HubSpot, Slack, Notion) and solid team collaboration tools. The bot joins your meetings, transcribes, learns speaker voices, captures slides automatically. AI chat lets you query transcripts. -However, there are significant concerns. A [federal lawsuit](https://char.com/blog/is-otter-ai-safe) alleges Otter records without proper consent. Users report the bot automatically inviting colleagues and joining meetings uninvited. Transcripts get shared externally. Customer support is slow and account cancellations are difficult. Your data gets shared with third parties for AI training. The free tier is deliberately limited to push users toward paid plans. +There are real concerns though. A [federal lawsuit](https://anarlog.so/blog/is-otter-ai-safe) alleges Otter records without proper consent. Users report the bot automatically inviting colleagues and joining meetings uninvited. Transcripts get shared externally. Customer support is slow and account cancellations are difficult. Your data gets shared with third parties for AI training. The free tier is deliberately limited to push users toward paid plans. If you're comfortable with cloud-only recording, bot-based capture, and the privacy tradeoffs, Otter delivers on features. If data control matters or you work with sensitive information, the problems outweigh the polish. @@ -368,29 +368,29 @@ Offers a free tier, but real use requires Pro at $12/month. ### 1. Can I use AI for note-taking? -Yes. AI note-taking tools transcribe conversations, generate summaries, extract action items, and help you search across everything you've captured. They're particularly useful for meetings, lectures, interviews, or anytime you need to focus on the conversation instead of frantically typing notes. +Yes. AI note-taking tools transcribe conversations, generate summaries, extract action items, and help you search across everything you've captured. They're useful for meetings, lectures, interviews, or anytime you need to focus on the conversation instead of frantically typing notes. -The right tool depends on your needs. If you want zero lock-in and complete control, use Char. If you need CRM integration, use something built for sales (Fathom). If you want AI to organize your messy thoughts, try Mem.ai. +The right tool depends on your needs. If you want zero lock-in and complete control, use Anarlog. If you need CRM integration, use something built for sales (Fathom). If you want AI to organize your messy thoughts, try Mem.ai. ### 2. Can ChatGPT take notes? ChatGPT can help you organize and summarize notes you've already taken, but it can't record or transcribe meetings. You'd need to feed it text manually—paste in a transcript, ask it to summarize or extract action items. -For actual meeting transcription and note-taking, you need dedicated tools like Char, Otter, or Granola that capture audio and transcribe in real-time. Some of these tools use GPT-4 for summarization, but the recording and transcription happens separately. +For actual meeting transcription and note-taking, you need dedicated tools like Anarlog, Otter, or Granola that capture audio and transcribe in real-time. Some of these tools use GPT-4 for summarization, but the recording and transcription happens separately. ### 3. Is AI note-taker legal? -Yes, [AI note-takers are legal](https://char.com/blog/is-ai-notetaking-legal), but recording conversations has legal requirements depending on your location. In the US, some states require "two-party consent" (everyone must agree to be recorded), while others only need "one-party consent" (you can record if you're part of the conversation). +Yes, [AI note-takers are legal](https://anarlog.so/blog/is-ai-notetaking-legal), but recording conversations has legal requirements depending on your location. In the US, some states require "two-party consent" (everyone must agree to be recorded), while others only need "one-party consent" (you can record if you're part of the conversation). -The legal risk comes from recording without consent, not from using AI. Tools like Otter and Fireflies that join meetings as bots make it obvious you're recording. Tools like Char and Granola that capture system audio are invisible—so you need to tell people you're recording. +The legal risk comes from recording without consent, not from using AI. Tools like Otter and Fireflies that join meetings as bots make it obvious you're recording. Tools like Anarlog and Granola that capture system audio are invisible—so you need to tell people you're recording. -For work meetings, check your company's policy. For healthcare, legal, or finance, look for tools that handle consent properly (Char has consent management for enterprise). +For work meetings, check your company's policy. For healthcare, legal, or finance, look for tools that handle consent properly (Anarlog has consent management for enterprise). ### 4. Which AI note-taking app doesn't join video calls? -Char records your microphone and system audio directly on your Mac. No bot, no cloud dependency, everything stored as plain markdown files you own. +Anarlog records your microphone and system audio directly on your Mac. No bot, no cloud dependency, everything stored as plain markdown files you own. -Bot-based tools like Otter, Fireflies, and most meeting-focused AI note-takers join as visible participants. If you want invisible recording, use Char—just make sure you tell people you're recording. +Bot-based tools like Otter, Fireflies, and most meeting-focused AI note-takers join as visible participants. If you want invisible recording, use Anarlog—just make sure you tell people you're recording. ### 5. Can AI be 100% trusted? @@ -400,6 +400,6 @@ That said, AI transcription accuracy is generally 85-95% with good audio quality ### 6. Which AI note taker is secure? -Char gives you complete control because everything is stored as plain markdown files on your device. You choose which AI processes your data—or keep it fully local. IT teams can audit the open-source code, and you can use the AI provider your security team approves. +Anarlog gives you complete control because everything is stored as plain markdown files on your device. You choose which AI processes your data—or keep it fully local. IT teams can audit the open-source code, and you can use the AI provider your security team approves. Avoid tools with known security issues. Otter has a federal lawsuit about unauthorized recording. Fireflies has reports of bots joining meetings after account deletion. If control matters, use an open-source tool with zero lock-in. diff --git a/apps/web/content/articles/best-ai-notetakers-google-meet.mdx b/apps/web/content/articles/best-ai-notetakers-google-meet.mdx index 05101c2f54..f766df81db 100644 --- a/apps/web/content/articles/best-ai-notetakers-google-meet.mdx +++ b/apps/web/content/articles/best-ai-notetakers-google-meet.mdx @@ -2,7 +2,6 @@ meta_title: "7 Best AI Notetakers for Google Meet in 2026" meta_description: "Google's native AI notes falling short? Find the right alternative by comparing the 7 best AI notetakers for Google Meet in 2026. Free & paid options included." author: "John Jeong" -coverImage: "/api/assets/blog/best-ai-notetakers-google-meet/cover.png" featured: false category: "Comparisons" date: "2026-02-11" @@ -16,7 +15,7 @@ That leaves room for specialized tools. We tested the ones that work with Google Here are our top picks: -1. **Char:** Best for people and teams who seek control over data and AI stack. +1. **Anarlog:** Best for people and teams who seek control over data and AI stack. 2. **Fireflies AI:** Best for teams needing comprehensive conversation analytics and multilingual support. 3. **Sembly AI:** Best for teams needing automated deliverable generation and cross-meeting analytics 4. **tl;dv:** Best for sales teams needing video analysis, coaching insights, and CRM automation @@ -28,15 +27,15 @@ Here are our top picks: Each of these tools brings different strengths to meeting capture and analysis. -### 1. Char: complete control over your data and AI stack +### 1. Anarlog: complete control over your data and AI stack -Most transcription software forces you into their cloud, their servers, their rules. **[Char](https://char.com)** is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. +Most transcription software forces you into their cloud, their servers, their rules. **[Anarlog](https://anarlog.so)** is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. You decide if your audio, transcripts, or notes ever leave your device. You pick your preferred STT and LLM provider, which means you can go completely local if you want to. No forced stack. No lock-in. #### How it works with Google Meet -Char doesn't join your Google Meet as a bot. Instead, it captures both your microphone input and your system's audio. During meetings, Char transcribes conversations in real-time. When the meeting ends, it combines any notes you took with your transcripts to create a summary. +Anarlog doesn't join your Google Meet as a bot. Instead, it captures both your microphone input and your system's audio. During meetings, Anarlog transcribes conversations in real-time. When the meeting ends, it combines any notes you took with your transcripts to create a summary. ![](/api/assets/blog/articles/best-ai-notetakers-google-meet/image-1-2.png) @@ -65,11 +64,11 @@ Char doesn't join your Google Meet as a bot. Instead, it captures both your micr #### Pricing -Unlimited free plan with local transcription or bring-your-own-key. Pro is $25/month for managed cloud service. +Free forever, fully local + BYOK, open source. ### 2. Fireflies AI: the conversation intelligence platform -**[Fireflies](http://fireflies.ai/)** positions itself as more than just a transcription tool—it's a full conversation intelligence solution for sales teams and growing businesses. +**[Fireflies](http://fireflies.ai/)** is more than a transcription tool — it's a full conversation intelligence platform for sales teams and growing businesses. #### How it works with Google Meet @@ -104,7 +103,7 @@ Free forever with unlimited transcription but limited AI features. Paid plans st ### 3. Sembly AI: the workflow integration specialist -**[Sembly](https://www.sembly.ai/)** transforms meetings into actionable business deliverables like project plans, requirements documents, and reports. Beyond transcription, it analyzes patterns across multiple meetings, automatically generates artifacts tailored to specific roles, and provides enterprise-grade analytics for team productivity. +**[Sembly](https://www.sembly.ai/)** turns meetings into business deliverables like project plans, requirements documents, and reports. It analyzes patterns across multiple meetings, generates artifacts tailored to specific roles, and provides enterprise-grade analytics for team productivity. #### How it works with Google Meet @@ -129,7 +128,7 @@ You can invite a Sembly agent to your meeting from the Google Chrome Extension o #### Considerations - Bot presence required, appearing as an additional participant that introduces itself in meetings -- Steeper learning curve compared to more straightforward tools like Char +- Steeper learning curve compared to more straightforward tools like Anarlog - Limited export options lacking direct Word document export for summaries and tasks #### Pricing @@ -138,7 +137,7 @@ Free trial available, with paid plans starting at $15/user/month. ### 4. tl;dv: the video-first meeting assistant -**[tl;dv](https://tldv.io/)** turns meeting notes into clickable timestamps, making it easy to navigate directly to specific moments in recordings while providing advanced sales coaching features. +**[tl;dv](https://tldv.io/)** turns meeting notes into clickable timestamps, so you can jump directly to specific moments in recordings. It also has sales coaching features built in. #### How it works with Google Meet @@ -173,7 +172,7 @@ Free forever plan with unlimited recordings. Pro plans start at $29/month for ad ### 5. Tactiq: the Chrome extension solution -**[Tactiq](https://tactiq.io/)** works as a Chrome extension rather than a standalone app. It provides live transcription directly in your browser during Google Meet, with powerful workflow automation capabilities. +**[Tactiq](https://tactiq.io/)** works as a Chrome extension rather than a standalone app. It runs live transcription directly in your browser during Google Meet, with workflow automation built in. #### How it works with Google Meet @@ -208,7 +207,7 @@ Free plan with 10 meetings/month and 5 AI credits. Paid plans start at $12/user/ ### 6. Krisp: noise cancellation plus AI notes -**[Krisp](https://krisp.ai/)** combines background noise cancellation with meeting transcription. The platform removes background noises, voices, and echoes from both sides of calls while transcribing meetings in real-time. +**[Krisp](https://krisp.ai/)** combines background noise cancellation with meeting transcription. It strips noise, voices, and echoes from both sides of a call while transcribing in real-time. #### How it works with Google Meet @@ -244,7 +243,7 @@ Free plan with unlimited transcripts and 2 daily meeting notes. Paid plans start ### 7. Otter AI: the established cloud solution -**[Otter AI](https://otter.ai/)** is a well-known AI meeting assistant offering cloud-based meeting intelligence that works across Google Meet, [Zoom](/blog/best-ai-notetaker-for-zoom/), and Microsoft Teams with established real-time collaboration features. +**[Otter AI](https://otter.ai/)** is a well-known AI meeting assistant that runs in the cloud and works across Google Meet, [Zoom](/blog/best-ai-notetaker-for-zoom/), and Microsoft Teams, with real-time collaboration built in. #### How it works with Google Meet @@ -271,7 +270,7 @@ Otter joins your Google Meet as a visible bot participant called "OtterPilot". I #### Considerations - Limited language support restricted to English, Spanish, and French only -- Privacy concerns with an ongoing **[class-action lawsuit](https://char.com/blog/is-otter-ai-safe)** regarding data handling practices +- Privacy concerns with an ongoing **[class-action lawsuit](https://anarlog.so/blog/is-otter-ai-safe)** regarding data handling practices - Free plan restrictions limited to 300 monthly minutes, which gets consumed quickly - Bot presence in meetings can feel intrusive for sensitive client conversations @@ -283,7 +282,7 @@ Free plan with 300 monthly minutes. Paid plans start at $16.99/user/month. If all you want is a lightweight Google-native recap and your Workspace plan includes it, Gemini notes may be enough. -If you want something you can keep using outside Google's stack, with real control over where the data goes, start with Char. You can try it, keep the notes as files, and walk away later without rebuilding your system around one vendor. +If you want something you can keep using outside Google's stack, with real control over where the data goes, start with Anarlog. You can try it, keep the notes as files, and walk away later without rebuilding your system around one vendor. ## Frequently asked questions @@ -308,8 +307,8 @@ If you have access: ### 4. Is there a botless AI notetaker? -Yes. Char and Tactiq both work without sending a bot into your meetings. +Yes. Anarlog and Tactiq both work without sending a bot into your meetings. ### 5. What is the most secure AI note taker for Google Meet? -For maximum security, run Char with fully local AI models (Ollama/LM Studio)—zero data leaves your device. For organizations that need cloud AI, bring your own approved API keys from providers your security team has already vetted. +For maximum security, run Anarlog with fully local AI models (Ollama/LM Studio)—zero data leaves your device. For organizations that need cloud AI, bring your own approved API keys from providers your security team has already vetted. diff --git a/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx b/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx index ef54a08df9..ec9ea353a8 100644 --- a/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx +++ b/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx @@ -2,18 +2,17 @@ meta_title: "2026's Best Bot-Free AI Meeting Assistants" meta_description: "Want AI meeting notes without bots joining your call? Here's a list of the best bot-free AI meeting assistants for secure, distraction-free note-taking." author: "John Jeong" -coverImage: "/api/assets/blog/bot-free-ai-meeting-assistants/cover.png" category: "Comparisons" date: "2025-08-27" --- -I'm tired of AI bots crashing my meetings uninvited. You know the drill - you're in a sensitive client call, and suddenly "OtterPilot has joined the meeting." Awkward silence. "Who invited the bot?" +I'm tired of AI bots crashing my meetings uninvited. You're in a sensitive client call, and suddenly "OtterPilot has joined the meeting" pops up. Awkward silence. "Who invited the bot?" -That's why I've been testing bot-free alternatives that capture your meetings without sending virtual party crashers. These tools record directly from your device or browser, so your calls stay natural while you still get AI-powered notes. +I've been testing bot-free alternatives that capture meetings without sending a stranger to the call. These tools record from your device or browser, so the call stays natural and you still get AI notes. -According to my research, here are the 12 bot-free meeting assistants worth trying: +Here are the 12 bot-free meeting assistants worth trying: -- **Char** → Local, offline, open-source, max privacy +- **Anarlog** → Local, offline, open-source, max privacy - **Jamie AI** → GDPR-compliant AI with executive recall - **Granola AI** → Hybrid human + AI note expansion - **Tactiq** → Live transcription with deep integrations @@ -37,19 +36,19 @@ According to my research, here are the 12 bot-free meeting assistants worth tryi ## What are the best bot-free meeting assistants? -### 1. Char +### 1. Anarlog -[Char](/) is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, bring your own keys, or run local models. +[Anarlog](/) is an open-source AI notepad for meetings that gives you control over your data and AI stack. Everything is stored as plain markdown files, not in a proprietary database. You pick the AI: bring your own keys or run local models. -The interface feels like Apple Notes but with AI superpowers running in the background. Start a meeting and it transcribes in real-time. When you hang up, you get structured summaries with decisions and action items automatically extracted. +The interface feels like Apple Notes with AI running in the background. Start a meeting and it transcribes in real-time. When you hang up, you get a structured summary with decisions and action items extracted. -Char's Smart Consent Management Package for enterprises stands out. It handles consent legally and transparently with multiple options: simple pop-ups, voice recognition that listens for verbal consent, and customizable policies. +Anarlog's Smart Consent Management Package handles consent legally and transparently: pop-ups, voice recognition that listens for verbal consent, and customizable policies. **Pricing:** Free forever for local transcription, BYOK, and all core features. Managed cloud $25/month. Enterprise with custom pricing for consent management and on-premises deployment. -[Download Char for MacOS](/download). +[Download Anarlog for MacOS](https://anarlog.so). -Char +Anarlog #### Top features @@ -68,7 +67,7 @@ Char's Smart Consent Management Package for enterprises stands out. It handles c - Are an engineer or developer who wants files over apps - Work in healthcare, legal, or finance where compliance matters -- Want zero lock-in with open-source transparency ([GitHub](https://github.com/fastrepl/char)) +- Want zero lock-in with open-source transparency ([GitHub](https://github.com/fastrepl/anarlog)) - Your company has banned cloud tools like Otter, ChatGPT, or Granola - Already have unused LLM API credits and want to bring your own keys @@ -77,13 +76,11 @@ Char's Smart Consent Management Package for enterprises stands out. It handles c - Mac only right now (Windows and Linux coming in Q2 2026) - No video recording by design for maximum privacy - - ### 2. Jamie AI -[Jamie AI](https://www.meetjamie.ai/) takes privacy seriously but differently than Char. Instead of keeping everything local, they process your audio in Germany under strict GDPR rules, then immediately delete the recordings. It's like having a very discreet German assistant who forgets everything after writing your notes. +[Jamie AI](https://www.meetjamie.ai/) handles privacy differently than Anarlog. Instead of keeping everything local, it processes audio in Germany under GDPR, then deletes the recordings. Discreet German assistant who forgets everything after writing your notes. -What makes Jamie special is the Executive Assistant feature. Hit Ctrl+J and you get an AI sidebar that remembers everything from past meetings. Ask "What did Sarah say about the Q4 budget?" and it knows instantly. +The Executive Assistant feature is the differentiator. Hit Ctrl+J and an AI sidebar pulls context from past meetings. Ask "what did Sarah say about the Q4 budget?" and it answers. **Pricing:** Free plan with 10 meetings/month. Paid plans start at €24/month (~$25) with higher tiers available. @@ -111,7 +108,7 @@ What makes Jamie special is the Executive Assistant feature. Hit Ctrl+J and you ### 3. Granola AI -[Granola](https://www.granola.ai/) operates similarly to Char's note-taking approach but sends your data to the cloud instead of processing it locally. You jot down key points during calls, and after the meeting ends, Granola expands your rough notes into actionable summaries. +[Granola](https://www.granola.ai/) takes a similar approach to Anarlog but sends your data to the cloud instead of processing it locally. You jot down points during the call, and after the meeting ends, Granola expands your rough notes into a summary. **Pricing:** 25 free meetings (lifetime), then $18/month individual or $14/user/month for teams. @@ -135,13 +132,13 @@ What makes Jamie special is the Executive Assistant feature. Hit Ctrl+J and you - Only 25 free meetings total, then payment required - Limited integrations and search capabilities -- Privacy approach less clear than Char's local processing or Jamie's explicit deletion +- Privacy approach less clear than Anarlog's local processing or Jamie's explicit deletion ### 4. Tactiq -[Tactiq](https://tactiq.io/) takes a completely different approach from the previous tools. Instead of post-meeting summaries, it shows live transcription as people speak. Watching words appear in real-time is satisfying and surprisingly useful for catching names or technical terms you might miss. +[Tactiq](https://tactiq.io/) does something the others don't: live transcription as people speak. Watching words appear in real-time is satisfying and useful for catching names or technical terms you'd otherwise miss. -Tactiq's integration ecosystem is where it really shines. It pushes meeting insights to Slack, HubSpot, Jira—basically everywhere you actually work. +The integration ecosystem is the real draw. Tactiq pushes meeting insights to Slack, HubSpot, and Jira. **Pricing:** Free plan with 10 meetings/month and 5 AI credits. Paid plans start at $12/user/month. @@ -164,16 +161,16 @@ Tactiq's integration ecosystem is where it really shines. It pushes meeting insi #### Things to consider: -- Chrome-only limits flexibility compared to universal tools like Char +- Chrome-only limits flexibility compared to universal tools like Anarlog - [Accuracy with strong accents is less reliable](https://www.g2.com/products/tactiq/reviews/tactiq-review-5307104) than tools like Jamie - Limited to three meeting platforms - No offline functionality ### 5. Krisp -[Krisp](https://krisp.ai/) handles two jobs at once—it cancels background noise AND takes meeting notes. If you're working from cafes or dealing with construction next door, this dual functionality is genuinely valuable. +[Krisp](https://krisp.ai/) does two things at once: cancels background noise and takes meeting notes. If you work from cafes or live next to construction, that combination is useful. -The meeting notes are more basic than the AI-enhanced summaries from Char or Granola, but they capture the essentials without fuss. +The notes are more basic than what Anarlog or Granola produce, but they capture the essentials. **Pricing:** Free plan with 60 minutes/day noise cancellation, and paid plans start at $16/user/month. @@ -202,9 +199,9 @@ The meeting notes are more basic than the AI-enhanced summaries from Char or Gra ### 6. Superpowered -[Superpowered](https://superpowered.me/) focuses specifically on multilingual teams—something most other tools handle as an afterthought. With 50+ languages and automatic language detection, it's built for organizations where English isn't everyone's first language. +[Superpowered](https://superpowered.me/) is built for multilingual teams, which most other tools treat as an afterthought. 50+ languages with automatic detection, aimed at orgs where English isn't everyone's first language. -Like Jamie, it emphasizes privacy through data minimization, automatically deleting audio after generating notes. +Like Jamie, it deletes audio after generating notes. **Pricing:** Offers a limited free plan, with paid plans ranging between $36-$108/month. @@ -225,15 +222,15 @@ Like Jamie, it emphasizes privacy through data minimization, automatically delet #### Things to consider: -- Less feature-rich than comprehensive tools like Char or Jamie +- Less feature-rich than comprehensive tools like Anarlog or Jamie - Desktop dependency limits mobile usage compared to Tactiq's browser approach - Smaller user base means less community support ### 7. Circleback AI -[Circleback AI](https://circleback.ai/) provides both bot-free and bot-based recording options, with a strong focus on CRM integration. While tools like Tactiq integrate broadly across many platforms, Circleback is built specifically for sales teams who need meeting insights to flow directly into their CRM. +[Circleback AI](https://circleback.ai/) offers both bot-free and bot-based recording, with a focus on CRM integration. Tactiq integrates broadly; Circleback is built for sales teams who need meeting insights to flow into their CRM. -The tool captures audio via its desktop app or browser extension and automatically pushes structured data—contact details, deal stages, next steps—into systems like Salesforce and HubSpot. +It captures audio via desktop app or browser extension and pushes structured data — contacts, deal stages, next steps — into Salesforce and HubSpot. **Pricing:** Free trial available, then paid plans start at $25/month. @@ -260,9 +257,9 @@ The tool captures audio via its desktop app or browser extension and automatical ### 8. Notta Chrome Extension -[Notta's](https://www.notta.ai) browser extension brings multilingual capabilities to the bot-free world. While Tactiq supports 60+ languages, Notta focuses specifically on accuracy across different languages and dialects within the same meeting—something international teams really need. +[Notta's](https://www.notta.ai) browser extension brings multilingual capability to bot-free recording. Tactiq supports 60+ languages; Notta focuses on accuracy across multiple languages and dialects within the same meeting. -The extension works similarly to other Chrome-based solutions but with stronger emphasis on translation and cross-language understanding. Think of it as Tactiq's multilingual specialist cousin. +The extension works like other Chrome-based options but emphasizes translation and cross-language understanding. Tactiq's multilingual specialist cousin. **Pricing:** Free plan with 120 minutes/month, Pro from $13.49/month. @@ -290,9 +287,9 @@ The extension works similarly to other Chrome-based solutions but with stronger ### 9. Bluedot -[Bluedot](https://www.bluedothq.com/) is another Chrome extension positioned as the quiet alternative to more visible tools. Like Circleback, it focuses on workflow integration but with broader scope—pushing meeting insights to both CRM systems like Salesforce and productivity tools like Notion. +[Bluedot](https://www.bluedothq.com/) is another Chrome extension, positioned as the quiet alternative to more visible tools. Like Circleback, it focuses on workflow integration, but with broader scope: meeting insights flow to both CRMs like Salesforce and productivity tools like Notion. -Users appreciate that it works without announcing itself, similar to how Granola operates discretely. The template system helps structure different types of meetings automatically. +Users like that it works without announcing itself, similar to Granola. The template system structures different types of meetings automatically. **Pricing:** Very limited free tier (5 meetings total), then Basic at $18/month, Pro at $25/month, Business at $39/month. @@ -319,9 +316,9 @@ Users appreciate that it works without announcing itself, similar to how Granola ### 10. Voicenotes -[Voicenotes](https://voicenotes.com/) takes a different approach from all the meeting-focused tools above. It's designed as a "second brain" that captures both meetings and voice memos in one unified system. Think of it as Jamie's meeting capabilities combined with a personal voice note system. +[Voicenotes](https://voicenotes.com/) takes a different angle than the meeting-focused tools above. It's a "second brain" that captures both meetings and voice memos in one place. Jamie's meeting capability combined with a personal voice note system. -The tool supports an impressive 100+ languages (more than Superpowered's 50+) and lets you query your entire voice history—meetings, personal notes, random thoughts—through AI search. +It supports 100+ languages (more than Superpowered's 50+) and lets you query your full voice history — meetings, personal notes, random thoughts — through AI search. **Pricing:** Offers a free trial, with paid plans starting at $14.99/month. @@ -348,9 +345,9 @@ The tool supports an impressive 100+ languages (more than Superpowered's 50+) an ### 11. Meeting.ai -[Meeting.ai](https://meeting.ai) focuses specifically on compliance-heavy industries where data handling is critical. With 30+ language support and emphasis on regulatory compliance, it's positioned as the enterprise-safe alternative to consumer-focused tools. +[Meeting.ai](https://meeting.ai) is built for compliance-heavy industries where data handling is the whole game. 30+ languages with regulatory compliance baked in, positioned as the enterprise-safe alternative to consumer tools. -The platform is designed for organizations that need meeting intelligence but can't use tools with unclear data policies or overseas processing. It finds middle ground between Char's local processing and cloud-based tools. +It's designed for orgs that need meeting intelligence but can't use tools with unclear data policies or overseas processing. Middle ground between Anarlog's local processing and cloud-based tools. **Pricing:** No free plans. Paid pricing starts at USD 19.99/month. @@ -377,9 +374,9 @@ The platform is designed for organizations that need meeting intelligence but ca ### 12. Sonnet AI -[Sonnet](https://www.sonnetai.com/) is the CRM specialist of bot-free tools, designed specifically for sales teams who live in systems like Salesforce and HubSpot. While Circleback offers CRM integration, Sonnet goes deeper—it's built primarily for automating the entire sales meeting workflow. +[Sonnet](https://www.sonnetai.com/) is the CRM specialist of bot-free tools, built for sales teams who live in Salesforce or HubSpot. Circleback offers CRM integration; Sonnet goes deeper, automating the whole sales meeting workflow. -The tool captures meetings without bots and automatically updates deal stages, contact information, and next steps directly in your CRM. It's like having a dedicated sales assistant who never forgets to log activities. +It captures meetings without bots and updates deal stages, contact info, and next steps directly in your CRM. A dedicated sales assistant who never forgets to log activities. **Pricing:** Free plan with 5 meetings/month and 30-minute limit, Plus at $25/month, Pro at $35/month. @@ -409,13 +406,13 @@ The tool captures meetings without bots and automatically updates deal stages, c ## How to choose the best bot-free meeting assistant -Picking from 12 tools can get overwhelming fast. I've grouped them into clear categories based on what really matters when you're picking an AI meeting assistant. +Picking from 12 tools gets overwhelming fast. I've grouped them by what actually matters when choosing one. ### 1. Privacy & Compliance If you handle sensitive data in healthcare, finance, law, or enterprise: -- **Char** → Local processing, no data leaves your device +- **Anarlog** → Local processing, no data leaves your device - **Jamie** → GDPR-compliant, auto-deletes audio - **Meeting.ai** → Compliance-grade cloud solution @@ -455,14 +452,14 @@ Bot-free tools record directly from your device or browser instead of joining ca Here's a quick look at bot-free vs. bot-based meeting assistants like Otter AI and Fireflies. -| Aspect | Bot-Based Meeting Assistants | Bot-Free Meeting Assistants | +| Aspect | Bot-Based Meeting Assistants | Bot-Free Meeting Assistants | | --------------------- | ------------------------------------------ | -------------------------------------------- | -| Presence in Meeting | Visible participants in attendee list | No participant; works silently in background | -| Meeting Flow Impact | Can interrupt or distract meetings | Keeps meetings smooth and natural | -| Audio Processing | Mostly cloud-based with variable retention | Local processing or strict privacy cloud | -| Technical Integration | Connect via meeting platform APIs as users | Capture audio from device/browser output | -| Privacy & Security | Data retention depends on provider | Designed for strict data control | -| Typical Use Cases | Casual, internal, or less sensitive calls | Sensitive, regulated, client-facing calls | +| Presence in Meeting | Visible participants in attendee list | No participant; works silently in background | +| Meeting Flow Impact | Can interrupt or distract meetings | Keeps meetings smooth and natural | +| Audio Processing | Mostly cloud-based with variable retention | Local processing or strict privacy cloud | +| Technical Integration | Connect via meeting platform APIs as users | Capture audio from device/browser output | +| Privacy & Security | Data retention depends on provider | Designed for strict data control | +| Typical Use Cases | Casual, internal, or less sensitive calls | Sensitive, regulated, client-facing calls | ### 3. How do you take AI meeting notes without bots? @@ -476,7 +473,7 @@ They rely on local apps or browser extensions that listen to the meeting through Consent is handled differently depending on the tool: -- **Char** → Advanced consent management (pop-ups, voice recognition, customizable enterprise policies). +- **Anarlog** → Advanced consent management (pop-ups, voice recognition, customizable enterprise policies). - **Jamie/Superpowered** → Inform participants upfront; audio deleted after processing. - **Others** → Disclosure often relies on manual policies (users informing participants if needed). @@ -491,4 +488,3 @@ People often choose bot-free assistants because: - They need offline or low-bandwidth options that don't depend on cloud connectivity. - They want full control over where and how meeting data is processed and stored. - diff --git a/apps/web/content/articles/building-editorial-workflow-github-slack.mdx b/apps/web/content/articles/building-editorial-workflow-github-slack.mdx index edcba0dcae..a6c31275de 100644 --- a/apps/web/content/articles/building-editorial-workflow-github-slack.mdx +++ b/apps/web/content/articles/building-editorial-workflow-github-slack.mdx @@ -7,13 +7,13 @@ category: "Engineering" date: "2026-01-26" --- -We recently rebuilt our entire editorial workflow from scratch. +We rebuilt our editorial workflow from scratch. -And the question we kept getting was: "Why didn't you just use Payload? Or Sanity? Or any of the existing CMS products?" +The question we kept getting: "Why didn't you just use Payload? Or Sanity? Or any of the existing CMS products?" -The answer goes deeper than "we wanted more control." It connects to something we've been thinking about since we started Char: the belief that content should live as files, not database rows. +The answer goes deeper than "we wanted more control." It connects to something we've been thinking about since we started Anarlog: content should live as files, not database rows. -This is Part 7 of our publishing series, and it's probably the most philosophical one. We'll explain the "why" first, then break down exactly what we built. +This is Part 7 of our publishing series, and it's the most philosophical one. We'll explain the "why" first, then break down what we built. ## The filesystem is the cortex @@ -23,27 +23,27 @@ We wrote about this in [The Filesystem Is the Coretex](/blog/filesystem-is-coret Humans have organized information spatially for centuries. Drawers. Cabinets. Folders. We remember where something is. Location is recall. Structure is memory. -When notes—or blog posts—live as files, they feel like part of you. When they live as opaque blobs behind an API, they feel like someone else's product. +When notes — or blog posts — live as files, they feel like part of you. When they live as opaque blobs behind an API, they feel like someone else's product. -This is a belief about the future: +A belief about the future: > If AI is going to help humans think, it needs access to artifacts humans can own, inspect, move, and understand. Markdown. Plain text. Files. -The filesystem is the cortex—and in the AI era, it becomes even more important, not less. +The filesystem is the cortex, and in the AI era it matters more, not less. ## Why not Payload, Sanity, or other CMS products? -We evaluated them all. Payload is excellent. Sanity is powerful. But every CMS we looked at had the same fundamental issue: they want your content to live in their system. +We evaluated them all. Payload is excellent. Sanity is powerful. But every CMS we looked at had the same problem: they want your content to live in their system. Even the "open source" ones. Even the "self-hosted" ones. Your content becomes structured JSON in a database, mediated through schemas, accessed via APIs. -That's fine for some use cases. For us, it violated the core principle: +Fine for some use cases. For us it violated the core principle: > Content should be files. Files in a repo. Diffable. Portable. Ownable forever. -We already wrote about this in [Why Our CMS Is GitHub](/blog/why-our-cms-is-github). GitHub gives us everything a CMS promises—versioning, collaboration, review workflows, rollback—without the lock-in. +We already wrote about this in [Why Our CMS Is GitHub](/blog/why-our-cms-is-github). GitHub gives us everything a CMS promises — versioning, collaboration, review workflows, rollback — without the lock-in. But there was one gap: editorial workflows for non-engineers. @@ -69,9 +69,9 @@ The system has three layers: ### 1. Draft PRs by default -Every change—new post, edit, unpublish—creates a **draft pull request**. +Every change — new post, edit, unpublish — creates a **draft pull request**. -Draft PRs are GitHub's best-kept secret. They don't notify reviewers. They don't show up in review queues. They signal "I'm still working on this." And they can be converted to "ready for review" with one API call. +Draft PRs are GitHub's best-kept secret. They don't notify reviewers. They don't show up in review queues. They signal "I'm still working on this", and they convert to "ready for review" with one API call. When a writer creates a new post, it lives on a draft PR. They can edit, save, walk away, come back. The PR stays invisible until they're ready. @@ -104,12 +104,12 @@ When a writer clicks "Submit for Review": ``` ┌─────────────────────────────────────────────────────────────┐ -│ Article submitted for review │ -│ @john please review │ -│ │ -│ > "Building an Editorial Workflow with GitHub and Slack" │ -│ │ -│ [ Preview ] [ View PR ] [ Merge ] │ +│ Article submitted for review │ +│ @john please review │ +│ │ +│ > "Building an Editorial Workflow with GitHub and Slack" │ +│ │ +│ [ Preview ] [ View PR ] [ Merge ] │ └─────────────────────────────────────────────────────────────┘ ``` @@ -147,7 +147,7 @@ Just GitHub, Slack, and a few hundred lines of glue code. ## Button states: small details matter -Getting button states right is crucial for clarity: +Button state is where clarity lives or dies: | Scenario | Save | Submit for Review | Status | |----------|------|-------------------|--------| @@ -161,17 +161,17 @@ Writers don't need a "Publish" button. They need to know if something is publish ## Why this matters for open source -We're an open source company. Char is open source. And we believe this editorial workflow should be open source too. +We're an open source company. Anarlog is open source. This editorial workflow should be open source too. -Not just "source available". Actually usable by other teams. +Not "source available". Actually usable by other teams. -The vision: bring your own repository and S3 bucket. Get a complete editorial workflow with draft PRs, auto-save, Slack notifications, and one-click merge. Zero vendor lock-in. Zero data migration. Forever portable. +The vision: bring your own repository and S3 bucket. Get the full editorial workflow — draft PRs, auto-save, Slack notifications, one-click merge. Zero vendor lock-in. Zero data migration. Forever portable. -Imagine content living in your GitHub repo instead of ours. Images in your S3 bucket instead of ours. You get the entire workflow without being locked into our system. +Content lives in your GitHub repo. Images in your S3 bucket. You get the workflow without being locked into our system. -This is the opposite of what CMS products offer. They want you to put content in their system. We want to give you tools that work with the systems you already have. +The opposite of what CMS products offer. They want you to put content in their system. We want to give you tools that work with the systems you already have. -We're not there yet. But the architecture is designed for it. The code is written to be extractable. And we're committed to open sourcing it when it's ready. +We're not there yet. The architecture is designed for it, the code is written to be extractable, and we'll open source it when it's ready. ## What we learned @@ -197,15 +197,15 @@ Every design decision was easier because we started with files. No schema migrat ## The philosophy behind it -We built this because we believe content should be owned, not rented. Your words should live in your repo, not in a SaaS database. +Content should be owned, not rented. Your words should live in your repo, not in a SaaS database. Tools should compose, not capture. GitHub, Slack, and Netlify already exist. We connected them instead of replacing them. -Simplicity survives. Markdown files will outlive every CMS platform launched this decade. +Markdown files will outlive every CMS platform launched this decade. -Open source means open data. If the software is open, the content should be portable. +If the software is open, the content should be portable. -This editorial workflow is one piece of that vision. It's how we publish our blog, our docs, our changelog. And eventually, it's something we want to share with every team that believes content should be files. +This editorial workflow is one piece of that. It's how we publish our blog, our docs, our changelog. And eventually, it's something we want to share with every team that believes content should be files. --- @@ -216,5 +216,5 @@ This editorial workflow is one piece of that vision. It's how we publish our blo 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/publishing-stack) +6. [How We Built Anarlog's Publishing Stack](/blog/publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) (You are here) diff --git a/apps/web/content/articles/can-you-transcribe-meetings-without-sending-data-to-cloud.mdx b/apps/web/content/articles/can-you-transcribe-meetings-without-sending-data-to-cloud.mdx index ee3c29b3c7..15103723a2 100644 --- a/apps/web/content/articles/can-you-transcribe-meetings-without-sending-data-to-cloud.mdx +++ b/apps/web/content/articles/can-you-transcribe-meetings-without-sending-data-to-cloud.mdx @@ -1,45 +1,44 @@ --- meta_title: "Can You Transcribe Meetings Without Sending Data to the Cloud?" -meta_description: "Learn how to transcribe meetings without sending data to the cloud. Discover local AI tools like Char that keep conversations on-device and completely private." +meta_description: "Learn how to transcribe meetings without sending data to the cloud. Discover local AI tools like Anarlog that keep conversations on-device and completely private." author: - "Harshika" -coverImage: "/api/assets/blog/can-you-transcribe-meetings-without-sending-data-to-cloud/cover.png" featured: false category: "Guides" date: "2025-08-22" --- -**Short answer: Absolutely. And with Char, you choose exactly how.** +**Short answer: yes. And with Anarlog, you choose exactly how.** -Most people don't realize that every time you use cloud-based transcription tools, you're uploading your most sensitive conversations to someone else's servers. Your client calls, strategy sessions, and confidential discussions sit in databases you'll never see. Recent privacy concerns with tools like Otter AI show just how risky this can be. +Every time you use a cloud-based transcription tool, you're uploading your most sensitive conversations to someone else's servers. Client calls, strategy sessions, confidential discussions, all sitting in databases you'll never see. Recent privacy concerns with tools like Otter AI show how risky this gets. -But the answer isn't to give up on AI-powered meeting notes. It's to take control of how they work. +The answer isn't to give up on AI-powered meeting notes. It's to control how they work. ## What happens to your data with cloud-based transcription tools? -With cloud-based tools, your sensitive discussions, client information, and strategic planning sessions are processed by third-party vendors with varying privacy standards. +Your sensitive discussions, client info, and strategy sessions get processed by third-party vendors with varying privacy standards. -Your audio and transcripts may be stored on servers across different countries, used for AI training, and subject to the provider's changing privacy policies. +Audio and transcripts can be stored on servers across different countries, used for AI training, and subject to the provider's changing privacy policies. -For organizations in regulated industries, this creates real compliance complexity. You're not just ensuring your own systems meet HIPAA, GDPR, or SOC 2 requirements — you're now responsible for your vendor's compliance across multiple jurisdictions. +For regulated industries, this creates real compliance overhead. You're not just ensuring your own systems meet HIPAA, GDPR, or SOC 2 — you're responsible for your vendor's compliance across multiple jurisdictions. -Giving teams control over their AI stack reduces dependency on cloud vendors, makes audits easier, and puts data handling decisions with the people who understand the risk. +Letting teams control their own AI stack reduces vendor dependency, makes audits easier, and puts data decisions in the hands of the people who understand the risk. -## **Enter Char: the AI notepad built around your choice** +## **Anarlog: the AI notepad built around your choice** -[Char](https://char.com) is an open-source AI notepad for meetings with zero lock-in and complete control over your data and AI stack. +[Anarlog](https://anarlog.so) is an open-source AI notepad for meetings with zero lock-in and full control over your data and AI stack. ### **You control how your data is processed** -Char lets you configure your own stack: +Anarlog lets you configure your own stack: - **Transcription** — run it locally on your device, or use cloud APIs like Deepgram or AssemblyAI -- **AI Summarization** — use your own API keys (OpenAI, Anthropic, and others), run local models via Ollama or LM Studio, or use Char's managed service +- **AI Summarization** — use your own API keys (OpenAI, Anthropic, and others) or run local models via Ollama or LM Studio - **Storage** — everything saves as plain markdown files on your device, not in a proprietary database ### **Universal platform compatibility** -Char captures your microphone input and system audio directly — no bots joining your calls, no calendar permissions required. It works with all meeting platforms (Zoom, Teams, Meet, Slack), in-person meetings, and phone calls routed through your computer, with no platform-specific integrations needed. +Anarlog captures your microphone input and system audio directly — no bots joining your calls, no calendar permissions required. It works with all meeting platforms (Zoom, Teams, Meet, Slack), in-person meetings, and phone calls routed through your computer, with no platform-specific integrations needed. ### **Your files, your format** @@ -47,11 +46,11 @@ Everything saves as plain markdown on your device. Open them in Obsidian, Notion ## **Does more control mean worse performance?** -No. Char delivers real-time transcription, AI summaries with action items and key decisions, custom templates for different meeting types, AI chat to query your transcripts, search across your meeting history, and support for 45+ languages including English, Spanish, French, German, Japanese, Portuguese, Italian, Dutch, Korean, and Chinese. +No. Anarlog delivers real-time transcription, AI summaries with action items and decisions, custom templates for different meeting types, AI chat to query your transcripts, search across meeting history, and support for 45+ languages including English, Spanish, French, German, Japanese, Portuguese, Italian, Dutch, Korean, and Chinese. -## **Char's privacy vs. performance spectrum** +## **Anarlog's privacy vs. performance spectrum** -**Full Local Mode:** Everything runs on your device. Your conversations never touch the internet. Works well for lawyers, doctors, or anyone handling sensitive information. +**Full Local Mode:** Everything runs on your device. Your conversations never touch the internet. For lawyers, doctors, or anyone handling sensitive information. *Uses: Local transcription + Local AI models (Ollama, LM Studio) + Device-only storage* @@ -59,32 +58,32 @@ No. Char delivers real-time transcription, AI summaries with action items and ke *Uses: Local transcription + Cloud LLM (OpenAI, Anthropic, etc.) + Hybrid storage* -**Performance Mode:** Cloud APIs handle both transcription and AI. You get better accuracy and speed, and you still choose which providers handle your data. +**Performance Mode:** Cloud APIs handle both transcription and AI. Better accuracy and speed, and you still pick which providers see your data. *Uses: Cloud STT (Deepgram, AssemblyAI) + Your own Cloud LLM + Hybrid storage* ### **Enterprise options** -Companies can run everything on their own servers — transcription, AI, and storage. No vendor dependency, full compliance control, and your IT and security teams can audit every part of the stack. +Companies can run everything on their own servers: transcription, AI, and storage. No vendor dependency, full compliance control, and IT and security teams can audit every part of the stack. *Uses: On-premise transcription + Company LLM endpoints + Self-hosted storage* -Want help configuring your setup? [Get in touch with our team](https://char.com). +Want help configuring your setup? [Get in touch with our team](https://anarlog.so). ## **Related questions** ### **1. Is there a tool that will listen to meetings via an app and transcribe the notes?** -Yes. Tools like Otter AI and Fireflies send your audio to remote servers for processing. They're convenient, but you don't get a say in how your data is handled. +Yes. Tools like Otter AI and Fireflies send your audio to remote servers for processing. Convenient, but you don't get a say in how your data is handled. -Char lets you decide — local, hybrid, or cloud with your own API keys — with full visibility into what's happening, backed by open-source code you can inspect. +Anarlog lets you decide — local, hybrid, or cloud with your own API keys — with full visibility into what's happening, backed by open-source code you can read. ### **2. Can you transcribe without recording a meeting?** Yes. Transcription converts speech to text in real-time without saving the audio file itself. Recording saves the actual audio for playback later. -Char can produce transcripts and summaries without retaining the original audio. You decide what gets stored and where. +Anarlog can produce transcripts and summaries without retaining the original audio. You decide what gets stored and where. ### **3. Can you use local transcription for languages other than English?** -Yes. Char supports 45+ languages including English, Spanish, French, German, Japanese, Portuguese, Italian, Dutch, Korean, and Chinese. If you want stronger multilingual AI summarization, you can connect your own cloud LLM provider. \ No newline at end of file +Yes. Anarlog supports 45+ languages including English, Spanish, French, German, Japanese, Portuguese, Italian, Dutch, Korean, and Chinese. If you want stronger multilingual AI summarization, you can connect your own cloud LLM provider. \ No newline at end of file diff --git a/apps/web/content/articles/char-is-now-anarlog.mdx b/apps/web/content/articles/char-is-now-anarlog.mdx new file mode 100644 index 0000000000..801b0dcb0f --- /dev/null +++ b/apps/web/content/articles/char-is-now-anarlog.mdx @@ -0,0 +1,71 @@ +--- +meta_title: "Char Is Now Anarlog" +display_title: "Char Is Now Anarlog" +meta_description: "The AI meeting notetaker you've been using is now Anarlog — open source, MIT, free forever, fully local. Char is a different product now: an AI notepad that finishes your todos. Two names because they're two different products." +author: "John Jeong" +featured: true +category: "Founders' notes" +date: "2026-05-03" +--- + +The AI meeting notetaker you've been using is now **[Anarlog](https://github.com/fastrepl/anarlog)**. Same app, same files, same 8.3k⭐ on GitHub. New name, new license (MIT), and a clear single purpose: an open-source meeting notetaker that runs on your machine. + +The name **Char** still exists — but it's a different product now. Char is an AI notepad that finishes your todos. Different category, different audience, different bet. We'll get to it below. + +If you're here for meeting notes — you're in the right place. anarlog.so is the new home. char.com/blog/\* redirects here. + +### why two names + +Char (the meeting notetaker) and Char (the agentic todo notepad we've been building) were trying to live under one brand. They couldn't. + +The meeting notetaker is finished software in the best sense — system audio capture, plain markdown files, your AI of choice, no bots in your calls. People love it because it's *focused*. They want a tool they can audit, fork, and run forever — with or without us. + +The new Char is the opposite of finished. It's a thesis: that your todo list should finish itself. Type a checkbox, an agent picks it up — researches, drafts, schedules — while you stay in command of what matters. It's a delegation product, not a transcription one. Different mechanic. Different problem. Different people. + +We tried to ship both as "Char". The homepage was a mess. The roadmap was a coin flip on every feature. Worst of all: people who came for the meeting notetaker kept getting confused by the agent stuff, and people interested in delegation kept asking "wait, do I have to take meeting notes?" + +Two products, two names. That's the whole story. + +### so what is anarlog + +It's the existing app. Renamed, relicensed (GPL → MIT), kept open source forever. + +- Open source on GitHub: [github.com/fastrepl/anarlog](https://github.com/fastrepl/anarlog) +- Free forever — there is no paid tier and there won't be +- System audio capture (no bot joining your calls, no calendar permissions) +- Plain markdown files on your device +- BYOK or local models (Ollama, LM Studio) — your AI, your keys +- 45+ languages, works with Zoom / Meet / Teams / phone / in-person +- macOS and Linux. Windows is on the roadmap. + +If you're already running Char (the desktop app), it'll keep working. The next update will rename itself to Anarlog. Your local files don't move. Your stack doesn't change. We're keeping the pro AI models and the calendar integration in Anarlog through the migration window — nothing gets ripped out from under you. + +After the migration, Anarlog stays as a community-maintained OSS project. We'll keep it running. Patches, security fixes, the occasional feature. But the team's daily focus moves to the new Char. + +### so what is char now + +Char is the AI notepad that knows and works for you. **Your todos, on autopilot.** + +Type a checkbox in your daily note. An agent picks it up — researches a company before your call, drafts a follow-up email, schedules a meeting, books the flight. You stay in command of what matters. The work moves on its own. Char remembers your projects, contacts, and how you work, so week one it helps and month one it knows you. + +The category Char competes in isn't "meeting notetaker". It's the todo list — Things, Todoist, Notion — and the new wave of delegation agents (Yutori, AirJelly, Manus). Notetakers stop where the meeting ends. Char starts there: every action item becomes a checkbox that finishes itself. + +It's a different product. Private alpha right now, paid SaaS when it ships. Same company. Same conviction that humans deserve to stay in command of their own work. Different shape. + +If that sounds interesting, char.com is the place. If it doesn't — totally fine. Stay on Anarlog, take your meeting notes, own your files. We built it for you. + +### the journey + +If you've been following along, this is the second rename. The first one, [from Hyprnote to Char](/blog/hyprnote-is-now-char), corrected a name we never liked. This one is different. We're not renaming for fashion. We're separating two products that grew up inside one repo and started pulling in opposite directions. + +Each rename has been a sharper version of the same conviction: people deserve to own their work. The first time, that conviction was an instinct. The second time, it's a product split — because the people who care most about owning their meeting notes shouldn't have to share a brand with the people who want a notepad that delegates their day. + +Both are valid. They want different things. Now they get different things. + +### why "anarlog" + +It's an anagram of *granola*. The product everyone keeps comparing us to — and the one we keep beating on the things that matter (no bots, local files, your stack). "Granola, rearranged". We liked the joke and the name stuck. + +It also reads as **anar + log** — anar for autonomy, log for the artifact. Your stack, your keys, your files. A log you keep, not one we host. + +Both readings are true. Take whichever you like. diff --git a/apps/web/content/articles/char-vs-meetily.mdx b/apps/web/content/articles/char-vs-meetily.mdx index 026598fcd4..10bd65a7d3 100644 --- a/apps/web/content/articles/char-vs-meetily.mdx +++ b/apps/web/content/articles/char-vs-meetily.mdx @@ -1,6 +1,6 @@ --- -meta_title: "Char vs. Meetily: Which Is Better?" -meta_description: "Char and Meetily are both local-first, bot-free, open-source AI meeting tools. This comparison explains what sets them apart and which one fits your workflow." +meta_title: "Anarlog vs. Meetily: Which Is Better?" +meta_description: "Anarlog and Meetily are both local-first, bot-free, open-source AI meeting tools. This comparison explains what sets them apart and which one fits your workflow." author: - "Harshika" featured: false @@ -8,38 +8,35 @@ category: "Comparisons" date: "2026-04-15" --- -Char and Meetily were both created in December 2024. Both are written in Rust. Both capture system audio without a bot. Both are open-source and local-first.  +Anarlog and Meetily were both created in December 2024. Both are written in Rust. Both capture system audio without a bot. Both are open-source and local-first.  -They look like the same product. They are not. +They look like the same product. They aren't. -Meetily is a private transcription recorder. It records, transcribes locally, and generates a summary. Char is a meeting notepad. Its bet is that meetings are part of your workflow, not a separate activity. Transcription is one component of that. +Meetily is a private transcription recorder. It records, transcribes locally, and generates a summary. Anarlog is a meeting notepad. The bet is that meetings are part of your workflow, not a separate activity. Transcription is one component. -## **TL;DR: Char vs Meetily** +## **TL;DR: Anarlog vs Meetily** **Use Meetily if:** you are on Windows, or you want local transcription that works in minutes with no API keys. -**Use Char if:** you are on Mac, you take active notes during meetings, or you want your meeting history connected to your calendar, contacts, and developer tools. +**Use Anarlog if:** you are on Mac, you take active notes during meetings, or you want your meeting history connected to your calendar, contacts, and developer tools. -## **Char vs Meetily: Side-by-Side Comparison** +## **Anarlog vs Meetily: Side-by-Side Comparison** - -| | | | +| | | | | ---------------- | -------------------------------------------------------------------- | ------------------------------------------------------- | -| | **Char** | **Meetily** | -| **Platform** | macOS, Linux (Windows Q2 2026) | macOS, Windows | -| **Free tier** | On-device Transcription and Bring Your Own AI Model (cloud or local) | Ollama | -| **Entry paid** | $8/mo (Lite) | $10/mo (Pro) | -| **Pro** | $25/mo or $250/year | $10/mo | -| **License** | MIT | MIT | -| **GitHub stars** | [8,213](https://github.com/fastrepl/char) | [11,081](https://github.com/Zackriya-Solutions/meetily) | -| **Calendar** | Google, Apple, Outlook | Not available | -| **CLI** | Yes | No | -| **Daily notes** | In preview | No | - +| | **Anarlog** | **Meetily** | +| **Platform** | macOS, Linux (Windows Q2 2026) | macOS, Windows | +| **Free tier** | On-device Transcription and Bring Your Own AI Model (cloud or local) | Ollama | +| **Pricing** | Free forever (OSS, local + BYOK). Managed cloud at [Char](https://char.com) | $10/mo (Pro) | +| **License** | MIT | MIT | +| **GitHub stars** | [8,213](https://github.com/fastrepl/anarlog) | [11,081](https://github.com/Zackriya-Solutions/meetily) | +| **Calendar** | Google, Apple, Outlook | Not available | +| **CLI** | Yes | No | +| **Daily notes** | In preview | No | -## **Similarities Between Char and Meetily** +## **Similarities Between Anarlog and Meetily** -Both tools were built as a direct reaction to cloud meeting tools like Otter and Fireflies. They share the same foundational decisions: +Both tools were built in direct reaction to cloud meeting tools like Otter and Fireflies. They share the same foundational decisions: - System audio capture. No bot joins your call. No calendar permissions required. - 100% local processing available. Audio never has to leave your device. @@ -48,45 +45,45 @@ Both tools were built as a direct reaction to cloud meeting tools like Otter and - Bot-free recording. No visible recording participant in your meetings. - Built in Rust. -If your primary requirement is "no cloud, no bot, open-source," both tools satisfy it. The differences start after that. +If your primary requirement is "no cloud, no bot, open-source", both tools satisfy it. The differences start after that. -## **Differences Between Char and Meetily** +## **Differences Between Anarlog and Meetily** ### **1. Platform support** -Meetily runs on macOS and Windows. Char runs on macOS and Linux. If you are on Windows, Meetily is your only option today. Char has Windows on its roadmap but it is not available yet. +Meetily runs on macOS and Windows. Anarlog runs on macOS and Linux. If you're on Windows, Meetily is your only option today. Anarlog has Windows on the roadmap but it's not available yet. ### **2. What each tool is built to do** Meetily is a recorder. Record, transcribe with Whisper or Parakeet via Ollama, get a summary. The product loop is short and intentional. -Char is a meeting notepad. It connects to your calendar (Google, Apple, Outlook), tracks your contacts, gives you a rich editor for notes, ships an AI assistant called Charlie that can edit your notes directly, and has daily notes in preview with task carry-forward. Meetings in Char connect to the rest of your work.  +Anarlog is a meeting notepad. It connects to your calendar (Google, Apple, Outlook), tracks contacts, gives you a rich editor for notes, ships an AI assistant called Charlie that edits your notes directly, and has daily notes in preview with task carry-forward. Meetings in Anarlog connect to the rest of your work.  ### **3. Taking notes during a meeting** -Meetily is passive. You get a transcript and summary after the call. There is no editor. +Meetily is passive. You get a transcript and summary after the call. No editor. -Char has a built-in editor. You write notes before the meeting starts: agenda items, context, things you need to close on. You take notes during the call. After the meeting, the AI folds your notes and the transcript into a structured summary. Your pre-meeting context shapes the output. +Anarlog has a built-in editor. You write notes before the meeting starts: agenda items, context, things you need to close on. You take notes during the call. After the meeting, the AI folds your notes and the transcript into a structured summary. Your pre-meeting context shapes the output. Charlie, the persistent chat assistant, keeps the full session in context. It can reformat the summary, draft a follow-up email, or extract decisions. It edits your notes in place. ### **3. Developer tools** -Char ships with a [CLI](https://cli.char.com), a REST API, and an MCP server. Tools like Cursor and Claude Code can query your meeting history directly. A JS plugin runtime lets you write custom extensions. Shell hooks trigger automations when a meeting ends. You can also point Char's storage at your Obsidian vault. +Anarlog ships with a [CLI](https://cli.anarlog.so), a REST API, and an MCP server. Tools like Cursor and Claude Code can query your meeting history directly. A JS plugin runtime lets you write custom extensions. Shell hooks trigger automations when a meeting ends. You can also point Anarlog's storage at your Obsidian vault. Meetily is a standalone app with no API, CLI, or plugin layer. -Char and Meetily are both MIT licensed. +Anarlog and Meetily are both MIT licensed. ### **4. Pricing** Meetily's community edition works out of the box with Ollama. No account required, no API key. -Char's free tier offers unlimited local transcription and requires a BYOK API key for AI summaries (OpenAI, Anthropic, Deepgram) or a local model via Ollama or LM Studio. Two minutes of setup if you have keys already.  +Anarlog's free tier offers unlimited local transcription and requires a BYOK API key for AI summaries (OpenAI, Anthropic, Deepgram) or a local model via Ollama or LM Studio. Two minutes of setup if you have keys already.  -- **Char Lite: $8/month.** Cloud transcription, speaker identification, calendar integrations, sync, shareable links. +- **Anarlog: free forever.** Open source, fully local, BYOK. No paid tier on the OSS side. - **Meetily Pro: $10/month.** Enhanced accuracy, auto-meeting detection, PDF and DOCX export, improved diarization. 14-day free trial. -- **Char Pro: $25/month or $250/year.** Everything in Lite plus granular sync control and DocSend-style shareable links with view tracking and expiration. +- **Char (managed sibling of Anarlog): from $8/month.** If you want hosted transcription, speaker ID, calendar integrations, sync, and shareable links without running anything yourself, see [char.com](https://char.com). ## **When Should You Use Meetily?** @@ -95,7 +92,7 @@ Char's free tier offers unlimited local transcription and requires a BYOK API ke - Your meetings are self-contained. You do not need calendar integration, contacts, or cross-session history. - You want MIT-licensed code you can fork or self-host without restrictions. -## **When Should You Use Char?** +## **When Should You Use Anarlog?** - You are on Mac and want meetings connected to your broader workflow. - You take notes before or during meetings and want the AI to use that context. @@ -103,8 +100,8 @@ Char's free tier offers unlimited local transcription and requires a BYOK API ke - You want calendar-aware sessions: auto-creation from events, participant context, meeting countdown. - You want a long-term contact and meeting history that links across sessions. -## **Get Started with Char** +## **Get Started with Anarlog** -Char is free to download. The free tier works offline with a local model through Ollama or LM Studio. No subscription required to start. +Anarlog is free to download. The free tier works offline with a local model through Ollama or LM Studio. No subscription required to start. -[Download Char for Mac](https://char.com/download) and run your first meeting with full local processing. Upgrade to Lite at $8/month if you want cloud transcription and speaker identification without managing API keys. +[Download Anarlog](https://anarlog.so) and run your first meeting with full local processing. diff --git a/apps/web/content/articles/chatgpt-data-retention-policy.mdx b/apps/web/content/articles/chatgpt-data-retention-policy.mdx index c943e97036..eebf755140 100644 --- a/apps/web/content/articles/chatgpt-data-retention-policy.mdx +++ b/apps/web/content/articles/chatgpt-data-retention-policy.mdx @@ -8,40 +8,40 @@ category: "Guides" date: "2026-03-10" --- -In May 2025, a federal judge ordered OpenAI to preserve every ChatGPT conversation including the ones you deleted. Not just temporarily. Indefinitely, until further notice. +In May 2025, a federal judge ordered OpenAI to preserve every ChatGPT conversation, including the ones you deleted. Not temporarily. Indefinitely, until further notice. -Most users had no idea this happened. If you're evaluating AI providers before plugging one into your workflow, this is exactly the kind of detail that matters. +Most users had no idea this happened. If you're evaluating AI providers before plugging one into your workflow, this is the detail that matters. -Here's a full breakdown of OpenAI's data retention policy: what they keep, how long, and what you can actually do about it. +Here's the breakdown of OpenAI's data retention policy: what they keep, how long, and what you can actually do about it. ## What ChatGPT Stores Data by Default -When you use ChatGPT on a free, Plus, Pro, or Team plan, your conversations are saved to your account automatically. They sit there indefinitely until you delete them. +On Free, Plus, Pro, or Team plans, your conversations save to your account automatically. They sit there indefinitely until you delete them. -Once you delete a conversation, it disappears from your view immediately. But according to [OpenAI's own help documentation](https://help.openai.com/en/articles/8983778-chat-and-file-retention-policies-in-chatgpt), the data isn't actually removed from their servers for another 30 days and that's under normal circumstances. +When you delete a conversation, it disappears from your view immediately. But per [OpenAI's own help documentation](https://help.openai.com/en/articles/8983778-chat-and-file-retention-policies-in-chatgpt), the data isn't removed from their servers for another 30 days under normal circumstances. -One thing most users don't realize: if you've enabled ChatGPT's Memory feature, stored memories are retained separately from your chat history. Deleting a conversation doesn't wipe the memories extracted from it. You have to clear those manually through Settings. +If you've enabled ChatGPT's Memory feature, stored memories are retained separately from your chat history. Deleting a conversation doesn't wipe the memories extracted from it. Clear those manually through Settings. ## The Court Order You Probably Missed -In May 2025, a federal judge issued a preservation order requiring OpenAI to retain all ChatGPT user logs as part of the *New York Times* copyright lawsuit. The ruling was explicit: preserve and segregate all output log data that would otherwise be deleted. This includes conversations users had already removed. +In May 2025, a federal judge issued a preservation order requiring OpenAI to retain all ChatGPT user logs as part of the *New York Times* copyright lawsuit. The ruling was explicit: preserve and segregate all output log data that would otherwise be deleted. That includes conversations users had already removed. -[Bloomberg Law reported](https://news.bloomberglaw.com/ip-law/openai-must-turn-over-20-million-chatgpt-logs-judge-affirms) that by November 2025, the judge further ordered OpenAI to hand over 20 million de-identified ChatGPT logs to the Times and other news plaintiffs. +[Bloomberg Law reported](https://news.bloomberglaw.com/ip-law/openai-must-turn-over-20-million-chatgpt-logs-judge-affirms) that by November 2025, the judge further ordered OpenAI to hand over 20 million de-identified ChatGPT logs to the Times and other news plaintiffs. -The preservation order itself was lifted in late September 2025. OpenAI resumed normal deletion practices after that point. But conversations from the April–September 2025 window remain in secure storage pending the ongoing litigation. +The preservation order was lifted in late September 2025. OpenAI resumed normal deletion practices after that. But conversations from the April–September 2025 window remain in secure storage pending the litigation. -OpenAI says access to that data is restricted to a small, audited legal and security team. The Times and other plaintiffs don't have open access—any disclosure goes through court-approved discovery procedures. Still, data you thought was gone is sitting in a server somewhere. +OpenAI says access to that data is restricted to a small, audited legal and security team. The Times and other plaintiffs don't have open access — any disclosure goes through court-approved discovery. Still, data you thought was gone is sitting on a server somewhere. ## The "Opt Out of Training" Setting and What It Actually Does -ChatGPT uses your conversations to improve its models by default. You can turn this off: **Settings → Data Controls → toggle off "Improve model for everyone."** +ChatGPT uses your conversations to improve its models by default. Turn it off: **Settings → Data Controls → toggle off "Improve model for everyone".** -Two important caveats: +Two caveats: - It only affects future conversations. Any data already used for training stays in the training set. Disabling the toggle doesn't reach back. - It doesn't change how long your data is stored. Even with the setting off, OpenAI still keeps conversations on their servers for 30 days after deletion. They retain them during this window to screen for abuse and policy violations. -Temporary Chat mode works somewhat differently. Conversations in Temporary Chat are never used for training and are scheduled for deletion within 30 days automatically. You don't need to manually delete them but they're still on OpenAI's servers during that window. +Temporary Chat mode works differently. Conversations in Temporary Chat are never used for training and get auto-deleted within 30 days. You don't need to delete them manually, but they're still on OpenAI's servers during that window. ## How ChatGPT's Data Retention Policy Differs by Plan @@ -53,7 +53,7 @@ Conversations are retained indefinitely until deleted. After deletion, they rema ### 2. ChatGPT Enterprise and Edu -Conversations are not used to train OpenAI's models by default—no opt-out required. Workspace admins control retention settings. Deleted conversations are removed within 30 days unless legally required to be kept. +Conversations are not used to train OpenAI's models by default — no opt-out required. Workspace admins control retention settings. Deleted conversations are removed within 30 days unless legally required to be kept. ### 3. API (Standard) @@ -65,46 +65,46 @@ Inputs and outputs are never logged. There's no retention period because there's ## What About GDPR and HIPAA? -If you're in a regulated industry or dealing with sensitive data, the consumer-facing ChatGPT product is not the right tool. OpenAI has been explicit about this. +If you're in a regulated industry or handling sensitive data, the consumer ChatGPT product is not the right tool. OpenAI has been explicit about this. -Standard ChatGPT (Free, Plus, Pro, Team) does not offer a Business Associate Agreement, which means it cannot be used with Protected Health Information under HIPAA. Using it with patient data is a compliance violation. +Standard ChatGPT (Free, Plus, Pro, Team) doesn't offer a Business Associate Agreement, which means it can't be used with Protected Health Information under HIPAA. Using it with patient data is a compliance violation. -For GDPR, OpenAI offers a Data Processing Addendum (DPA) for ChatGPT Team, Enterprise, and API customers, not for consumer accounts. If you're in the EU and not on one of those plans, GDPR compliance is your own responsibility to figure out. +For GDPR, OpenAI offers a Data Processing Addendum (DPA) for ChatGPT Team, Enterprise, and API customers, not for consumer accounts. If you're in the EU and not on one of those plans, GDPR compliance is your own problem. -There's also a jurisdiction question worth flagging. Reddit's r/privacy community has noted that because OpenAI's servers are primarily US-based, data transferred from the EU to process through ChatGPT is subject to cross-border data transfer rules under GDPR. This has compliance implications for European organizations that aren't using a DPA. +One jurisdiction issue to flag: because OpenAI's servers are primarily US-based, data transferred from the EU through ChatGPT is subject to cross-border data transfer rules under GDPR. That has compliance implications for European organizations not using a DPA. ## So What's the Practical Risk? -For most individual users, the day-to-day risk is limited. OpenAI doesn't sell your conversations, and the data isn't publicly accessible. +For most individual users, the day-to-day risk is limited. OpenAI doesn't sell your conversations, and the data isn't publicly accessible. -The risk is more nuanced: +The real risk is more nuanced: -- **Legal exposure**: The court order showed that data you delete can be preserved and disclosed through legal proceedings. If you've had sensitive conversations through ChatGPT, there's a window of time where that data exists outside your control. -- **Training data reuse**: Unless you've explicitly opted out, your conversations have likely contributed to model training. That data doesn't get unlearned when you opt out later. -- **Compliance liability**: In healthcare, legal, finance, and other regulated fields, using consumer ChatGPT with client or patient information creates compliance risk regardless of what OpenAI promises about their security practices. +- **Legal exposure**: the court order showed that data you delete can be preserved and disclosed through legal proceedings. If you've had sensitive conversations through ChatGPT, there's a window where that data exists outside your control. +- **Training data reuse**: unless you've explicitly opted out, your conversations have likely contributed to model training. That data doesn't get unlearned when you opt out later. +- **Compliance liability**: in healthcare, legal, finance, and other regulated fields, using consumer ChatGPT with client or patient information creates compliance risk regardless of what OpenAI promises about their security practices. ## The OpenAI API Has Meaningfully Better Data Controls -If you're building with OpenAI or using a tool that lets you bring your own API key, then the data situation is materially different from the consumer product. +If you're building with OpenAI or using a tool that lets you bring your own API key, the data situation is materially different from the consumer product. -With the standard API, OpenAI retains inputs and outputs for 30 days for abuse monitoring, then deletes them. There's no default use of your data for model training. If you qualify for Zero Data Retention endpoints, OpenAI never logs your data at all. +With the standard API, OpenAI retains inputs and outputs for 30 days for abuse monitoring, then deletes them. No default use of your data for model training. If you qualify for Zero Data Retention endpoints, OpenAI never logs your data at all. -This is relevant if you're evaluating how OpenAI fits into a broader AI workflow where you want control over which provider sees what data. +This matters if you're evaluating how OpenAI fits into a broader AI workflow where you want control over which provider sees what data. ## BTW, Your OpenAI API Key Works for Meeting Notes Too If you're already paying for API access, you can use that same key for meeting transcription and notes without going through the consumer product. -[Char](https://char.com/) is an open-source AI notepad for meetings that lets you bring your own API key. Connect your OpenAI, Anthropic, Google Gemini, or Azure-hosted GPT key, and your meeting data goes through the API. The retention policies from your API agreement apply, not the consumer defaults described above. +[Anarlog](https://anarlog.so/) is an open-source AI notepad for meetings that lets you bring your own API key. Connect your OpenAI, Anthropic, Google Gemini, or Azure-hosted GPT key, and your meeting data goes through the API. The retention policies from your API agreement apply, not the consumer defaults described above. -If even API-level retention is too much, Char also runs fully offline with local models through Ollama or LM Studio. Nothing leaves your machine. +If even API-level retention is too much, Anarlog also runs fully offline with local models through Ollama or LM Studio. Nothing leaves your machine. -No bot in your call. Char captures audio directly from your computer's input and output. No third-party bot joins your Zoom or Google Meet, so no intermediary service handles your meeting data. +No bot in your call. Anarlog captures audio directly from your computer's input and output. No third-party bot joins your Zoom or Google Meet, so no intermediary service handles your meeting data. -Notes stay on your device. Transcripts and summaries are stored as plain markdown files locally—no cloud-synced account storage retaining data on someone else's servers. +Notes stay on your device. Transcripts and summaries are stored as plain markdown files locally — no cloud-synced account storage retaining data on someone else's servers. -More than a transcription tool. Char does real-time transcription while you jot down what matters, then generates summaries from your memos once the meeting ends. Built-in AI chat lets you ask follow-ups—action items, rewrites, translations. It supports customizable note templates and integrates with Apple Calendar, Contacts, and Obsidian, with Notion, Slack, HubSpot, and Salesforce on the way. +More than a transcription tool. Anarlog does real-time transcription while you jot down what matters, then generates summaries from your memos once the meeting ends. Built-in AI chat lets you ask follow-ups: action items, rewrites, translations. It supports customizable note templates and integrates with Apple Calendar, Contacts, and Obsidian, with Notion, Slack, HubSpot, and Salesforce on the way. You're not locked into one provider. If your security team approves a different model next quarter, switch the key. Your notes stay on your device either way. -[Download Char for macOS](https://char.com/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file +[Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file diff --git a/apps/web/content/articles/chatgpt-for-meeting-notes.mdx b/apps/web/content/articles/chatgpt-for-meeting-notes.mdx index 7ef520da32..1054582c25 100644 --- a/apps/web/content/articles/chatgpt-for-meeting-notes.mdx +++ b/apps/web/content/articles/chatgpt-for-meeting-notes.mdx @@ -3,7 +3,6 @@ meta_title: "How to Use ChatGPT for Meeting Notes?" meta_description: "Learn how to use ChatGPT for meeting notes with Record Mode & manual upload. Get proven prompts, privacy tips & better alternatives for professional use." author: - "Harshika" -coverImage: "/api/assets/blog/chatgpt-for-meeting-notes/cover.png" featured: false category: "Guides" date: "2025-09-25" @@ -13,18 +12,18 @@ date: "2025-09-25" If you use ChatGPT like your life depends on it, you've probably wondered whether it can also take meeting notes for you. -Yes. There are two ways to do it: +Yes. Two ways: -1. Using ChatGPT Record Mode -2. Manually uploading transcription and using the right prompt to generate notes +1. ChatGPT Record Mode +2. Manually uploading a transcript and prompting ChatGPT to generate notes -Let's look at both of these methods in detail. +Let's look at both. ## Method 1: using ChatGPT record mode ![chatgpt record mode](/api/assets/blog/chatgpt-for-meeting-notes/chatgpt-2.webp) -When ChatGPT Record mode launched, the AI Twitter crowd lost it. The actual feature, though, feels like a beta that escaped into production. Before I get into that, let's see how it works. +When ChatGPT Record mode launched, the AI Twitter crowd lost it. The actual feature feels like a beta that escaped into production. First, how it works. ### Prerequisites of ChatGPT record mode @@ -44,21 +43,21 @@ Here's the step-by-step process: Important details: - Each session can run up to 120 minutes and ends with an editable summary that includes time-stamped citations and suggested action items. -- ChatGPT doesn't join your meetings as a bot, so no one knows you're recording. Make sure to explicitly ask for consent. -- Record mode works with all meeting types—virtual, in-person, or playback of recorded conversations—because it captures microphone and system-level audio. +- ChatGPT doesn't join your meetings as a bot, so no one knows you're recording. Ask for consent explicitly. +- Record mode works with all meeting types — virtual, in-person, or playback of recorded conversations — because it captures microphone and system-level audio. #### Pros of record mode: -- **The 120-minute limit is generous.** Most meetings don't reach this. I've recorded entire workshop sessions without running out of time. Compare that to tools capping you at 30 minutes on free tiers. -- **Canvas integration is genuinely useful.** Once you get your summary, you can edit it collaboratively, ask ChatGPT to reformat it, or transform it into other documents. I've turned meeting notes into project plans, emails, and slide outlines without leaving the interface. -- **It handles interruptions gracefully.** You can pause mid-meeting to take a phone call, resume when you're back, and it treats it as one continuous session. No weird gaps or formatting issues. +- **The 120-minute limit is generous.** Most meetings don't hit it. I've recorded full workshop sessions without running out. Compare that to tools capping you at 30 minutes on the free tier. +- **Canvas integration is useful.** Once you get the summary, you can edit it, ask ChatGPT to reformat it, or turn it into something else. I've turned meeting notes into project plans, emails, and slide outlines without leaving the interface. +- **It handles interruptions well.** You can pause mid-meeting for a phone call, resume after, and it treats it as one continuous session. No weird gaps or formatting issues. #### Cons of record mode: -- **The reliability issue is real.** I've had it stop mid-recording randomly (network hiccup, even though it's supposed to be local processing), and once it simply forgot a 20-minute chunk of a client call. -- **AI hallucination creates false meeting records.** ChatGPT will confidently invent details that never happened. I've seen it fabricate entire action items, claim people said things they didn't, or create follow-up meetings that were never scheduled. -- **Speaker identification is basically broken.** It can tell the difference between your microphone and system audio, which works for one-on-one calls. But it gets confused with multiple speakers. If someone joins late, it doesn't identify them as a new speaker. -- **No explicit GDPR compliance workflow.** OpenAI basically shrugs and says, "make sure you have consent." That's it. If you're dealing with European clients or employees, Record mode puts you in a legally gray area. +- **The reliability issue is real.** I've had it stop mid-recording randomly (network hiccup, even though it's supposed to be local processing), and once it forgot a 20-minute chunk of a client call. +- **Hallucinations create false meeting records.** ChatGPT will confidently invent details that never happened. I've seen it fabricate entire action items, claim people said things they didn't, or create follow-up meetings that were never scheduled. +- **Speaker identification is basically broken.** It can tell the difference between your microphone and system audio, which works for one-on-one calls. With multiple speakers, it gets confused. If someone joins late, it doesn't identify them as a new speaker. +- **No explicit GDPR compliance workflow.** OpenAI shrugs and says "make sure you have consent". That's it. If you're dealing with European clients or employees, Record mode puts you in a legally gray area. ### How much does ChatGPT record mode cost? @@ -74,17 +73,17 @@ When you delete a conversation, the canvas and transcript are removed from their ### Is ChatGPT record mode worth trying? -If you're already paying for a ChatGPT Team plan and want to experiment with AI note-taking for casual internal meetings, it's worth trying. For client calls, legal discussions, or anything where accuracy matters, the risk of losing or corrupting your meeting record outweighs the convenience. +If you're already paying for a ChatGPT Team plan and want to experiment with AI note-taking for casual internal meetings, sure, try it. For client calls, legal discussions, or anything where accuracy matters, the risk of losing or corrupting your meeting record outweighs the convenience. -You still have to verify everything it produces, which defeats most of the time-saving benefits. It's a preview of what meeting AI could be, but it's not ready for professional use yet. +You still have to verify everything it produces, which defeats most of the time-saving benefit. A preview of what meeting AI could be, not yet ready for professional use. ## Method 2: manually uploading transcription + smart prompting ![chatgpt meeting notes](/api/assets/blog/chatgpt-for-meeting-notes/chatgpt-3.webp) -This is where I found better results. It's more manual, but you get way better control and outcomes. +This got me better results. More manual, but the control and output are much better. -I use my phone's voice recorder for in-person meetings and let Zoom transcribe virtual ones. Then I copy-paste into ChatGPT with specific prompts I've refined through trial and error. +I use my phone's voice recorder for in-person meetings and let Zoom transcribe virtual ones. Then I copy-paste into ChatGPT with prompts I've refined through trial and error. ### ChatGPT prompts for meeting notes that worked for me @@ -140,60 +139,60 @@ Only include items that have clear action items or decisions. Ignore general dis #### Pro tips -- **Chunk long transcripts** -- ChatGPT has limits. If your meeting was over an hour, break the transcript into 30-minute chunks and process them separately. I learned this after getting "that's too long" errors. -- **Clean your transcript first** -- Take 2 minutes to fix obvious transcription errors—especially names and technical terms. "John" becoming "Jon" throughout screws up action item assignments. -- **Be specific about format** -- Don't just say "make a list." Say "create a numbered list," or "use bullet points," or "make a table." ChatGPT defaults to paragraph form, which doesn't work for action items. +- **Chunk long transcripts** -- ChatGPT has limits. If your meeting was over an hour, break the transcript into 30-minute chunks and process them separately. I learned this after hitting "that's too long" errors. +- **Clean your transcript first** -- Take 2 minutes to fix obvious transcription errors, especially names and technical terms. "John" becoming "Jon" throughout screws up action-item assignments. +- **Be specific about format** -- Don't say "make a list". Say "create a numbered list", "use bullet points", or "make a table". ChatGPT defaults to paragraph form, which doesn't work for action items. - **Test your prompts** -- I keep a doc with my best-working prompts because what works varies by meeting type. Sales calls need different output than technical planning meetings. ### How does the manual upload method use your data? When you copy-paste transcripts into regular ChatGPT conversations, different privacy rules apply than in Record Mode. By default, OpenAI may use your content to train its models unless you opt out through the "Improve the model for everyone" toggle in Settings > Data Controls. -The biggest trap is the feedback system. Even if you've opted out everywhere, giving a single thumbs up or thumbs down on any response means the entire conversation, including your meeting transcript, can be used for model training. +The biggest trap is the feedback system. Even if you've opted out everywhere, a single thumbs up or thumbs down on any response means the whole conversation, including your meeting transcript, can be used for model training. -One accidental click can override all your privacy settings and make your sensitive meeting data part of ChatGPT's training dataset. +One accidental click overrides all your privacy settings and puts your sensitive meeting data into ChatGPT's training dataset. ### Is the manual upload method worth your time? -- **The good** -- You get complete control. You can run the same transcript through different prompts to get summaries for different audiences—detailed minutes for the file, an executive summary for your boss, and action items for the team. +- **The good** -- Full control. You can run the same transcript through different prompts to get summaries for different audiences: detailed minutes for the file, an executive summary for your boss, action items for the team. - **The annoying** -- Sometimes ChatGPT decides to be "helpful" and adds context that wasn't in the meeting. I once had it infer that we "probably decided" something we absolutely did not. Always double-check. -- **The weird** -- It occasionally misses obvious action items while perfectly capturing random side conversations. It seems to get drawn to interesting tangents. +- **The weird** -- It occasionally misses obvious action items while perfectly capturing random side conversations. It gets drawn to interesting tangents.   ## Is there a better alternative to ChatGPT for meeting notes? -Bot-based AI notetakers like Otter or Fireflies can feel intrusive and may not offer privacy advantages. For bot-free options, we recommend [Char](/). Here's why: +Bot-based AI notetakers like Otter or Fireflies feel intrusive and don't always offer privacy advantages. For bot-free options, we recommend [Anarlog](/). Here's why: -Char listens to system audio like ChatGPT and runs on Mac, but that's where the similarities end. +Anarlog listens to system audio like ChatGPT and runs on Mac, but that's where the similarities end. -Char is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files. You choose your AI: managed cloud, bring your own keys, or run local models. Zero lock-in, zero compromises. +Anarlog is an open-source AI notepad for meetings that gives you control over your data and AI stack. Everything is stored as plain markdown files. You pick the AI: bring your own keys or run local models. Zero lock-in. -It's also open-source, so you can inspect every line of code, modify any component, and self-host on your infrastructure. No licensing servers or usage tracking. +Open source, so you can read every line, modify any component, and self-host on your own infrastructure. No licensing servers or usage tracking. -Char's note-taking features include: +Anarlog's note-taking features include: -![Char](/api/assets/blog/chatgpt-for-meeting-notes/chatgpt-4.webp) +![Anarlog](/api/assets/blog/chatgpt-for-meeting-notes/chatgpt-4.webp) - Take notes and let AI enhance them based on the transcript, or let AI do the entire summary without hallucinations. - Set custom vocabulary for industry jargon, client names, project codenames, or technical terms so transcription gets them right every time. -- Familiar interface that just works. Feels like Apple Notes with powerful AI running locally in the background. +- Familiar interface that just works. Feels like Apple Notes with AI running locally in the background. - Universal platform support. Works with any meeting software and in-person conversations without bots or restrictions. -- Custom templates for every situation—therapy sessions, legal meetings, job interviews, or casual conversations. -- Search your meeting history naturally. Ask "What did Sarah say about the budget?" instead of scrolling through months of notes. -- AI chat for instant clarification. Get immediate answers about action items, deadlines, or who said what. -- Control how much AI changes your notes. Choose minimal improvements to your original thoughts or complete restructuring. +- Custom templates for every situation: therapy sessions, legal meetings, job interviews, casual conversations. +- Search your meeting history naturally. Ask "what did Sarah say about the budget?" instead of scrolling through months of notes. +- AI chat for instant clarification. Get answers about action items, deadlines, or who said what. +- Control how much AI changes your notes. Pick minimal improvements to your original thoughts or full restructuring. - Verify everything with source analysis. Hover over summaries to see the exact transcript quote behind every point. -Also, Char is free forever for local transcription, BYOK, and all core features. The managed cloud service is $25/month for the easiest setup. +Also, Anarlog is free forever, fully local, BYOK, and open source. -**Want to try Char? [Download it for free](/download)!** +**Want to try Anarlog? [Download it for free](https://anarlog.so)!** ## Frequently asked questions ### 1. Can ChatGPT record meeting minutes? -Yes, if you have the right plan and setup. But "can" and "should" are different questions. It works great for internal team meetings and completely fails on important client calls. +Yes, if you have the right plan and setup. "Can" and "should" are different questions. It works for internal team meetings and fails on important client calls. ### 2. How long can ChatGPT record audio? @@ -214,6 +213,6 @@ No. Business features like data analysis, record mode, canvas, projects, tasks, ### 5. Is ChatGPT record mode secure? -Your audio gets deleted after transcription, but the transcript can be used for AI training unless you're on Enterprise or opt out. For sensitive meetings, this should raise concerns. +Your audio gets deleted after transcription, but the transcript can be used for AI training unless you're on Enterprise or opt out. For sensitive meetings, that's a concern.   diff --git a/apps/web/content/articles/choosing-a-cms.mdx b/apps/web/content/articles/choosing-a-cms.mdx index 63d536b2c6..cca3a02565 100644 --- a/apps/web/content/articles/choosing-a-cms.mdx +++ b/apps/web/content/articles/choosing-a-cms.mdx @@ -11,11 +11,11 @@ date: "2025-11-28" If you're building a blog, docs, or a knowledge base in 2026, the options are overwhelming. -Sanity or Payload? Nextra or GitBook? Git-based CMS like Keystatic? Or just raw MDX in GitHub with zero CMS at all? +Sanity or Payload? Nextra or GitBook? Git-based CMS like Keystatic? Or raw MDX in GitHub with zero CMS at all? -We went through the same decision for Char. This post maps the territory so you don't have to burn as many cycles on it. +We went through this for Anarlog. This post maps the territory so you don't have to burn the same cycles. -I won't tell you "X is the best CMS." Instead, I'll show you what each category is good at, what it quietly locks you into, and when you probably don't need a CMS at all. +I won't tell you "X is the best CMS". I'll show you what each category is good at, what it quietly locks you into, and when you probably don't need a CMS at all. **This is Part 3 of our publishing series.** @@ -24,7 +24,7 @@ I won't tell you "X is the best CMS." Instead, I'll show you what each category 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) (You are here) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/publishing-stack) +6. [How We Built Anarlog's Publishing Stack](/blog/publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) ## Step 0: decide what problem you're actually solving @@ -32,25 +32,25 @@ I won't tell you "X is the best CMS." Instead, I'll show you what each category Start by answering these questions instead: 1. **Who is writing most of the content?** - - engineers only - - mixed team (engineers + PMs + marketing) - - non-technical people only + - engineers only + - mixed team (engineers + PMs + marketing) + - non-technical people only 2. **What are you publishing?** - - technical blog - - product docs / API reference - - internal knowledge base - - marketing pages / landing pages + - technical blog + - product docs / API reference + - internal knowledge base + - marketing pages / landing pages 3. **How often does content structure change?** - - almost never (simple posts, titles, tags) - - sometimes (new fields, types, locales) - - constantly (complex content models, multiple channels) + - almost never (simple posts, titles, tags) + - sometimes (new fields, types, locales) + - constantly (complex content models, multiple channels) 4. **How long should your content survive?** - - 6 months? - - 2–3 years? - - 10+ years and multiple stack rewrites? + - 6 months? + - 2–3 years? + - 10+ years and multiple stack rewrites? Once you answer these, the CMS choice mostly falls out. @@ -63,7 +63,7 @@ Once you answer these, the CMS choice mostly falls out. ## 1. No CMS: just Git + MDX (what we use) -At Char, content lives as `.mdx` files in the repo. We write in Zed / Cursor / VS Code. GitHub is the CMS. PRs are drafts, merges are publish. CI handles link checking, OG generation, and so on. +At Anarlog, content lives as `.mdx` files in the repo. We write in Zed / Cursor / VS Code. GitHub is the CMS. PRs are drafts, merges are publish. CI handles link checking, OG generation, and so on. Pros: @@ -79,7 +79,7 @@ Cons: - No drag-and-drop media UI out of the box - No built-in content dashboard, workflows, or roles -If your team is very technical and you're early-stage, this is almost always the correct default. You can always add a Git-based or headless CMS later without throwing anything away. +If your team is technical and you're early-stage, this is the right default. You can add a Git-based or headless CMS later without throwing anything away. ## 2. Git-based CMS: "UI for your repo" (Keystatic & friends) @@ -104,7 +104,7 @@ Cons: - You're tied into their config models and upgrade cycle - Not as "enterprise omnichannel" as big headless CMSs -Use this when you like the Git + MDX approach but want PMs, designers, or marketing to tweak copy and metadata without opening an IDE. +Use this when you like Git + MDX but want PMs, designers, or marketing to tweak copy and metadata without opening an IDE. --- @@ -147,17 +147,17 @@ Sometimes you don't need a CMS. You need a **docs product**. ### GitBook -GitBook positions itself as a documentation + knowledge base platform for technical teams, with a polished block-based editor, site customization options, and Git Sync that lets you connect to GitHub/GitLab for a docs-as-code workflow. [GitBook](https://gitbook.com/docs) +GitBook is a documentation + knowledge base platform for technical teams, with a block-based editor, site customization, and Git Sync that lets you connect to GitHub/GitLab for a docs-as-code workflow. [GitBook](https://gitbook.com/docs) -It works well if you want a hosted, beautiful docs site with search, navigation, versioning, and collaboration. It supports optional Git synchronization for teams that still want Markdown in repos. +It works if you want a hosted docs site with search, navigation, versioning, and collaboration, and supports optional Git sync for teams that still want Markdown in repos. -Notion, Confluence, and similar tools live in the same category but less focused on developers. +Notion, Confluence, and similar tools live in the same category but are less focused on developers. Pros: -- Fastest path to a polished docs or knowledge base +- Fastest path to a docs or knowledge base - Low setup cost, almost no infrastructure -- Very friendly for non-technical stakeholders +- Friendly for non-technical stakeholders - Often have built-in AI-assisted search and summaries Cons: @@ -172,38 +172,38 @@ Use this when you want a **docs site ready tomorrow**, UX matters more than cont ## Summary: the four buckets at a glance -| Bucket | Best For | Pros | Cons | Examples | +| Bucket | Best For | Pros | Cons | Examples | | ---------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | --------------------------- | -| **No CMS (Git + MDX)** | Technical teams, engineers writing content | Maximum portability, no vendor lock-in, diffable/reviewable, perfect IDE integration | No visual UI, non-technical users need training | Plain MDX in GitHub | -| **Git-Based CMS** | Mixed teams needing visual UI while keeping content in Git | Content-as-code benefits + visual editing, portable files, non-engineers can edit | Extra admin app to host, tied to their config models | Keystatic, TinaCMS, Decap | -| **Headless CMS** | Multi-channel content platforms with complex workflows | Powerful schema modeling, omnichannel delivery, great editorial UX, localization | Infrastructure complexity, proprietary content structure, migration cost | Sanity, Payload | -| **Docs Platforms** | Teams needing polished docs/KB sites fast | Fastest setup, beautiful out-of-box, low infra cost, non-tech friendly | Limited portability, harder product integration, platform dependency | GitBook, Notion, Confluence | +| **No CMS (Git + MDX)** | Technical teams, engineers writing content | Maximum portability, no vendor lock-in, diffable/reviewable, perfect IDE integration | No visual UI, non-technical users need training | Plain MDX in GitHub | +| **Git-Based CMS** | Mixed teams needing visual UI while keeping content in Git | Content-as-code benefits + visual editing, portable files, non-engineers can edit | Extra admin app to host, tied to their config models | Keystatic, TinaCMS, Decap | +| **Headless CMS** | Multi-channel content platforms with complex workflows | Powerful schema modeling, omnichannel delivery, great editorial UX, localization | Infrastructure complexity, proprietary content structure, migration cost | Sanity, Payload | +| **Docs Platforms** | Teams needing polished docs/KB sites fast | Fastest setup, beautiful out-of-box, low infra cost, non-tech friendly | Limited portability, harder product integration, platform dependency | GitBook, Notion, Confluence | --- ## 5. Special case: Nextra & friends (frameworks, not CMSs) -Tools like **Nextra** are not CMS platforms but content-focused frameworks built on Next.js that make it easy to build docs and blog sites with Markdown/MDX and opinionated themes. [Nextra](https://nextra.site/docs) +Tools like **Nextra** aren't CMS platforms. They're content-focused frameworks built on Next.js that make it easy to ship docs and blog sites with Markdown/MDX and opinionated themes. [Nextra](https://nextra.site/docs) -Nextra gives you prebuilt docs/blog UI (sidebar, nav, search glue), MDX-first authoring, and works well for developer docs and personal blogs. +Nextra gives you prebuilt docs/blog UI (sidebar, nav, search glue), MDX-first authoring, and works for developer docs and personal blogs. -You still choose how to manage content: plain files, a Git-based CMS, a headless CMS feeding MDX remotely, etc. +You still pick how to manage content: plain files, a Git-based CMS, a headless CMS feeding MDX remotely, etc. -This is the category Char's stack resembles: modern React/MDX frontend with content-first in Git. +This is the category Anarlog's stack resembles: modern React/MDX frontend with content-first in Git. --- ## A simple decision matrix -| Situation | Likely Best Choice | +| Situation | Likely Best Choice | | ------------------------------------------------------------------ | ----------------------------------------------- | -| Small, technical team, mostly engineers writing | **Git + MDX, no CMS** | -| Technical team, but PMs/design/marketing need to edit copy | **Git-based CMS** (Keystatic/Tina/etc.) | -| Product needs content across web, app, marketing, multiple locales | **Headless CMS** (Sanity, Payload, etc.) | -| You just need a docs/KB site ASAP | **Docs platform** (GitBook, maybe Notion early) | -| You want fine control over UI + MDX, but not heavy infra | **Nextra or similar framework** on top of Git | +| Small, technical team, mostly engineers writing | **Git + MDX, no CMS** | +| Technical team, but PMs/design/marketing need to edit copy | **Git-based CMS** (Keystatic/Tina/etc.) | +| Product needs content across web, app, marketing, multiple locales | **Headless CMS** (Sanity, Payload, etc.) | +| You just need a docs/KB site ASAP | **Docs platform** (GitBook, maybe Notion early) | +| You want fine control over UI + MDX, but not heavy infra | **Nextra or similar framework** on top of Git | -If you're not sure, default to Git + MDX now. Add a Git-based or headless CMS later only if reality forces you to. +If you're not sure, default to Git + MDX. Add a Git-based or headless CMS later only if reality forces you to. Most teams overestimate how much CMS infrastructure they need in the first 2–3 years. @@ -244,7 +244,7 @@ For a technical founder starting a new product: - If I hit real omnichannel, multi-locale complexity, I'd consider **Sanity or Payload** and accept the trade-offs. [Sanity or Payload](https://nextjstemplates.com/blog/headless-cms-nextjs) - For standalone internal knowledge bases, I'd probably just use **GitBook**. [GitBook](https://gitbook.com/docs) -That's basically Char's philosophy: start with the simplest thing that respects **developer workflows, portability, and long-term ownership.** +That's basically Anarlog's philosophy: start with the simplest thing that respects **developer workflows, portability, and long-term ownership.** The tools will change. Your content shouldn't have to. @@ -257,5 +257,5 @@ The tools will change. Your content shouldn't have to. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) (You are here) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/publishing-stack) +6. [How We Built Anarlog's Publishing Stack](/blog/publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) diff --git a/apps/web/content/articles/company-handbook-in-public.mdx b/apps/web/content/articles/company-handbook-in-public.mdx index f70305e98c..de24aa639e 100644 --- a/apps/web/content/articles/company-handbook-in-public.mdx +++ b/apps/web/content/articles/company-handbook-in-public.mdx @@ -1,7 +1,7 @@ --- -meta_title: "Why We're Publishing Our Company Handbook at Char" +meta_title: "Why We're Publishing Our Company Handbook at Anarlog" display_title: "Why We're Publishing Our Company Handbook" -meta_description: "Why early-stage companies are built on founder philosophy, not just products—and why we're publishing our company handbook openly at Char." +meta_description: "Why early-stage companies are built on founder philosophy, not just products—and why we're publishing our company handbook openly at Anarlog." author: "John Jeong" featured: false category: "Founders' notes" @@ -12,7 +12,7 @@ In the early days of a startup, engagement doesn't come from feature lists or po Before companies had traction, they had manifestos. Before metrics, they had beliefs. People followed because the way of thinking felt honest, coherent, and human. -That's why we decided to publish our company handbook openly at Char. +That's why we decided to publish our company handbook openly at Anarlog. ## Two Documents, Not One @@ -32,7 +32,7 @@ Most startups explain what they're building. Very few explain how they think. Culture, expectations, and definitions of success are often left implicit. People are expected to "pick it up" over time. But when expectations live only in people's heads, everyone fills in the gaps differently — and misalignment is almost guaranteed. -This problem compounds in open-source companies. Our code is public by default. Anyone can read it, audit it, or contribute to it. But the thinking behind the code — the standards, the trade-offs, the way decisions are made — usually stays hidden. +This problem compounds in open-source companies. Our code is public by default. Anyone can read it, audit it, or contribute to it. But the thinking behind the code, the standards, the trade-offs, the way decisions are made, usually stays hidden. We didn't want that asymmetry. @@ -40,13 +40,13 @@ We didn't want that asymmetry. Publishing a company handbook publicly builds trust. In the early days, people don't engage with companies because they're perfect. They engage because they feel real. They want to know how the founders see the world, how they make trade-offs, and what kind of future they're trying to build. -Some of the most respected open-source companies — like GitLab, Cal.com, and PostHog — treat their handbooks not as internal policy, but as living documents. Not slogans. Not posters. Just honest clarity. +Some of the most respected open-source companies, like GitLab, Cal.com, and PostHog, treat their handbooks not as internal policy, but as living documents. Not slogans. Not posters. Just honest clarity. Authentic writing creates more meaningful engagement than polished marketing copy. Especially early on. ## What Our Handbook Is (and Isn't) -The Char handbook is not a rulebook. It's not a list of corporate values. It's not a finished product. +The Anarlog handbook is not a rulebook. It's not a list of corporate values. It's not a finished product. It's a snapshot of how we define good work, ownership, communication, and trust right now. Some parts may change. Some beliefs may evolve. What matters is that our expectations are explicit, not assumed. @@ -54,7 +54,7 @@ Writing this down is slightly uncomfortable. That's exactly why it matters. ## An Invitation -If you're building a company — especially an open-source one — ask yourself: What expectations exist in your company that everyone "just knows," but have never been written down? +If you're building a company, especially an open-source one, ask yourself: What expectations exist in your company that everyone "just knows", but have never been written down? If you're reading this as a contributor, user, or future teammate, this handbook is our attempt to show how we think, not just what we build. diff --git a/apps/web/content/articles/developer-documentation-tools.mdx b/apps/web/content/articles/developer-documentation-tools.mdx index 2cf72b7884..d3c7939444 100644 --- a/apps/web/content/articles/developer-documentation-tools.mdx +++ b/apps/web/content/articles/developer-documentation-tools.mdx @@ -7,38 +7,38 @@ category: "Engineering" date: "2025-12-02" --- -Documentation is not the same thing as blogging. +Documentation is not blogging. -Docs are a product. People rely on them to unblock themselves, integrate with you, debug issues, and understand how your system actually works. So the tools you'd use for blog posts don't always work for docs. +Docs are a product. People rely on them to unblock themselves, integrate with you, debug issues, and understand how your system actually works. The tools you'd use for blog posts don't always work for docs. Docs need structure, navigation, versioning, API examples, sidebars, and searchable knowledge. They also need to be maintainable by a real team — engineers, PMs, support, devrel — not just whoever has access to the CMS. -This post breaks down what tools you should consider in 2026 if you're building developer documentation from scratch. +This post breaks down what to consider in 2026 if you're building developer documentation from scratch. ## There are really only five kinds of docs systems Every modern docs system falls into one of these: -1. **Pure File-Based (MD/MDX)** - → Docusaurus, Nextra, Mintlify Open Source, MkDocs, Astro + MDX +1. **Pure File-Based (MD/MDX)** + → Docusaurus, Nextra, Mintlify Open Source, MkDocs, Astro + MDX -2. **Git-Based CMS Layer on Top** - → Keystatic, TinaCMS, Decap +2. **Git-Based CMS Layer on Top** + → Keystatic, TinaCMS, Decap -3. **Headless CMS with Docs Frontend** - → Sanity, Payload, Contentful powering a docs UI +3. **Headless CMS with Docs Frontend** + → Sanity, Payload, Contentful powering a docs UI -4. **Hosted Docs Platforms** - → GitBook, Readme.com, Mintlify Cloud, Archbee, Notion (light docs) +4. **Hosted Docs Platforms** + → GitBook, Readme.com, Mintlify Cloud, Archbee, Notion (light docs) -5. **Custom, In-Product Documentation** - → Your own framework + components (what many top devtools do) +5. **Custom, In-Product Documentation** + → Your own framework + components (what many top devtools do) Each has a different cost model, maintenance profile, and failure mode. ## 1. Pure file-based docs (MDX) — the default for technical teams -This includes Docusaurus, Nextra, MkDocs / Material for MkDocs, Astro + MDX, and TanStack Start + MDX. This is the modern "docs-as-code" standard. +This includes Docusaurus, Nextra, MkDocs / Material for MkDocs, Astro + MDX, and TanStack Start + MDX. The modern "docs-as-code" standard. ### Pros @@ -61,7 +61,7 @@ This includes Docusaurus, Nextra, MkDocs / Material for MkDocs, Astro + MDX, and Use this if you're a technical team that wants control without premature complexity. It works well if you don't have 15 PMs writing docs every day. -For 80% of startups, file-based MDX docs will be the right answer. Vercel, Supabase, Clerk, Planetscale, PostHog, and most modern OSS projects use this approach. +For 80% of startups, file-based MDX is the right answer. Vercel, Supabase, Clerk, Planetscale, PostHog, and most modern OSS projects use this approach. ## 2. Git-based CMS layer — best middle ground @@ -106,7 +106,7 @@ Using Sanity, Payload, or Contentful to power docs. ### When to use it -Use this if you're a larger organization with multiple products, multiple locales, and multiple teams updating docs. Think Stripe-level documentation needs. If you're under 100 employees, this is almost always unnecessary. +Use this if you're a larger organization with multiple products, locales, and teams updating docs. Think Stripe-level documentation. If you're under 100 employees, this is almost always unnecessary. ## 4. Hosted docs platforms — fastest to deploy, most lock-in @@ -134,7 +134,7 @@ Use this if you need docs fast, your team is mixed-technical, and you don't mind ## 5. Custom in-product docs — the future for some teams -Many modern companies (Linear, Raycast, Warp, Replit) embed docs directly into their product or create heavily customized documentation systems with React/MDX component libraries, interactive playgrounds, copyable codeblocks, live components, search tied into product data, custom navigation patterns, and AI-assisted docs generation. +Linear, Raycast, Warp, and Replit embed docs directly into their product or build heavily customized systems: React/MDX component libraries, interactive playgrounds, copyable codeblocks, live components, search tied into product data, custom navigation, AI-assisted docs generation. ### Pros @@ -152,7 +152,7 @@ Many modern companies (Linear, Raycast, Warp, Replit) embed docs directly into t ### When to use it -Use this if you're building a developer platform and your docs are part of your differentiation. This is the final evolution stage. +Use this if you're building a developer platform and your docs are part of your differentiation. ## So… Which Should You Use in 2026? @@ -164,7 +164,7 @@ Use this if you're building a developer platform and your docs are part of your **If you need something live tomorrow:** use GitBook or Mintlify Cloud. Accept the lock-in for speed. -**If your docs are part of your product:** build custom. This is where world-class developer tooling systems end up. +**If your docs are part of your product:** build custom. That's where world-class developer tooling ends up. ## Things most teams don't realize about docs @@ -174,11 +174,11 @@ Use this if you're building a developer platform and your docs are part of your **Search matters more than design.** Your docs UI can be plain, but your search must be excellent. Algolia DocSearch, Pagefind, or a custom hybrid all work well. -**Docs need to survive your future rewrites.** Your framework will change. Your content shouldn't break with it. This is why MDX in Git is the gold standard. +**Docs need to survive your future rewrites.** Your framework will change. Your content shouldn't break with it. MDX in Git is the gold standard for that reason. -## What we're betting on at Char +## What we're betting on at Anarlog -We're building our docs on TanStack Start + MDX, with a GitHub PR workflow, custom MDX components (callouts, tabs, code demos), automatic TOC + search, Keystatic as our team grows, and future interactive "live notebooks" tied to Char. +We're building our docs on TanStack Start + MDX, with a GitHub PR workflow, custom MDX components (callouts, tabs, code demos), automatic TOC + search, Keystatic as our team grows, and future interactive "live notebooks" tied to Anarlog. The principle is simple: docs are code. The best way to maintain code is with tools you already love. @@ -191,5 +191,5 @@ This is Part 5 of our publishing series. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) (You are here) -6. [How We Built Char's Publishing Stack](/blog/publishing-stack) +6. [How We Built Anarlog's Publishing Stack](/blog/publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) diff --git a/apps/web/content/articles/devin-ai-review.mdx b/apps/web/content/articles/devin-ai-review.mdx index 31696e192a..1167a1f308 100644 --- a/apps/web/content/articles/devin-ai-review.mdx +++ b/apps/web/content/articles/devin-ai-review.mdx @@ -9,9 +9,7 @@ category: "Engineering" date: "2026-02-13" --- -In the last two months, my two-person team spent over $5,000 running roughly 1,000 tasks in **[Devin](https://devin.ai/)**, the AI software engineer. We weren't spinning up 10 Claude Code instances to burn tokens as fast as possible. This is what actually happened when we integrated AI agents into our real-world workflow while building Char. - -Here's what we learned. +In the last two months, my two-person team spent over $5,000 running roughly 1,000 tasks in **[Devin](https://devin.ai/)**, the AI software engineer. We weren't spinning up 10 Claude Code instances to burn tokens. This is what actually happened when we integrated AI agents into our workflow while building Anarlog. ## Not a Reader? Watch the Video Instead @@ -21,9 +19,9 @@ Here's what we learned. ## Running AI Agents Where Your Team Already Works -The single most powerful decision we made was running Devin inside Slack, not our IDE. +The best decision we made was running Devin inside Slack, not our IDE. -Slack is where discussions already happen. We're already getting alerts from Zendesk, Sentry, and Discord. Launching an agent directly inside a thread where the problem is being discussed eliminates the friction of switching contexts. +Slack is where discussions already happen. We already get alerts from Zendesk, Sentry, and Discord there. Launching an agent inside the thread where a problem is being discussed kills the friction of switching contexts. **Real example:** John, my co-founder, identified an issue with our AI prompts and tagged Devin to fix it. Devin fixed it, but the approach was non-optimal. Since AI prompting is something I work on, I jumped into the same thread with more context. Devin figured it out based on my additional input, finished the PR, and it got merged. We stayed in the same Slack thread the whole time, with no copy-pasting between tools. @@ -33,29 +31,29 @@ The agent participates in the same Slack threads where we discuss the problem. ## AI Agents Enable Non-Technical Teams to Ship Code -Having an agent accessible from Slack opened up tasks that don't necessarily require technical skills. For instance, understanding what we're tracking in analytics or making small adjustments to better understand user behavior. +Having an agent accessible from Slack opened up tasks that don't require technical skills—understanding what we're tracking in analytics, or making small adjustments to better understand user behavior. -John attached some PostHog docs and asked questions about what we're tracking and what we should be tracking long-term. Devin made the changes. Now both John and I know we have analytics updates—super helpful for staying aligned. +John attached some PostHog docs and asked questions about what we're tracking and what we should be tracking long-term. Devin made the changes. Now both of us know we have analytics updates—helpful for staying aligned. -Since we use GitHub as a CMS for Char, we can even update landing pages or blog content directly from Slack. John attached a PDF from an internal discussion, and Devin updated our docs based on the actual conversation we had. +Since we use GitHub as a CMS for Anarlog, we can even update landing pages or blog content directly from Slack. John attached a PDF from an internal discussion, and Devin updated our docs based on the actual conversation we had. ## Three Types of Tasks to Delegate to Devin AI -As a small early-stage startup, there's always a lot going on. We're handling day-to-day work while thinking about what's next—new features, product direction, how the codebase should evolve. Dumping all of this into an async coding agent lets us explore solutions without blocking other work. +As a small early-stage startup, there's always a lot going on. We're handling day-to-day work while thinking about new features, product direction, how the codebase should evolve. Dumping all of that into an async coding agent lets us explore solutions without blocking the work in front of us. ### Degree 1: Exploration (Not Shipping Anytime Soon) -This is work that isn't planned for the immediate future. We're not going to ship it or even merge it right now, but exploring it helps us understand what the work would look like, how complex it is, and roughly how long it might take. +Work we're not planning to ship soon. We won't merge it right now, but exploring it tells us what the work looks like, how complex it is, and roughly how long it might take. **Example:** Even though we're focusing on our macOS desktop app, we had ideas around building a Chrome extension. I asked Devin to research how to make a Chrome extension that works with a desktop app. We learned how 1Password does it and got a rough plan. -Then we cloned the repository of a popular Chrome extension framework. Based on the docs and actual code examples, we implemented it to see how it would look. We didn't merge it, but it's useful to see how it'll look in the future. +Then we cloned the repository of a popular Chrome extension framework. Using the docs and code examples, we implemented it to see how it would look. We didn't merge it, but the prototype is useful to have around. ### Degree 2: Preparation (Relevant, But Not Right Now) This is work we'll likely merge, but we're not pulling it into the IDE yet. -**Example:** Someone asked whether Char could import data from Apple Notes. That feels like a feature we could support in the future, but it's not a core focus at the moment. +**Example:** Someone asked whether Anarlog could import data from Apple Notes. That feels like a feature we could support in the future, but it's not a core focus at the moment. We did research to see if there was any existing work on that. There was, so we cloned it, ported the test cases, and let Devin implement it. Tests passed, so we safely merged it for a future feature. @@ -63,7 +61,7 @@ We did research to see if there was any existing work on that. There was, so we This is work we'll definitely look at, but spawning the agent right now lets us avoid context switching. Maybe we're traveling or about to go to sleep. -**Example:** We needed to update test cases around our Tinybase utils—very relevant and important work. We asked Devin to clone the repo, inspect the codebase, and write the test cases. +**Example:** We needed to update test cases around our Tinybase utils. We asked Devin to clone the repo, inspect the codebase, and write the test cases. One trick: we asked Devin to use the Claude CLI that we already installed on Devin's machine. This way we can offload some AI inference to our Anthropic account and use some credits. @@ -71,11 +69,11 @@ One trick: we asked Devin to use the Claude CLI that we already installed on Dev ## Good Documentation Enables AI Agents to Ship Code Faster -In Char, we focus on supporting multiple providers for language and speech model inference as part of our open-source effort. Early on, we spent time designing and documenting flexible, clean interfaces. This worked well for future contributions and community involvement, and the same benefits apply when working with coding agents. +In Anarlog, we support multiple providers for language and speech model inference as part of our open-source effort. Early on, we spent time designing and documenting clean interfaces. That paid off for community contributions, and the same benefits apply when working with coding agents. **Example: ElevenLabs Support** -We support both WebSocket-based real-time transcription and file upload-based batch transcription. We had a very detailed prompt on how models should be handled, how language should be handled, and other API references in the docs. +We support both WebSocket-based real-time transcription and file upload-based batch transcription. We had a detailed prompt on how models should be handled, how language should be handled, and other API references in the docs. Since we have end-to-end testing support in place, we sent the ElevenLabs API key as credentials (this can be passed in the prompt or through Infisical CLI). With all the documentation, test cases, and API key in place, Devin implemented it in almost one shot, and we safely merged it. @@ -91,11 +89,11 @@ The pattern: good docs, clean interfaces, and test infrastructure make it possib ## Automating Code Maintenance with AI Agents -Once a codebase reaches a certain size and age, maintenance work alone can consume significant engineering time and slow the team down. Coding agents let us offload much of that work. +Once a codebase reaches a certain size and age, maintenance alone can eat most of your engineering time. Coding agents let us offload most of it. ### Single-Prompt Migrations -One common example is doing migrations that have clear documentation. In Char, we recently completed: +One common example is doing migrations that have clear documentation. In Anarlog, we recently completed: - **AI SDK version 6 migration** in a single prompt - **Tailwind v3 to v4 migration** in a single prompt @@ -104,7 +102,7 @@ One common example is doing migrations that have clear documentation. In Char, w Things can get more complicated and may require multiple PRs or concurrent work. -A good example is applying Vercel's recent React best practices agent skills. We attached Vercel's React best practices document, and Devin figured out what changes should be done. Since there was a lot of isolatable work, we prompted Devin to do this concurrently by spawning concurrent Devin sessions. +A good example: applying Vercel's recent React best practices agent skills. We attached Vercel's document, and Devin figured out what changes were needed. Since most of the work was isolatable, we prompted Devin to spawn concurrent sessions. One way to do this is to ask Devin to make actual API calls. But there's a better way: use the analyze-session task. This lets you spawn concurrent Devin sessions to run work concurrently and generate separate PRs per task. @@ -112,15 +110,15 @@ One way to do this is to ask Devin to make actual API calls. But there's a bette Migrations and new guidelines don't happen every day, but pairing an agent with an automated linting tool can be applied daily. -In Char, we have a large Rust codebase, and since Cargo Clippy is pretty good, we set up a GitHub Action to run Cargo Clippy daily and spawn Devin to apply any fixes based on the output. +We have a large Rust codebase, and Cargo Clippy is pretty good, so we set up a GitHub Action to run Clippy daily and spawn Devin to apply any fixes based on the output. -This saves a lot of time applying these guidelines or Clippy warnings in an async manner, since running Clippy and Cargo check takes a long time. +Clippy and Cargo check take a while to run, so doing this async saves us real time. ## When Devin Works and When It Doesn't If you're expecting AI agents to replace developers, you'll be disappointed. We're not there yet. -But if you're looking to meaningfully extend what a small team can accomplish, it's absolutely worth it. +If you're looking to extend what a small team can accomplish, it's worth the spend. **Devin AI is worth the investment when you:** @@ -137,8 +135,8 @@ But if you're looking to meaningfully extend what a small team can accomplish, i - Want it to make architectural decisions - Need it to work in complete isolation without human oversight -After 1,000 tasks, the pattern is clear: You're not buying code generation. You're buying the ability to delegate work and parallelize effort across your team. +After 1,000 tasks, the pattern is clear. You're not buying code generation. You're buying the ability to delegate work and parallelize effort across your team. -The best ROI came from tasks we could delegate async—exploration work at 2 AM, maintenance work during travel, migrations while focusing on core features. The agent didn't replace our judgment; it multiplied our capacity to act on it. +The best ROI came from tasks we could delegate async—exploration at 2 AM, maintenance during travel, migrations while we focus on core features. The agent didn't replace our judgment. It multiplied our capacity to act on it. -**Our recommendation:** Start with one well-defined use case (like automated linting or simple migrations), measure the time saved, then expand. Don't try to use it for everything on day one. +**Our recommendation:** Start with one well-defined use case (automated linting, simple migrations), measure the time saved, then expand. Don't try to use it for everything on day one. diff --git a/apps/web/content/articles/dont-use-a-cms.mdx b/apps/web/content/articles/dont-use-a-cms.mdx index 149b951517..ee7df85c13 100644 --- a/apps/web/content/articles/dont-use-a-cms.mdx +++ b/apps/web/content/articles/dont-use-a-cms.mdx @@ -7,13 +7,13 @@ category: "Engineering" date: "2025-11-29" --- -There's a recurring mistake I see early teams make: they pick a CMS way too early. +Early teams keep making the same mistake: they pick a CMS way too early. -They adopt one because it feels official, not because they need it or because their content is genuinely complicated. It's the same instinct that leads to setting up Jira before you have 5 engineers, or building a microservice before you have users. The structure feels mature and professional. +They adopt one because it feels official, not because they need it or because their content is genuinely complicated. It's the same instinct that leads to Jira before you have 5 engineers, or microservices before you have users. The structure feels mature and professional. But it's unnecessary work that slows you down. -This post is Part 4 of our publishing series, and it's probably the most direct one: don't use a CMS until you actually need one. Most teams don't, and you probably don't either. +This post is Part 4 of our publishing series, and it's the most direct one: don't use a CMS until you actually need one. Most teams don't, and you probably don't either. ## 1. CMS complexity creeps up slowly and quietly @@ -43,7 +43,7 @@ When you're small, content comes from whoever feels like writing that week: foun Early-stage content has one job: communicate clearly, ship quickly, correct later, and move on. -A CMS inverts this. You spend time defining models, configuring schemas, deciding fields, arranging layouts, creating environments, and publishing through abstraction layers. You end up spending more time setting up the publishing pipeline than actually writing. +A CMS inverts this. You spend time defining models, configuring schemas, arranging layouts, creating environments, and publishing through abstraction layers. You end up spending more time on the publishing pipeline than the writing. The best writing stacks make writing feel like thinking. A CMS makes it feel like filing a ticket. @@ -51,13 +51,13 @@ The best writing stacks make writing feel like thinking. A CMS makes it feel lik You don't feel lock-in in Month 1. You feel it in Year 2 when you want to redesign the site, migrate to a new framework, or reduce technical debt—and you discover exports with weird block formats, "portable text" that isn't portable, HTML blobs with inline attributes, API rate limits, broken image references, and schema migrations that silently fail. -That's when your CMS quietly tells you: "By the way, you can't leave." +That's when your CMS quietly tells you "by the way, you can't leave". ## 6. Every CMS introduces a second reality -A CMS creates two worlds: the content world (editor UI, schema models, drafts) and the code world (MDX, components, styling, deployment). You maintain a mental bridge between both, and every change travels across two systems. +A CMS creates two worlds: the content world (editor UI, schema models, drafts) and the code world (MDX, components, styling, deployment). You maintain a mental bridge between them, and every change has to travel across two systems. -Early teams can't absorb that cognitive cost. You need one world, not two. +Early teams can't absorb that cost. You need one world, not two. ## 7. Git + MDX + IDE will take you much further than you think @@ -86,7 +86,7 @@ A CMS is structure. Structure is stability. Stability is for late-stage companie Don't over-engineer your publishing stack. Don't imitate companies 100x your size. Don't install enterprise workflows into a tiny team. -Just write and ship in the simplest system that respects your time. You can add complexity later. You cannot subtract it. +Write and ship in the simplest system that respects your time. You can add complexity later. You can't subtract it. --- @@ -97,5 +97,5 @@ Just write and ship in the simplest system that respects your time. You can add 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) (You are here) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/publishing-stack) +6. [How We Built Anarlog's Publishing Stack](/blog/publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) diff --git a/apps/web/content/articles/enterprise-ai-notetaking-tools.mdx b/apps/web/content/articles/enterprise-ai-notetaking-tools.mdx index 465a673365..00440f8811 100644 --- a/apps/web/content/articles/enterprise-ai-notetaking-tools.mdx +++ b/apps/web/content/articles/enterprise-ai-notetaking-tools.mdx @@ -4,21 +4,20 @@ display_title: "AI Notetaking Tools for Enterprises: 5 Solutions Compared" meta_description: "Compare enterprise AI notetaking solutions on security, compliance, deployment options, and limitations. A practical guide for organizations with strict data policies." author: - "John Jeong" -coverImage: "/api/assets/blog/enterprise-ai-notetaking-tools/cover.png" featured: false category: "Comparisons" date: "2025-10-02" --- -If you're evaluating AI notetaking tools for your organization, you're probably dealing with questions like: "Can our legal team actually use this?" "What happens to our data?" or "Will IT have a meltdown when I suggest this?" +If you're evaluating AI notetaking tools for your organization, you're probably stuck on questions like "can our legal team actually use this?", "what happens to our data?", or "will IT have a meltdown when I suggest this?" -Most AI notetaking tools weren't built for enterprises. They were built for individuals, then scaled up with "enterprise features" bolted on later. This creates predictable friction during procurement: the tool technically works, but it violates data policies, lacks the right deployment options, or creates compliance headaches nobody anticipated. +Most AI notetaking tools weren't built for enterprises. They were built for individuals, then scaled up with "enterprise features" bolted on later. The result is predictable friction during procurement: the tool technically works, but it violates data policies, lacks the right deployment options, or creates compliance headaches nobody anticipated. -This guide cuts through the marketing noise and covers five notetaking solutions that work for enterprises. +This guide covers five notetaking tools that work for enterprises. ## AI Notetakers For Enterprises: At a Glance -1. **Char**: Open-source, zero lock-in AI notepad with complete control over data and AI stack +1. **Anarlog**: Open-source, zero lock-in AI notepad with complete control over data and AI stack 2. **Read AI**: Cloud platform with unified search across meetings, emails, and messages 3. **MeetGeek**: Conversation intelligence with team-level analytics and performance tracking 4. **Fellow**: Internal meeting focus with granular privacy controls for manager workflows @@ -28,60 +27,60 @@ This guide cuts through the marketing noise and covers five notetaking solutions We evaluated dozens of tools and narrowed the list to five based on what procurement teams actually need to see: -- **Security and compliance readiness**: Does the tool have the certifications, architectural design, or deployment options that let it pass your legal and security review? +- **Security and compliance readiness**: Does the tool have the certifications, architecture, or deployment options that pass your legal and security review? - **Administrative control**: We prioritized tools with SSO, role-based permissions, audit trails, user provisioning, and data retention policies that work across hundreds or thousands of users. - **Integration depth**: Does it plug into your existing stack (CRMs, collaboration tools, document repos)? We focused on tools that meet teams where they work. - **Production-proven**: We prioritized tools already deployed at Fortune 500 companies, healthcare systems, legal firms, and government agencies. -The five tools we're covering represent fundamentally different architectural approaches. If one doesn't fit your requirements, another likely will. +These five tools take genuinely different architectural approaches. If one doesn't fit your requirements, another likely will. ## Top Enterprise AI Notetaking Solutions: Detailed Reviews -### 1. Char +### 1. Anarlog ![Best ai note taker for enteprises](/api/assets/blog/enterprise-ai-notetaking-tools/enterprise-1.webp) -[Char](/) is an open-source AI notepad for meetings built for high-agency organizations that demand complete control over their data, AI stack, and workflow. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, bring your own keys, or run local models. Zero lock-in fundamentally changes what's possible from both a compliance and ownership perspective. +[Anarlog](/) is an open-source AI notepad for meetings built for organizations that demand complete control over their data, AI stack, and workflow. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: bring your own keys or run local models. Zero lock-in changes what's possible on both compliance and ownership. #### Deployment Options -- **Edge-only**: All processing happens locally with zero external connections—maximum creative IP protection -- **All-prem**: Complete organizational control with all components running on your infrastructure -- **Prem-hybrid**: STT and database on-premises while LLM workloads run in controlled environments—balances compliance with powerful AI capabilities +- **Edge-only**: All processing happens locally with zero external connections—maximum IP protection +- **All-prem**: Every component runs on your infrastructure +- **Prem-hybrid**: STT and database on-premises while LLM workloads run in controlled environments—balances compliance with stronger AI capabilities #### Pricing -Char is free forever for local transcription, BYOK, and all core features. Managed cloud service is $25/month for the easiest setup. +Anarlog is free forever, fully local, BYOK, and open source. -Enterprise pricing applies when your organization needs an on-premises deployment, SSO integration, consent management, multi-factor authentication, an agentic workflow builder, 24/7 priority support, or custom branding. [Contact sales for a quote](/founders). +Enterprise pricing applies when your organization needs an on-premises deployment, SSO integration, consent management, multi-factor authentication, an agentic workflow builder, 24/7 priority support, or custom branding. [Contact sales for a quote](https://char.com). #### Enterprise Features -- **End-to-end encryption**: Zero-trust architecture with hardware security module support protects data at every stage—from capture to storage to processing. -- **Multi-framework compliance**: Built-in support for HIPAA, SOC 2, ISO 27001, GDPR, and CCPA without requiring new certifications or security reviews. Works with existing compliance frameworks. -- **Smart consent management**: Built-in recording consent collection with audit logs for all acknowledgments, ensuring compliance with one-party, two-party, or all-party consent laws. -- **Enterprise authentication**: SSO (SAML 2.0/OIDC) and Multi-Factor Authentication (MFA) with seamless integration into your existing identity provider. -- **Access management**: Advanced role-based permissions and granular controls for managing who can record, access, and share meeting data across the organization. -- **Audit and monitoring**: Comprehensive audit trails and real-time monitoring track all access and usage, with a custom admin dashboard builder for visibility. -- **Team collaboration**: Securely share notes with team-level controls, ensuring information flows to the right people while maintaining governance. -- **Universal meeting compatibility**: Works with all platforms (Zoom, Teams, Meet, etc.) without requiring bots—captures both microphone and system audio directly. -- **Vendor-agnostic AI**: Connect to OpenAI, Mistral, Ollama (via LM Studio), or any custom endpoint—no vendor lock-in for LLM processing. -- **Workflow integrations**: Current integrations with Obsidian and Attio, with enterprise customers driving additional integration priorities based on their tech stack. +- **End-to-end encryption**: Zero-trust architecture with hardware security module support protects data from capture to storage to processing. +- **Multi-framework compliance**: HIPAA, SOC 2, ISO 27001, GDPR, and CCPA support without requiring new certifications or security reviews. Works with your existing compliance frameworks. +- **Smart consent management**: Recording consent collection with audit logs for every acknowledgment, covering one-party, two-party, and all-party consent laws. +- **Enterprise authentication**: SSO (SAML 2.0/OIDC) and MFA that integrate with your existing identity provider. +- **Access management**: Role-based permissions and granular controls for who can record, access, and share meeting data. +- **Audit and monitoring**: Audit trails and real-time monitoring across all access and usage, with a custom admin dashboard builder. +- **Team collaboration**: Share notes with team-level controls so information flows to the right people without losing governance. +- **Universal meeting compatibility**: Works with Zoom, Teams, Meet, and others without bots—captures microphone and system audio directly. +- **Vendor-agnostic AI**: Connect to OpenAI, Mistral, Ollama (via LM Studio), or any custom endpoint. No vendor lock-in for LLM processing. +- **Workflow integrations**: Obsidian and Attio today, with enterprise customers driving the next integrations based on their stack. - **Custom model support**: Bring your own speech-to-text and language models, plus calendar integration for automatic meeting detection. #### How it works -Char uses two types of models: speech-to-text (STT) and large language models (LLMs). +Anarlog uses two types of models: speech-to-text (STT) and large language models (LLMs). -For transcription, Char uses the Whisper series from Hugging Face, which ranges from the lightweight Tiny and Base models to the V3 Large Turbo. Users choose based on their accuracy vs. speed requirements. +For transcription, Anarlog uses the Whisper series from Hugging Face, ranging from the lightweight Tiny and Base models to V3 Large Turbo. You pick based on your accuracy vs. speed requirements. -For summarization and intelligence, Char developed HyperLLM-V1, a custom model based on Qwen 1.7B architecture. At just 1.1GB (compared to LLaMA's 2.0GB), it delivers high-quality summaries optimized for English while being extremely fast. +For summarization, Anarlog ships HyperLLM-V1, a custom model based on Qwen 1.7B. At 1.1GB (vs. LLaMA's 2.0GB), it produces fast, English-tuned summaries. -All of this happens locally. Char captures both microphone input and system audio output, allowing it to work universally across various meeting platforms without requiring bots. No internet required for core functionality. +All of this runs locally. Anarlog captures both microphone input and system audio output, so it works across meeting platforms without bots. No internet needed for core functionality. -Your transcription and note data never touch external servers unless you explicitly choose to connect an LLM provider for additional features. Speaker identification works automatically for one-on-one calls by distinguishing between the microphone and system audio. +Your transcription and note data never touch external servers unless you explicitly connect an LLM provider. Speaker identification works automatically for one-on-one calls by distinguishing microphone from system audio. -For group meetings, users manually tag speakers using the transcript editor. Real-time transcription is supported with latency varying based on model size and hardware capabilities. Export options include Markdown, PDF, and Rich Text. +For group meetings, you tag speakers manually in the transcript editor. Real-time transcription latency depends on model size and hardware. Exports: Markdown, PDF, Rich Text. #### Limitations & Trade-offs @@ -95,7 +94,7 @@ For group meetings, users manually tag speakers using the transcript editor. Rea ![Read AI for enterprises](/api/assets/blog/enterprise-ai-notetaking-tools/enterprise-4.webp) -[Read AI](https://www.read.ai) is a cloud-based AI copilot that transforms meetings, emails, and messages into searchable insights across your entire workspace. It functions as a unified intelligence layer connecting all your communication platforms. +[Read AI](https://www.read.ai) is a cloud-based AI copilot that turns meetings, emails, and messages into searchable insights across your workspace. It's a unified layer across your communication platforms. **Deployment options**: Cloud-based (SaaS only) @@ -124,7 +123,7 @@ Read AI joins meetings as a visible bot that announces itself and requires host ![meetgeek for enterprises](/api/assets/blog/enterprise-ai-notetaking-tools/enterprise-5.webp) -[MeetGeek](https://meetgeek.ai/) is a cloud-based conversation intelligence platform focused on tracking meeting performance across teams and departments. It emphasizes analytics, measuring things like engagement, sentiment, and meeting effectiveness at an organizational scale. +[MeetGeek](https://meetgeek.ai/) is a cloud-based conversation intelligence platform that tracks meeting performance across teams and departments. The focus is analytics: engagement, sentiment, and meeting effectiveness at organizational scale. **Deployment options**: Cloud-based (SaaS), with private data storage on Enterprise @@ -155,7 +154,7 @@ Bot-based assistant with automatic language detection across 100+ languages and ![fellow ai for enterprises](/api/assets/blog/enterprise-ai-notetaking-tools/enterprise-6.webp) -[Fellow](https://fellow.ai) is a cloud-based meeting assistant designed around internal meeting workflows, particularly manager tools like one-on-ones, team meetings, and collaborative agendas. Its distinguishing feature is granular recording controls for internal conversations. +[Fellow](https://fellow.ai) is a cloud-based meeting assistant built around internal workflows—one-on-ones, team meetings, collaborative agendas. The distinguishing feature is granular recording controls for internal conversations. **Deployment options**: Cloud-based (SaaS) @@ -190,7 +189,7 @@ Fellow operates as a bot that joins meetings to record and transcribe. It suppor ![plaud](/api/assets/blog/enterprise-ai-notetaking-tools/enterprise-7.webp) -[Plaud](https://www.plaud.ai) takes a completely different approach: hardware-first AI notetaking. Instead of bots joining meetings, you get physical devices that capture conversations anywhere, anytime. +[Plaud](https://www.plaud.ai) takes a different approach: hardware-first AI notetaking. Instead of bots joining meetings, you get physical devices that capture conversations anywhere. **Deployment options**: Cloud-based with hardware devices @@ -198,14 +197,14 @@ Fellow operates as a bot that joins meetings to record and transcribe. It suppor #### Enterprise Features -- **Hardware-based capture**: Plaud leverages dedicated sensors to capture conversations in multiple scenarios, unlocking previously untapped real-life data. -- **Security & compliance**: HIPAA and SOC 2 compliant with GDPR and EN18031 certification. End-to-end encryption with custom data storage options for where your meeting data lives. -- **Multi-language transcription**: AI transcription in 112 languages with automatic speaker labels and custom vocabulary that learns industry-specific terminology for better accuracy. -- **Template library**: Over 3,000+ summary templates for generating multidimensional summaries, mind maps, and structured outputs for different meeting types and use cases. -- **Ask Plaud AI**: Chatbot provides timestamped insights across all recordings—query meetings in natural language and get answers with exact timestamps to the original audio. -- **Cross-platform access**: Record on physical devices, then access transcripts and summaries on the mobile app, web browser, and desktop with unlimited cloud storage. -- **Dual-mode recording**: One-button switch between phone call recording mode and in-person conversation mode for different capture scenarios. -- **Multimodal input**: Combine audio recording with typed notes, images, and press-to-highlight key moments for richer context and documentation. +- **Hardware-based capture**: Dedicated sensors capture conversations across scenarios that software bots can't reach. +- **Security & compliance**: HIPAA and SOC 2 compliant with GDPR and EN18031 certification. End-to-end encryption with custom data storage options. +- **Multi-language transcription**: 112 languages with automatic speaker labels and custom vocabulary for industry terminology. +- **Template library**: 3,000+ summary templates for summaries, mind maps, and structured outputs across meeting types. +- **Ask Plaud AI**: Chatbot returns timestamped insights across recordings—query in natural language, get answers with exact timestamps to the original audio. +- **Cross-platform access**: Record on the device, then access transcripts and summaries on mobile, web, and desktop with unlimited cloud storage. +- **Dual-mode recording**: One-button switch between phone call mode and in-person mode. +- **Multimodal input**: Audio plus typed notes, images, and press-to-highlight key moments. #### How it works @@ -219,12 +218,12 @@ Hardware sensors capture high-quality audio locally (up to 64GB) then sync to cl - **Manual device management**: Remember to carry, charge, and position devices - **Less convenient for virtual-only meetings** compared to automatic software bots -## Why Choose Char +## Why Choose Anarlog -Every enterprise has unique compliance requirements, workflow needs, and technical constraints. Char's flexible architecture adapts to all three. Whether you need edge-only deployment for maximum IP protection, all-prem for complete infrastructure control, or prem-hybrid to balance compliance with powerful AI, there's a configuration that fits. +Every enterprise has different compliance requirements, workflows, and technical constraints. Anarlog's architecture adapts to all three. Edge-only for maximum IP protection, all-prem for complete infrastructure control, or prem-hybrid to balance compliance with stronger AI—pick the configuration that fits. Our team works with Fortune 500 companies, healthcare systems, legal firms, financial institutions, and government agencies to deploy AI notetaking that fits their security posture. -Ready to see how zero-lock-in AI works in practice? [Contact Sales](/founders) to discuss your specific needs, explore deployment options, and schedule a demo tailored to your organization's use cases. +Want to see how zero-lock-in AI works in practice? [Contact Sales](https://char.com) to discuss your needs, explore deployment options, and schedule a demo for your use cases.   diff --git a/apps/web/content/articles/fathom-ai-alternatives.mdx b/apps/web/content/articles/fathom-ai-alternatives.mdx index 4c3bac52df..2b659ac20a 100644 --- a/apps/web/content/articles/fathom-ai-alternatives.mdx +++ b/apps/web/content/articles/fathom-ai-alternatives.mdx @@ -2,16 +2,15 @@ meta_title: "5 Best Fathom AI Alternatives in 2026" meta_description: "Discover the 5 best alternatives to Fathom AI in 2026. Get better conversation insights, stronger security, and truly unlimited note-taking." author: "John Jeong" -coverImage: "/api/assets/blog/fathom-ai-alternatives/cover.png" category: "Comparisons" date: "2025-10-15" --- -If bots make you nervous, privacy is on your mind, and you want more than simple note-taking and unlimited free recordings, you should look beyond Fathom AI. +If bots make you nervous, privacy is on your mind, and you want more than transcription with unlimited free recordings, look beyond Fathom AI. ## Quick Fathom AI review -I tested Fathom AI for weeks. It does what it says on the tin—a meeting bot that transcribes your Zoom, Meet, or Teams calls and generates summaries in about 30 seconds after the call ends. The CRM sync works well for sales teams. +I tested Fathom AI for weeks. It does what it says on the tin: a meeting bot that transcribes your Zoom, Meet, or Teams calls and generates summaries about 30 seconds after the call ends. The CRM sync works well for sales teams. But the limitations become apparent quickly: @@ -20,33 +19,33 @@ But the limitations become apparent quickly: - **The desktop app is just a recording widget.** There's nowhere to write down your thoughts during the meeting. - **No real-time transcription.** You're flying blind during calls, and it doesn't work without internet. Dead WiFi means a dead tool. - **The bot is always visible.** For sensitive client calls or therapy sessions, having "Fathom Notetaker" in your participant list changes the dynamic. -- **The unlimited free plan is actually quite limited.** You get unlimited transcription and recordings, but AI summaries cap out at five per month. +- **The unlimited free plan isn't really unlimited.** You get unlimited transcription and recordings, but AI summaries cap at five per month. If any of that bothers you, here are the top Fathom AI alternatives. ## Top Fathom AI alternatives: quick comparison -| Tool | Best For | Starting Price | +| Tool | Best For | Starting Price | | --------- | -------------------------------------------------- | ------------------------------- | -| Char | Zero lock-in, complete control | Free forever (local + BYOK). Cloud: $25/mo | -| Avoma | Sales teams needing coaching & deal intelligence | $19/mo base ($54-109 realistic) | -| Read AI | Teams wanting unified search across communications | $19.75/mo | -| Fireflies | Teams needing deep conversation analytics | Free (Pro: $18/mo) | -| Sembly AI | Enterprise compliance certifications | $15/mo | +| Anarlog | Zero lock-in, complete control | Free forever, fully local + BYOK. | +| Avoma | Sales teams needing coaching & deal intelligence | $19/mo base ($54-109 realistic) | +| Read AI | Teams wanting unified search across communications | $19.75/mo | +| Fireflies | Teams needing deep conversation analytics | Free (Pro: $18/mo) | +| Sembly AI | Enterprise compliance certifications | $15/mo | ## 5 best Fathom alternatives: detailed reviews -### 1. Char: For When Control Actually Matters +### 1. Anarlog: For When Control Actually Matters -Full disclosure—I'm the co-founder of [Char](/). I built it because every other tool locked you into their cloud, their models, their rules. No way to switch, no way out. +Full disclosure—I'm the co-founder of [Anarlog](/). I built it because every other tool locked you into their cloud, their models, their rules. No way to switch, no way out. -Char is an open-source AI notepad for meetings. Everything is stored as plain markdown files on your device—not in proprietary databases. You choose which AI processes your data: managed cloud, bring your own API keys, or run local models. +Anarlog is an open-source AI notepad for meetings. Everything is stored as plain markdown files on your device, not in proprietary databases. You choose which AI processes your data: bring your own API keys or run local models. -You get a real notepad interface. Open Char and you see a notepad. Type notes during meetings, and the AI enhances them with context from the transcript. Real-time transcription appears while people are talking, not after. +The interface is a real notepad. Open Anarlog and you see a notepad. Type during meetings, and the AI enhances your notes with context from the transcript. Real-time transcription appears while people are talking, not after. **Best For:** Engineers, developers, and technical founders. Privacy-conscious professionals in healthcare, legal, and finance. People who already use tools like Obsidian or local-first workflows. Anyone whose company has banned cloud tools like Otter or Granola. -Char vs Fathom +Anarlog vs Fathom #### Key features @@ -78,15 +77,13 @@ You get a real notepad interface. Open Char and you see a notepad. Type notes du #### Pricing -Free forever for local transcription, BYOK, and all core features. No usage caps, no artificial limits. Managed cloud service is $25/month for the easiest setup. Enterprise pricing covers on-prem deployment, smart consent management, and SSO—[contact sales for a quote](/founders). - - +Free forever for local transcription, BYOK, and all core features. No usage caps, no artificial limits. Enterprise pricing covers on-prem deployment, smart consent management, and SSO—[contact sales for a quote](https://char.com). ### 2. Avoma: if you're serious about sales -[Avoma](https://www.avoma.com) is a complete revenue intelligence platform that actually helps your sales team improve. +[Avoma](https://www.avoma.com) is a revenue intelligence platform that actually helps your sales team improve. -It joins meetings as a bot, but unlike Fathom's basic summaries, it automatically structures notes around what matters for sales—pain points, budget, decision criteria, next steps. If you're using MEDDIC or SPICED frameworks, it populates those fields automatically. +It joins meetings as a bot, but unlike Fathom's basic summaries, it structures notes around what matters for sales—pain points, budget, decision criteria, next steps. If you're using MEDDIC or SPICED, it populates those fields automatically. **Best For:** Sales teams needing coaching analytics, deal intelligence, and automated CRM updates. Works best for teams of 10+ reps who need scalable coaching. @@ -127,9 +124,9 @@ Starts at $19/user/month base, but you'll need add-ons ($35-70/user/month) for c ### 3. Read AI: the everything-everywhere tool -[Read AI](https://www.read.ai) takes a completely different approach than Fathom. Instead of just doing meetings, it connects meetings, emails, and messages into one searchable knowledge base. +[Read AI](https://www.read.ai) takes a different approach than Fathom. Instead of just meetings, it connects meetings, emails, and messages into one searchable knowledge base. -Read's Search Copilot works across everything. Ask "What did the engineering team say about the API redesign?" and it pulls answers from meetings, Slack threads, and email chains. This is genuinely useful when you're piecing together decisions made across multiple channels. +Read's Search Copilot works across all of it. Ask "what did the engineering team say about the API redesign?" and it pulls answers from meetings, Slack threads, and email chains. Genuinely useful when you're piecing together decisions made across multiple channels. **Best For:** Teams who need unified search across all communications, not just meetings. Works well for product teams, executives, and anyone coordinating across multiple platforms. @@ -172,7 +169,7 @@ Free plan offers 5 meetings per month with basic features. Paid plans start at $ [Fireflies.ai](https://fireflies.ai) has been around longer than most competitors, and it shows in the analytics depth. -The conversation analytics go way deeper than Fathom. Speaker talk time, sentiment analysis, topic tracking, even monitoring for specific keywords across all calls. If you're tracking how often competitors get mentioned or what objections keep coming up, Fireflies surfaces those patterns automatically. +Conversation analytics go deeper than Fathom: speaker talk time, sentiment analysis, topic tracking, keyword monitoring across all calls. If you're tracking how often competitors get mentioned or what objections keep coming up, Fireflies surfaces those patterns automatically. **Best For:** Teams needing conversation analytics, sales teams tracking objections, product teams monitoring feature requests, or anyone managing high meeting volume. @@ -219,7 +216,7 @@ Free plan includes unlimited transcription with 800 minutes of storage and limit [Sembly AI](https://www.sembly.ai) built its reputation on one thing: meeting the compliance requirements that make procurement teams happy. SOC 2 Type II, HIPAA, GDPR—Sembly checks boxes that most meeting assistants ignore. -Fathom gives you basic summaries and expects you to handle compliance yourself. Sembly was designed from the ground up for regulated industries. Healthcare, finance, legal—teams that can't afford to mess around with data security. +Fathom gives you basic summaries and expects you to handle compliance yourself. Sembly was designed for regulated industries from the start. Healthcare, finance, legal—teams that can't afford to mess around with data security. **Best For:** Enterprise organizations needing strict compliance, regulated industries (healthcare, finance, legal), teams building secure knowledge bases, or anyone where HIPAA/SOC 2 certification isn't optional. @@ -249,7 +246,7 @@ Fathom gives you basic summaries and expects you to handle compliance yourself. - Bot-based recording affects meeting dynamics like Fathom - Steeper learning curve than simpler alternatives—more features means more complexity -- Cloud-only processing—no on-premises option like Char +- Cloud-only processing—no on-premises option like Anarlog - Limited export options—no direct Word document export for summaries - Higher price point than Fathom for full feature access - Requires stable internet connection—no offline mode like Fathom @@ -260,14 +257,13 @@ Free plan offers 60 minutes of transcription per month. Paid plans start at $15/ ## Which is the best Fathom AI alternative? -**If control and zero lock-in matter, use Char.** Plain markdown files, your choice of AI stack, and complete ownership. Engineers, developers, privacy-conscious professionals in healthcare and legal, or anyone whose company has banned cloud tools—Char gives you ownership at the foundational level. You also get real-time transcription and offline support that Fathom can't match. +**If control and zero lock-in matter, use Anarlog.** Plain markdown files, your choice of AI stack, complete ownership. Engineers, developers, privacy-conscious professionals in healthcare and legal, or anyone whose company has banned cloud tools—Anarlog gives you ownership at the foundational level. Real-time transcription and offline support that Fathom can't match. -**If you're running a sales team and budget allows, go with Avoma.** The automated CRM updates and call scoring actually improve team performance. The coaching analytics are worth the price if you're serious about revenue. Budget realistically—you'll need $70-110/user/month for features you actually want. +**If you're running a sales team and budget allows, go with Avoma.** The automated CRM updates and call scoring actually move performance. The coaching analytics are worth the price if you're serious about revenue. Budget realistically—you'll need $70-110/user/month for the features you actually want. -**If you need unified search across all communications, Read AI is worth exploring.** The ability to query meetings, emails, and Slack in one place is genuinely useful for product teams and executives tracking decisions across channels. Negotiate the Pro pricing—don't accept the first quote. +**If you need unified search across all communications, Read AI is worth exploring.** Querying meetings, emails, and Slack in one place is genuinely useful for product teams and executives tracking decisions across channels. Negotiate the Pro pricing—don't accept the first quote. -**If you want conversation analytics on a budget, Fireflies works.** The unlimited free transcription beats Fathom's 5-summary cap, and the pattern recognition across meetings is deeper. Watch the storage limits and disable auto-sharing to all participants. +**If you want conversation analytics on a budget, Fireflies works.** Unlimited free transcription beats Fathom's 5-summary cap, and pattern recognition across meetings is deeper. Watch the storage limits and disable auto-sharing. -**If compliance certifications are required, Sembly is your answer.** SOC 2, HIPAA, GDPR out of the box. Regulated industries can't use tools without these certifications, and Sembly was built for that purpose. +**If compliance certifications are required, Sembly is your answer.** SOC 2, HIPAA, GDPR out of the box. Regulated industries can't use tools without these, and Sembly was built for it. - diff --git a/apps/web/content/articles/filesystem-is-coretex.mdx b/apps/web/content/articles/filesystem-is-coretex.mdx index fcc91d4ed4..fdae48269b 100644 --- a/apps/web/content/articles/filesystem-is-coretex.mdx +++ b/apps/web/content/articles/filesystem-is-coretex.mdx @@ -1,7 +1,7 @@ --- meta_title: "Why Your Notes Should Live as Files, Not Database Rows" display_title: "The Filesystem Is the Cortex" -meta_description: "Why file-based systems matter in the AI era, and how Char fits into the future of note-taking." +meta_description: "Why file-based systems matter in the AI era, and how Anarlog fits into the future of note-taking." author: "John Jeong" category: "Founders' notes" date: "2025-12-30" @@ -9,17 +9,17 @@ date: "2025-12-30" Most businesses win not because they have more features, but because they communicate a clearer philosophy—a picture of the future that some people want to live inside. -In older eras, religion acted as the gravity of communities. Today, we're still pulled by gravity, just different kinds: interests, crafts, identities, missions. People want to become good at something. They want to enjoy something deeply. They want to find tools that make their journey feel coherent. +In older eras, religion was the gravity of communities. Today, we're still pulled by gravity, just different kinds: interests, crafts, identities, missions. People want to get good at something. They want to enjoy it. They want tools that make the journey feel coherent. -A startup's job is to make a specific journey possible and to make it feel inevitable. +A startup's job is to make a specific journey possible and make it feel inevitable. ## Notes Are Not Productivity. They're Mind Extension. -Note-taking is one of the most underestimated categories in software because it looks simple. But notes aren't documents or content. They're closer to memory, and memory is closer to identity. A note-taking system is an extension of your mind: a place where thoughts land, take shape, connect, and return later as insight. +Note-taking is one of the most underestimated categories in software because it looks simple. But notes aren't documents or content. They're closer to memory, and memory is closer to identity. A note-taking system is an extension of your mind—a place where thoughts land, take shape, connect, and come back later as insight. -The mind operates in modes. There's private thinking—personal thoughts, half-formed ideas, internal reflections. There's work mode—decisions, planning, collaboration, outcomes. There's publishing mode—writing for other people, making ideas legible, shipping something public. +The mind operates in modes. Private thinking is personal thoughts, half-formed ideas, internal reflection. Work mode is decisions, planning, collaboration, outcomes. Publishing mode is writing for other people, making ideas legible, shipping something public. -Good note-taking tools don't force those modes into a single abstraction. They let them coexist. +Good note-taking tools don't force those modes into one abstraction. They let them coexist. This is why many serious note-takers end up with multiple tools: @@ -33,21 +33,21 @@ These aren't competing products. They're compatible because they share a common File-based systems are a cognitive model, not a relic. Humans have organized information spatially for centuries: drawers, cabinets, boxes, folders. We remember where something is. Location is recall. Structure is memory. -This is why the terminal still feels natural. `ls`, `pwd`, `grep`—these commands mirror how people already understand information. There's a place, there's a name, there's content, there's a way to find it. Computers didn't invent this model. They adopted it. +That's why the terminal still feels natural. `ls`, `pwd`, `grep` mirror how people already understand information. There's a place, there's a name, there's content, there's a way to find it. Computers didn't invent this model. They adopted it. -When we talk about "local-first" and "file-based," we're discussing something beyond implementation: trust. A note-taking system is a memory prosthetic. Memory needs to feel owned, reliable, and genuinely yours. When notes live as files, they feel like part of you. When notes live as opaque blobs behind an API, they feel like someone else's product. +When we talk about "local-first" and "file-based", we're talking about trust, not implementation. A note-taking system is a memory prosthetic. Memory has to feel owned, reliable, and yours. When notes live as files, they feel like part of you. When notes live as opaque blobs behind an API, they feel like someone else's product. ## Why AI Works So Well in Coding (and Why Writing Is Following) -Coding agents feel effective right now partly because models are capable, but also because code is file-based. Software lives as artifacts—foo.tsx, bar.py, main.rs, README.md—not rows in a database or JSON stuffed inside Postgres fields. Real files with real names, clear structure, readable content. +Coding agents feel effective right now partly because the models are capable, but mostly because code is file-based. Software lives as artifacts—foo.tsx, bar.py, main.rs, README.md—not rows in a database or JSON stuffed into Postgres fields. Real files with real names, clear structure, readable content. AI agents can traverse repositories, read context, understand conventions, search across files, and generate changes that fit the existing structure. The agent sees the artifact, diffs it, rewrites it, reasons about it. The best coding tools work because they're not inventing a new abstraction. They ride the filesystem—the same abstraction humans have used to manage complexity for decades. -Writing is following the same pattern. People draft blog posts and essays inside coding environments. It seems odd on the surface, but writing and coding are closer than we admit. In both cases you're composing, shaping intent into an artifact, embedding values into structure, turning a vague thought into something executable—either by machines (code) or by humans (writing). +Writing is following the same path. People draft blog posts and essays inside coding environments. It seems odd on the surface, but writing and coding are closer than we admit. In both, you're composing, shaping intent into an artifact, embedding values into structure, turning a vague thought into something executable—by machines (code) or by humans (writing). -"Agents for writing" are happening inside code tools because those tools are file-native. They treat writing as a first-class artifact, not as a database record. +"Agents for writing" are happening inside code tools because those tools are file-native. They treat writing as a first-class artifact, not a database record. ## The Endgame: Less Folder Worship, More Meaning @@ -55,22 +55,22 @@ The future probably isn't "more folders." Folders are useful as a spatial index In the AI era, titles matter less than content. Location matters less than meaning. The real organizing principle will shift toward content semantics, metadata, search, retrieval, and links between ideas. The filesystem remains the substrate. The intelligence layer sits on top. -## Where Char Fits +## Where Anarlog Fits -In the note-taking ecosystem, Obsidian communicates a clearer picture of the future than most. It's an incredible interface on top of a file-based knowledge base, and it has influenced how an entire generation thinks about notes. I've subscribed for years because of what it represents, not because of one feature. +In the note-taking ecosystem, Obsidian communicates a clearer picture of the future than most. It's a great interface on top of a file-based knowledge base, and it has shaped how a generation of users thinks about notes. I've paid for it for years because of what it represents, not because of one feature. -We don't want to replace Obsidian. We want to be part of the same movement: tools that respect files, respect ownership, and respect the mind. +We don't want to replace Obsidian. We want to be part of the same movement: tools that respect files, ownership, and the mind. -Char is built to become the best interface for meeting notes and a connector across the full meeting workflow. Before the meeting: context, agenda, prep. During: capture, audio, real-time structure. After: summaries, decisions, action items, follow-ups. Eventually, a command center where you can direct AI instead of being replaced by it. +Anarlog is built to be the best interface for meeting notes and a connector across the full meeting workflow. Before the meeting: context, agenda, prep. During: capture, audio, real-time structure. After: summaries, decisions, action items, follow-ups. Eventually, a command center where you direct AI instead of being replaced by it. -Because Char is file-based and local-first, it can sit alongside your existing vault. It can read from your knowledge base and write back into it. It treats meetings as a specialized region of your brain—deeply integrated, but not controlling everything else. +Because Anarlog is file-based and local-first, it can sit alongside your existing vault. It reads from your knowledge base and writes back into it. It treats meetings as a specialized region of your brain—deeply integrated, but not controlling everything else. -That's the direction that matters: building a tool that fits into the cortex, not another walled garden. +That's the direction that matters: a tool that fits into the cortex, not another walled garden. ## Closing -This isn't nostalgia for old-school files. It's a belief about the future: if AI is going to help humans think, it needs access to artifacts humans can own, inspect, move, and understand. Markdown, plain text, files. +This isn't nostalgia for old-school files. It's a belief about the future. If AI is going to help humans think, it needs access to artifacts humans can own, inspect, move, and understand. Markdown, plain text, files. -The filesystem is the cortex. In the AI era, it becomes even more important, not less. +The filesystem is the cortex. In the AI era, it becomes more important, not less. -Char is our contribution to that future: a meeting-native interface inside a file-native world. \ No newline at end of file +Anarlog is our contribution to that future: a meeting-native interface inside a file-native world. \ No newline at end of file diff --git a/apps/web/content/articles/fireflies-ai-alternatives.mdx b/apps/web/content/articles/fireflies-ai-alternatives.mdx index e184aba77d..fc15eea490 100644 --- a/apps/web/content/articles/fireflies-ai-alternatives.mdx +++ b/apps/web/content/articles/fireflies-ai-alternatives.mdx @@ -2,16 +2,15 @@ meta_title: "9 Best Fireflies.ai Alternatives in 2026" meta_description: "Complete Fireflies.ai review plus 9 alternative comparisons. Compare AI meeting assistants by features, pricing, privacy, and platform support." author: "Harshika" -coverImage: "/api/assets/blog/fireflies-ai-alternatives/cover.png" category: "Comparisons" date: "2025-09-23" --- Fireflies is easy to adopt, which is why so many teams try it first. -It is also one of those products that feels different after the novelty wears off. The complaints are consistent: a bot people do not always want in the room, pricing that gets messy, multilingual performance that sounds better on the landing page than in practice, and upsells that show up once your team depends on it. +It's also one of those products that feels different once the novelty wears off. The complaints are consistent: a bot people don't always want in the room, pricing that gets messy, multilingual performance that sounds better on the landing page than in practice, and upsells that show up once your team depends on it. -So this list is for a specific buyer. Not someone new to the category. Someone who already understands what Fireflies gets right and wants a better trade-off. +This list is for a specific buyer—not someone new to the category, but someone who already knows what Fireflies gets right and wants a better trade-off. ## Quick Fireflies AI review @@ -21,25 +20,25 @@ Fireflies is an AI meeting assistant that automatically joins your Zoom, Google ### The good stuff -**Setup is genuinely easy:** Connect your calendar, and the bot starts auto-joining your scheduled meetings without any extra work from you. For people managing tons of meetings, this automation is a significant timesaver. +**Setup is genuinely easy:** Connect your calendar and the bot starts auto-joining your scheduled meetings without extra work. For people managing tons of meetings, the automation is a real timesaver. -**Transcription quality is decent:** In good conditions—clear audio, minimal background noise—you're looking at around 85-90% accuracy. That's solid enough to capture the gist of conversations and make them searchable later. +**Transcription quality is decent:** In good conditions—clear audio, minimal background noise—you get 85-90% accuracy. Solid enough to capture the gist of conversations and make them searchable later. -**AI summaries save time:** When they work well, the auto-generated summaries do capture key discussion points and action items. No more scrambling to remember what was decided three meetings ago. +**AI summaries save time:** When they work, the auto-generated summaries do capture key discussion points and action items. No more scrambling to remember what was decided three meetings ago. -**The search feature is actually useful:** Being able to type in keywords and instantly find where specific topics were discussed months ago is valuable, especially for sales teams tracking client conversations over time. +**The search feature is actually useful:** Typing in keywords and instantly finding where a topic was discussed months ago matters, especially for sales teams tracking client conversations. -**Integrations work well:** If you're using a CRM, having meeting notes automatically sync there saves a ton of manual work. The Slack integration is particularly handy for team updates. +**Integrations work well:** If you use a CRM, having meeting notes sync automatically saves a lot of manual work. The Slack integration is handy for team updates. -**Mobile app is convenient:** You can record and transcribe conversations on the go, which is useful for interviews or in-person meetings. +**Mobile app is convenient:** Record and transcribe on the go, useful for interviews or in-person meetings. ### Where things get messy -**The billing situation is concerning:** Multiple users report being charged for plans they didn't knowingly sign up for. The trial cancellation process seems deliberately confusing, and many people only discover unexpected charges when reviewing their credit card statements. Some users even mention being unable to remove their payment information from the platform. +**The billing situation is concerning:** Multiple users report being charged for plans they didn't knowingly sign up for. The trial cancellation process seems deliberately confusing, and people often discover unexpected charges only when reviewing their credit card statements. Some users report being unable to remove their payment information from the platform. Fireflies 1 -**Privacy issues are real:** By default, Fireflies shares meeting summaries with all attendees without asking first. You have to manually opt out of this behavior. For sensitive business discussions, that's problematic. We've covered [Fireflies' safety and data practices in detail](/blog/is-fireflies-ai-safe/). Even more concerning, several users report the bot continuing to join meetings even after they've cancelled their accounts and deleted everything. +**Privacy issues are real:** By default, Fireflies shares meeting summaries with all attendees without asking first. You have to manually opt out. For sensitive business discussions, that's a problem. We've covered [Fireflies' safety and data practices in detail](/blog/is-fireflies-ai-safe/). Worse, several users report the bot continuing to join meetings after they've cancelled their accounts and deleted everything. Fireflies 2 @@ -47,43 +46,43 @@ Fireflies is an AI meeting assistant that automatically joins your Zoom, Google Fireflies 3 -**Hidden costs add up quickly:** Those fancy AI features like smart summaries and action items require additional "AI credits" that aren't included in your base subscription. What looks like a $29/month Business plan can easily become much more expensive once you factor in the credits needed for core functionality. +**Hidden costs add up quickly:** AI features like smart summaries and action items require "AI credits" that aren't included in your base subscription. A $29/month Business plan gets expensive fast once you factor in the credits needed for core functionality. Fireflies 4 -**Aggressive upselling tactics:** Users report extremely aggressive upselling tactics for non-pro accounts. The combination of storage restrictions and AI credits creates constant pressure to upgrade to higher-tier plans. +**Aggressive upselling tactics:** Users report aggressive upsells on non-pro accounts. The combination of storage restrictions and AI credits creates constant pressure to upgrade. Fireflies 5 ### The bottom line on Fireflies -Fireflies does what it promises for basic transcription and automation, especially if you're working in English and don't mind having a bot in your meetings. But between the billing issues, privacy concerns, and accuracy limitations, there are serious trade-offs to consider. You have other options available. +Fireflies does what it promises for basic transcription and automation, especially in English, if you don't mind a bot in your meetings. But between the billing issues, privacy concerns, and accuracy limits, the trade-offs are real. You have other options. ## Top Fireflies AI alternatives: at a glance -| Tool | Best For | Pricing | Platforms | +| Tool | Best For | Pricing | Platforms | | --------- | -------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------ | -| Char | Zero lock-in, complete control over data and AI stack | Free forever (local + BYOK). Cloud: $25/mo | All platforms (system audio capture) + in-person | -| Avoma | Sales teams needing revenue intelligence | $29-39/month base + $35-70/month add-ons | Zoom, Teams, Google Meet, Webex | -| Otter AI | Teams wanting established platform with collaboration | $16.99/user/mo (Free: 300 min) | Zoom, Teams, Google Meet | -| MeetGeek | Teams needing automated workflows & meeting analytics | $19/user/mo (Free: 3 hours of transcription/mo) | Zoom, Teams, Google Meet, Webex + 50 languages | -| Read AI | Enterprises needing unified search across communications | $19.75/month (Free: 5 transcripts/mo) | All major platforms + email/messaging | -| Tactiq | Chrome users wanting real-time browser transcription | $12/user/mo (Free: 10 meetings) | Chrome extension for Meet, Zoom, Teams | -| Sembly AI | Teams needing compliance & workflow automation | $15/user/mo (Free: 60 mins/mo) | Zoom, Teams, Google Meet, Webex + file upload | -| Fathom | Small teams wanting generous free plan + CRM sync | Free unlimited (Premium: $19/mo) | Zoom, Teams, Google Meet | -| Sonnet AI | Teams wanting enhanced meeting prep + bot-free capture | $25/month (Free: 5 recordings/mo) | All platforms (device-level capture) | +| Anarlog | Zero lock-in, complete control over data and AI stack | Free forever, fully local + BYOK. | All platforms (system audio capture) + in-person | +| Avoma | Sales teams needing revenue intelligence | $29-39/month base + $35-70/month add-ons | Zoom, Teams, Google Meet, Webex | +| Otter AI | Teams wanting established platform with collaboration | $16.99/user/mo (Free: 300 min) | Zoom, Teams, Google Meet | +| MeetGeek | Teams needing automated workflows & meeting analytics | $19/user/mo (Free: 3 hours of transcription/mo) | Zoom, Teams, Google Meet, Webex + 50 languages | +| Read AI | Enterprises needing unified search across communications | $19.75/month (Free: 5 transcripts/mo) | All major platforms + email/messaging | +| Tactiq | Chrome users wanting real-time browser transcription | $12/user/mo (Free: 10 meetings) | Chrome extension for Meet, Zoom, Teams | +| Sembly AI | Teams needing compliance & workflow automation | $15/user/mo (Free: 60 mins/mo) | Zoom, Teams, Google Meet, Webex + file upload | +| Fathom | Small teams wanting generous free plan + CRM sync | Free unlimited (Premium: $19/mo) | Zoom, Teams, Google Meet | +| Sonnet AI | Teams wanting enhanced meeting prep + bot-free capture | $25/month (Free: 5 recordings/mo) | All platforms (device-level capture) | ## 9 best Fireflies.ai alternatives: detailed reviews -### 1. Char +### 1. Anarlog -Char is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. It works like Apple Notes with AI superpowers, capturing audio from any meeting platform without joining as a bot. +Anarlog is an open-source AI notepad for meetings built for people who want complete control over their data and AI stack. Everything is stored as plain markdown files, not in proprietary databases. It works like Apple Notes with AI on top, capturing audio from any meeting platform without joining as a bot. -Char +Anarlog #### How it compares to Fireflies -Fireflies processes everything in the cloud, locks you into their platform, and uses a bot to join meetings. Char gives you complete control—choose your AI stack, own your files as plain markdown, and capture system audio directly. Zero lock-in, zero compromises. +Fireflies processes everything in the cloud, locks you into their platform, and uses a bot to join meetings. Anarlog gives you complete control—choose your AI stack, own your files as plain markdown, and capture system audio directly. Zero lock-in, zero compromises. #### Top features @@ -114,19 +113,17 @@ Fireflies processes everything in the cloud, locks you into their platform, and #### Pricing -Char is free forever for local transcription, BYOK, and all core features. The managed cloud service is $25/month for those who want the easiest setup. Enterprise plans include on-premises deployment, SSO integration, and consent management for compliance requirements. Contact sales for a quote. - - +Anarlog is free forever, fully local, BYOK, and open source. ### 2. Avoma -Avoma is a conversation intelligence platform designed specifically for revenue teams, combining meeting transcription with sales coaching, deal tracking, and revenue analytics. +Avoma is a conversation intelligence platform built for revenue teams. It combines meeting transcription with sales coaching, deal tracking, and revenue analytics. Avoma #### How it compares to Fireflies -While Fireflies provides basic conversation analytics, Avoma focuses on sales-specific insights like objection handling, talk time analysis, and deal progression tracking. It's built for revenue optimization rather than general meeting documentation. +Fireflies provides basic conversation analytics. Avoma focuses on sales-specific insights: objection handling, talk time analysis, deal progression. It's built for revenue, not general meeting documentation. #### Top features @@ -157,7 +154,7 @@ Base plans run $19-$39/user/month, with add-ons for sales features costing $35-$ ### 3. Otter AI -Otter AI is an established cloud-based AI meeting assistant that automatically joins video calls to record, transcribe, and summarize conversations with team collaboration features. +Otter AI is a cloud-based AI meeting assistant that automatically joins video calls to record, transcribe, and summarize conversations, with team collaboration features. Otter AI @@ -196,13 +193,13 @@ Also check: Top Otter AI's Alternatives ### 4. MeetGeek -MeetGeek is an AI meeting assistant that automatically detects meeting types and applies context-aware templates, workflows, and insights based on whether you're in sales calls, interviews, or team meetings. +MeetGeek is an AI meeting assistant that detects meeting types and applies context-aware templates, workflows, and insights for sales calls, interviews, or team meetings. MeetGeek #### How it compares to Fireflies -While Fireflies treats all meetings similarly, MeetGeek intelligently categorizes meetings and provides specialized insights for different contexts like hiring, sales, or project discussions. +Fireflies treats all meetings similarly. MeetGeek categorizes them and provides specialized insights for hiring, sales, or project discussions. #### Key features @@ -231,13 +228,13 @@ Starts at $19/user/month with meeting type detection and workflow automation. ### 5. Read AI -Read AI is a comprehensive AI platform that provides meeting intelligence alongside unified search across emails, messages, and CRM data, treating meetings as part of broader business communications. +Read AI is an AI platform that combines meeting intelligence with unified search across emails, messages, and CRM data. Meetings are part of broader business communications. Read AI #### How it compares to Fireflies -Fireflies focuses specifically on meeting intelligence, while Read.ai connects meeting insights with your entire communication ecosystem for unified knowledge management. +Fireflies focuses on meeting intelligence. Read.ai connects meeting insights with your entire communication ecosystem for unified knowledge management. #### Top features @@ -268,7 +265,7 @@ Free plan includes 5 monthly meeting reports. Paid plans start at $19.75/user/mo ### 6. Tactiq -Tactiq operates as a Chrome extension providing real-time transcription directly in your browser during Google Meet, Zoom, and Teams meetings without requiring separate app downloads. +Tactiq is a Chrome extension that provides real-time transcription in your browser during Google Meet, Zoom, and Teams meetings. No separate app to download. Tactiq @@ -412,10 +409,9 @@ Free plan with 5 recordings/month and a 30-minute recording limit. Paid plans st ## Where I'd start -If you like the idea of AI meeting notes but hate the idea of giving a vendor permanent leverage over your meeting data, start with Char. +If you like the idea of AI meeting notes but hate the idea of giving a vendor permanent leverage over your meeting data, start with Anarlog. It drops the bot, keeps the notes as files, and lets you decide whether AI runs through your own keys, the managed cloud, or a local model. That is a more durable setup than hoping one SaaS company keeps its pricing, privacy posture, and product direction aligned with yours. -[**Download Char for macOS**](/download) if that is the kind of system you would rather build on. +[**Download Anarlog for macOS**](https://anarlog.so) if that is the kind of system you would rather build on. - diff --git a/apps/web/content/articles/free-ai-notetakers.mdx b/apps/web/content/articles/free-ai-notetakers.mdx index 91498567e7..7f7ac4b67c 100644 --- a/apps/web/content/articles/free-ai-notetakers.mdx +++ b/apps/web/content/articles/free-ai-notetakers.mdx @@ -3,7 +3,6 @@ meta_title: "9 Best Free AI Notetakers with Forever-Free Plans in 2026" meta_description: "Review of 9 genuinely free AI meeting assistants that provide transcription, summaries, and note-taking without expiration dates or payment pressure." author: - "Harshika" -coverImage: "/api/assets/blog/free-ai-notetakers/cover.png" featured: false category: "Comparisons" date: "2025-08-07" @@ -21,18 +20,18 @@ We evaluated each free AI notetaker based on: - **Privacy and security** -- Data handling practices and compliance considerations - **User experience** -- Interface design, reliability, and ease of use -**Note:** If you're interested in premium options or tools with free trials, check out our comprehensive guide on the [best AI meeting assistants for taking notes](/blog/best-ai-meeting-assistant-for-taking-notes) that covers all available options. +**Note:** If you're interested in premium options or tools with free trials, check out our guide on the [best AI meeting assistants for taking notes](/blog/best-ai-meeting-assistant-for-taking-notes) that covers all available options. ## Top 9 Truly Free AI Notetakers for Meetings -### 1. Char: Best for Local Processing and Data Control +### 1. Anarlog: Best for Local Processing and Data Control -[Char](/) is an open-source AI notepad for meetings built for people who want complete control over their data and AI stack. It captures audio directly from your system, transcribes it in real-time, and combines your notes with the transcript to generate structured summaries, which are saved as plain Markdown files on your device. +[Anarlog](/) is an open-source AI notepad for meetings built for people who want complete control over their data and AI stack. It captures audio directly from your system, transcribes it in real-time, and combines your notes with the transcript to generate structured summaries, which are saved as plain Markdown files on your device. -![Char](/api/assets/blog/free-ai-notetakers/free-1.webp) +![Anarlog](/api/assets/blog/free-ai-notetakers/free-1.webp) **Best for:** -Anyone who wants enterprise-grade AI note-taking without vendor lock-in, cloud dependency, or data privacy compromises. +Anyone who wants AI note-taking without vendor lock-in, cloud dependency, or data privacy compromises. #### What's Free Forever @@ -48,9 +47,9 @@ Anyone who wants enterprise-grade AI note-taking without vendor lock-in, cloud d #### When Will You Need to Pay? -![Char pricing](/api/assets/blog/Screenshot-2026-03-04-at-9.01.49-PM.png) +![Anarlog pricing](/api/assets/blog/Screenshot-2026-03-04-at-9.01.49-PM.png) -Char is free forever for local transcription, BYOK, and all core features. The managed cloud service ($25/month) adds the easiest setup without managing API keys. Enterprise is relevant when your organization needs on-premises deployment, SSO integration, or custom branding for compliance requirements. +Anarlog is free forever for local transcription, BYOK, and all core features. ### 2. tl;dv: Best for Unlimited Recording @@ -150,7 +149,7 @@ The "Fred" bot captures meetings and provides detailed speaker analytics, sentim ![fireflies.ai](/api/assets/blog/free-ai-notetakers/free-9.webp) **Best for:** -Teams and organizations that want comprehensive conversation analytics, speaker insights, and detailed team performance metrics alongside basic transcription and summaries. +Teams and organizations that want conversation analytics, speaker insights, and detailed team performance metrics alongside basic transcription and summaries. #### What's Free Forever @@ -213,7 +212,7 @@ The 300-minute monthly limit is hit quickly by regular users, making the $16.99 ![MeetGeek](/api/assets/blog/free-ai-notetakers/free-13.webp) **Best for:** -Teams that want automated meeting workflows with smart templates that adapt to different meeting types, plus robust action item tracking and automated follow-up processes. +Teams that want automated meeting workflows with smart templates that adapt to different meeting types, plus action item tracking and automated follow-up processes. #### What's Free Forever @@ -265,7 +264,7 @@ The 120 monthly minutes (3 minutes per conversation) means regular users need th ### 9. Sonnet AI: Best for Automatic CRM Updates -[Sonnet AI](https://www.sonnetai.com) is a bot-free AI meeting assistant that focuses on the complete meeting lifecycle—from pre-meeting participant research to automatic CRM updates after calls. +[Sonnet AI](https://www.sonnetai.com) is a bot-free AI meeting assistant that focuses on the full meeting lifecycle—from pre-meeting participant research to automatic CRM updates after calls. It records device audio without visible meeting bots and emphasizes relationship management with automatic contact enrichment and conversation history tracking. @@ -292,16 +291,16 @@ Sales professionals who prioritize relationship management over just note-taking Sales professionals typically exceed 5 monthly recordings quickly, requiring the $25 Plus plan. The $35 Pro plan adds 100 monthly recordings and unlimited storage for extensive client call history. Enterprise is necessary when your sales team needs custom integrations, team functionality, and dedicated account management for revenue operations. -## Why Char May Be Your Best Choice +## Why Anarlog May Be Your Best Choice -These 9 tools offer genuine value without upfront costs—unlimited transcription from Fireflies, solid team features from Otter, multilingual support from Notta, and reliable recording from tl;dv. +These 9 tools offer real value without upfront costs—unlimited transcription from Fireflies, solid team features from Otter, multilingual support from Notta, and reliable recording from tl;dv. But there's a catch in every "free" offering: your privacy and data. Fireflies analyzes your conversation patterns. Fathom's bot joins sensitive client calls. Tactiq processes confidential discussions on remote servers. You're paying with access to your most private conversations. -Char operates entirely on your device. No cloud processing, no data collection, no privacy trade-offs. It delivers unlimited AI summaries, real-time transcription, and meeting intelligence on your hardware. +Anarlog operates entirely on your device. No cloud processing, no data collection, no privacy trade-offs. It delivers unlimited AI summaries, real-time transcription, and meeting intelligence on your hardware. -When "free" means no cost and no compromise, that's when you've found the right choice. +When "free" means no cost and no compromise, that's the right choice. -[Download Char for macOS](/download) to experience meeting intelligence that costs nothing and compromises nothing (Windows and Linux versions coming in Q2 2026). +[Download Anarlog for macOS](https://anarlog.so) to experience meeting intelligence that costs nothing and compromises nothing (Windows and Linux versions coming in Q2 2026).   diff --git a/apps/web/content/articles/free-transcription-software.mdx b/apps/web/content/articles/free-transcription-software.mdx index 5c6230a708..b017b9d9e7 100644 --- a/apps/web/content/articles/free-transcription-software.mdx +++ b/apps/web/content/articles/free-transcription-software.mdx @@ -4,7 +4,6 @@ display_title: "Best Free Transcription Software in 2026" meta_description: "Find truly free transcription solutions in 2026 that offer real usability, privacy, and accuracy for all your speech-to-text needs. No surprises or limits." author: - "Harshika" -coverImage: "/api/assets/blog/free-transcription-software/cover.png" featured: false category: "Comparisons" date: "2025-10-08" @@ -18,16 +17,14 @@ This guide reviews genuinely free transcription software that works without cons ## Best free transcription software: quick comparison - -| Tool | Best For | Transcription Type | Real-Time | Monthly Limit | Export Formats | +| Tool | Best For | Transcription Type | Real-Time | Monthly Limit | Export Formats | | -------------- | -------------------------- | ---------------------------------------- | --------- | --------------------------- | ------------------------ | -| Char | Privacy & unlimited use | Meeting + live video/audio transcription | ✅ Yes | Unlimited | MD, PDF, RTF | -| Transcript LOL | Multi-source imports | Files + URL imports | ❌ No | 2 files daily (20 min each) | TXT, DOCX, PDF, SRT, VTT | -| Riverside | Unlimited no-signup tool | Files only | ❌ No | Unlimited | TXT, SRT | -| Otter AI | Meeting bot automation | Meeting bot + files | ✅ Yes | 300 min (30 min/recording) | MP3, TXT (PDF/DOCX paid) | -| Notta | Multi-language translation | Meeting bot + files | ✅ Yes | 120 min (3 min/recording) | TXT (others paid) | -| MacWhisper | Simple Mac transcription | Files only | ❌ No | Unlimited | TXT, SRT, VTT | - +| Anarlog | Privacy & unlimited use | Meeting + live video/audio transcription | ✅ Yes | Unlimited | MD, PDF, RTF | +| Transcript LOL | Multi-source imports | Files + URL imports | ❌ No | 2 files daily (20 min each) | TXT, DOCX, PDF, SRT, VTT | +| Riverside | Unlimited no-signup tool | Files only | ❌ No | Unlimited | TXT, SRT | +| Otter AI | Meeting bot automation | Meeting bot + files | ✅ Yes | 300 min (30 min/recording) | MP3, TXT (PDF/DOCX paid) | +| Notta | Multi-language translation | Meeting bot + files | ✅ Yes | 120 min (3 min/recording) | TXT (others paid) | +| MacWhisper | Simple Mac transcription | Files only | ❌ No | Unlimited | TXT, SRT, VTT | ## How we chose these tools @@ -36,7 +33,7 @@ We evaluated dozens of transcription tools using these criteria: - **Automatic transcription only**: Manual transcription assistants help you type faster while listening to audio, but don't transcribe automatically. This guide focuses on software that actually transcribes for you. - **Genuine free access**: We prioritized tools with permanent free plans, not trials disguised as "free" offerings. - **Usable limitations**: Many "free" tools impose restrictions that make them unusable—10-minute one-time trials or 3-minute recording limits. We focused on tools where the free tier actually works for real-world use. -- **Transcription quality**: All selected tools use modern AI models (primarily OpenAI's Whisper) that deliver genuine accuracy. +- **Transcription quality**: All selected tools use modern AI models (primarily OpenAI's Whisper) that deliver real accuracy. - **Privacy and data handling**: We noted whether tools process locally or in the cloud, and whether your data gets used for AI training. - **No hidden costs**: We clearly documented where free plans end and paid features begin. @@ -58,7 +55,7 @@ We evaluated dozens of transcription tools using these criteria: [Sonix](https://sonix.ai) only offers a 30-minute one-time trial with no ongoing free plan. After that, you must pay $10/hour for transcription. -**Consider for**: Multi-language support across 53+ languages and team collaboration features with shared folders. +**Consider for**: Multi-language support across 53+ languages and team features with shared folders. ### 4. Trint @@ -70,17 +67,17 @@ We evaluated dozens of transcription tools using these criteria: [Happy Scribe](https://www.happyscribe.com) offers only 10 minutes of transcription total (one-time, not monthly) and doesn't allow file exports on the free tier. -**Consider for**: Subtitle creation with support for 120+ languages and automated translation capabilities. +**Consider for**: Subtitle creation with support for 120+ languages and automated translation. You can check detailed reviews of these tools in our guide to [best transcription software for Mac](/blog/transcription-software-mac). ## Best free transcription software: detailed reviews -### 1. Char: best for local processing and unlimited transcription +### 1. Anarlog: best for local processing and unlimited transcription -![Char](/api/assets/blog/free-transcription-software/transcription-1.webp) +![Anarlog](/api/assets/blog/free-transcription-software/transcription-1.webp) -[Char](/) is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, BYOK, or run local models. +[Anarlog](/) is an open-source AI notepad for meetings that gives you full control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: bring your own keys or run local models. Zero lock-in means you can switch AI providers anytime and your files work with any tool (Obsidian, Notion, VS Code). @@ -98,11 +95,9 @@ Zero lock-in means you can switch AI providers anytime and your files work with #### When will you need to pay? -![Char pricing](/api/assets/blog/Screenshot-2026-03-04-at-9.01.49-PM.png) - -Free forever for local transcription, BYOK, and all core features. The managed cloud service ($25/month) adds the easiest setup without managing API keys. +![Anarlog pricing](/api/assets/blog/Screenshot-2026-03-04-at-9.01.49-PM.png) -Enterprise licensing is available when your organization needs on-premises deployment, SSO integration, consent management, or custom branding. [Contact sales for a quote](/founders). +Free forever for local transcription, BYOK, and all core features.   @@ -166,7 +161,7 @@ The free transcription tool handles basic text conversion. Serious content creat ![otter ai transcription review](/api/assets/blog/free-transcription-software/transcription-7.webp) -Otter.ai is a meeting assistant similar to Char. It transcribes conversations, generates summaries, and helps you search through meeting history. The key difference is that Char processes everything locally on your device, while Otter sends all your audio to the cloud for processing. +Otter.ai is a meeting assistant similar to Anarlog. It transcribes conversations, generates summaries, and helps you search through meeting history. The key difference is that Anarlog processes everything locally on your device, while Otter sends all your audio to the cloud for processing. The cloud approach enables Otter's signature feature: the meeting bot that automatically joins your Zoom, Microsoft Teams, and Google Meet calls based on your calendar, records everything, and shares notes with participants afterward. @@ -232,7 +227,7 @@ Enterprise becomes relevant for organizations requiring SAML SSO, audit logs, no [MacWhisper](https://goodsnooze.gumroad.com/l/macwhisper) is a Mac app that wraps OpenAI's Whisper technology in a simple drag-and-drop interface. -While tools like Char use Whisper as part of a complete meeting assistant, MacWhisper focuses purely on transcription. You drop in an audio file, it gives you text. The interface is minimal: drag your file, select a model size based on how much accuracy versus speed you want, and get your transcript with synced audio playback. +While tools like Anarlog use Whisper as part of a complete meeting assistant, MacWhisper focuses purely on transcription. You drop in an audio file, it gives you text. The interface is minimal: drag your file, select a model size based on how much accuracy versus speed you want, and get your transcript with synced audio playback. Performance depends heavily on your hardware. Apple Silicon Macs (M1/M2/M3) handle transcription smoothly, processing files at roughly 15x real-time speed. Older Intel Macs work but run significantly slower, especially with larger models that require more than 8GB of RAM. @@ -257,10 +252,9 @@ MacWhisper Pro ($29-35 one-time payment, no subscription) unlocks better accurac ## Our top recommendation for free transcription -If data privacy matters and you need truly unlimited transcription without hidden costs, Char is the clear choice. +If data privacy matters and you need truly unlimited transcription without hidden costs, Anarlog is the clear choice. -Unlike most transcription tools that lock you into their cloud, Char stores everything as plain markdown files and lets you choose your AI stack. Zero lock-in, complete control. +Unlike most transcription tools that lock you into their cloud, Anarlog stores everything as plain markdown files and lets you choose your AI stack. Zero lock-in, full control. -Ready to take control of your transcription data? **[Download Char for macOS](/download)** and get unlimited private transcription (Windows and Linux versions coming in Q2 2026). +Ready to take control of your transcription data? **[Download Anarlog for macOS](https://anarlog.so)** and get unlimited private transcription (Windows and Linux versions coming in Q2 2026). - diff --git a/apps/web/content/articles/google-gemini-data-retention-policy.mdx b/apps/web/content/articles/google-gemini-data-retention-policy.mdx index f90b4a4acc..d00ab46efe 100644 --- a/apps/web/content/articles/google-gemini-data-retention-policy.mdx +++ b/apps/web/content/articles/google-gemini-data-retention-policy.mdx @@ -8,7 +8,7 @@ category: "Guides" date: "2026-03-13" --- -Every AI provider in this series stores your conversations to some degree. Google Gemini does that too. But Gemini sits inside an ecosystem that already holds your email, your calendar, your location history, and your search activity. That changes the risk profile entirely. +Every AI provider in this series stores your conversations to some degree. Google Gemini does that too. But Gemini sits inside an ecosystem that already holds your email, your calendar, your location history, and your search activity. That changes the risk profile. When you evaluate [OpenAI](/blog/chatgpt-data-retention-policy/) or Anthropic, you're asking what they do with your AI conversations. When you evaluate Google, you're asking what they do with your AI conversations and everything else they already know about you. @@ -16,14 +16,12 @@ Here is the full picture. ## Quick Reference: Gemini Data Retention by Tier - -| Feature | Consumer Gemini | Gemini API | Workspace Enterprise | +| Feature | Consumer Gemini | Gemini API | Workspace Enterprise | | ----------------- | ------------------ | ------------------------------- | -------------------- | -| Default Retention | 18 months | 55 days (abuse monitoring only) | Admin-defined | -| Human Review | Yes, up to 3 years | No | No (by default) | -| Model Training | Yes (opt-out) | No | No | -| Gmail Access | Default ON in US | No | Admin-controlled | - +| Default Retention | 18 months | 55 days (abuse monitoring only) | Admin-defined | +| Human Review | Yes, up to 3 years | No | No (by default) | +| Model Training | Yes (opt-out) | No | No | +| Gmail Access | Default ON in US | No | Admin-controlled | ## What Gemini Stores by Default? @@ -37,7 +35,7 @@ Google says it tries to anonymize or disconnect reviewed chats before humans see ## The Gmail Access Story -In late 2025, Google enabled Gemini access to Gmail, Google Chat, and Google Meet by default for US users. By early 2026, Google rebranded this cross-app data flow under the name "Personal Intelligence," which now manages how Gemini connects to Gmail, Drive, Maps, and other Google services. When enabled, Gemini can read your entire email history to power features like email summarization, draft suggestions, and surfacing relevant information across your inbox. +In late 2025, Google enabled Gemini access to Gmail, Google Chat, and Google Meet by default for US users. By early 2026, Google rebranded this cross-app data flow under the name "Personal Intelligence", which now manages how Gemini connects to Gmail, Drive, Maps, and other Google services. When enabled, Gemini can read your entire email history to power features like email summarization, draft suggestions, and surfacing relevant information across your inbox. In the US, this was opt-out. You had to actively turn it off. In Europe, the same feature required an opt-in before Gemini could access your Gmail, because GDPR places stricter requirements on default data sharing. @@ -53,7 +51,7 @@ With activity off, future conversations will not be sent for human review and wi To control Gmail and cross-app access: as of early 2026, Google groups these permissions under a Personal Intelligence dashboard inside the Gemini interface. This is the central place to control which Google services Gemini can read across Gmail, Drive, Maps, and Calendar. You can also disable Gmail access specifically via Gmail Settings → See all settings → General → Gemini for Gmail without affecting the rest of your account. -To adjust the default 18-month retention: open Gemini Apps Activity in your Google Account, select "Auto-delete," and choose your preferred window. +To adjust the default 18-month retention: open Gemini Apps Activity in your Google Account, select "Auto-delete", and choose your preferred window. ## Human Review in Plain Terms @@ -93,14 +91,14 @@ The consumer product connects to Gmail, Calendar, Drive, and location data. The For personal productivity use, most of this will not matter day to day. For professionals handling sensitive conversations, client data, or regulated information, the picture is more complicated. -## Use Gemini's API for Meeting Notes Through Char +## Use Gemini's API for Meeting Notes Through Anarlog -If you want to use Gemini without routing data through the consumer product, connecting your own Google AI API key through [Char](https://char.com) gives you API-level data handling: 55-day retention, no model training, no Gmail integration. +If you want to use Gemini without routing data through the consumer product, connecting your own Google AI API key through [Anarlog](https://anarlog.so) gives you API-level data handling: 55-day retention, no model training, no Gmail integration. -Char is an open-source AI notepad for meetings that lets you bring your own API key for Gemini, OpenAI, Anthropic, Mistral, or others. Your meeting notes are stored on your device. You choose which AI provider processes your data, and you can change that decision without rebuilding your workflow. +Anarlog is an open-source AI notepad for meetings that lets you bring your own API key for Gemini, OpenAI, Anthropic, Mistral, or others. Your meeting notes are stored on your device. You choose which AI provider processes your data, and you can change that decision without rebuilding your workflow. -For teams that need to go further, Char supports fully local models via Ollama. Your conversations never leave your device at all. +For teams that need to go further, Anarlog supports fully local models via Ollama. Your conversations never leave your device at all. That is the practical difference between using a consumer AI product and choosing your own stack. The first gives you a privacy policy. The second gives you control. -[Download Char for macOS](https://char.com/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file +[Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file diff --git a/apps/web/content/articles/google-gemini-meeting-notes.mdx b/apps/web/content/articles/google-gemini-meeting-notes.mdx index 02968fdc8f..d116d87430 100644 --- a/apps/web/content/articles/google-gemini-meeting-notes.mdx +++ b/apps/web/content/articles/google-gemini-meeting-notes.mdx @@ -3,13 +3,12 @@ meta_title: "I Tested Google Gemini Meeting Notes So You Don't Have To" meta_description: "Can Google Gemini take meeting notes? Yes, but only in Google Meet with paid plans. If you use Teams or Zoom, then you'll need workarounds. Know more." author: - "John Jeong" -coverImage: "/api/assets/blog/google-gemini-meeting-notes/cover.png" featured: false category: "Comparisons" date: "2025-09-24" --- -If you've come to rely on Gemini for day-to-day admin tasks, you're probably wondering whether it can handle meeting notes. It can, but there are significant limitations. +If you've come to rely on Gemini for day-to-day admin tasks, you're probably wondering whether it can handle meeting notes. It can, but only inside Google Meet on a paid Workspace plan. Everywhere else, you're doing the work yourself. ## Can Google Gemini take meeting notes? @@ -64,13 +63,13 @@ Zoom includes cloud recording with transcription on paid plans. Teams automatica 💡 Try this prompt: "Please summarize this meeting transcript. Include key decisions, action items, and next steps. Format it professionally with clear sections for main topics discussed, decisions made, and follow-up tasks assigned." -Gemini does excel at taking messy transcripts and turning them into clean, organized notes. This approach requires more work than the automated Google Meet solution, and it won't give you real-time transcription. +Gemini is good at turning messy transcripts into clean notes. But this approach is more work than the automated Google Meet solution, and you don't get real-time transcription. ## What are the pros and cons of using Gemini to take meeting notes? ### The good stuff -**It produces strong summaries.** When Gemini works, it catches key decisions, identifies action items, and organizes everything in a clean format you can actually use. +**It produces strong summaries.** When Gemini works, it catches decisions, pulls action items, and organizes them in a format you can actually use. **Real-time summaries help during calls.** The "Summary so far" feature during Google Meet is handy if you want to glance over and check what you might have missed without interrupting the conversation. @@ -80,7 +79,7 @@ Gemini does excel at taking messy transcripts and turning them into clean, organ **Subscription costs are steep.** Google Workspace plans with this feature run $12-18 per user per month. A 20-person company pays $2,880-4,320 annually just to take meeting notes. -**Privacy concerns matter.** Every word spoken gets uploaded to Google's servers. That works fine for internal team meetings, but client consultations, legal discussions, or healthcare conversations pose real risk. Many organizations can't justify it. +**Every word goes to Google's servers.** Fine for internal standups. Not fine for client consultations, legal discussions, or healthcare conversations. A lot of organizations can't justify it. **Manual workflows defeat the purpose.** For non-Google Meet platforms, you download transcripts, copy text, craft prompts, and organize results yourself. You're doing the AI's job. @@ -88,7 +87,7 @@ Gemini does excel at taking messy transcripts and turning them into clean, organ ### Should you use it? -After testing Google Gemini meeting notes extensively, it works well only if you meet three specific criteria: +After spending real time with it, Google Gemini meeting notes work well only if all three of these are true: - Your entire organization uses Google Meet exclusively - You're comfortable with Google processing sensitive conversations @@ -98,13 +97,11 @@ Most teams don't meet all three. You'll likely find yourself working around limi ## What is the best alternative to Gemini for note-taking? -The list of alternatives is substantial—any decent AI meeting assistant does a better job. We have guides for [Google Meet](/blog/best-ai-notetakers-google-meet), [Zoom](/blog/best-ai-notetaker-for-zoom), and [Microsoft Teams](/blog/best-ai-notetaker-for-microsoft-teams). +Almost any decent AI meeting assistant does this better. We have guides for [Google Meet](/blog/best-ai-notetakers-google-meet), [Zoom](/blog/best-ai-notetaker-for-zoom), and [Microsoft Teams](/blog/best-ai-notetaker-for-microsoft-teams). -If I have to recommend one platform that works universally, is free, and keeps your data local, it's Char. +If I have to pick one tool that works on every platform, is free, and keeps your data local, it's Anarlog. -Quick note: I'm the co-founder of Char, but my recommendation isn't just personal bias—the platform delivers results. - -Here's what it does: +I'm the co-founder, so take that as you will. But here's what it actually does. - Real-time transcription with no bots or calendar permissions required - Works with Zoom, Teams, Google Meet, and in-person meetings without any setup @@ -112,9 +109,9 @@ Here's what it does: - Smart consent management for enterprises with options from simple pop-ups to verbal consent recognition - Saves your notes as plain markdown files that work with Obsidian, Notion, VS Code, or any text editor -Char's note-taking features specifically: +Anarlog's note-taking features specifically: -![Char](/api/assets/blog/google-gemini-meeting-notes/gemini-2.webp) +![Anarlog](/api/assets/blog/google-gemini-meeting-notes/gemini-2.webp) - **Familiar interface.** Feels like Apple Notes with powerful AI running locally. - **Custom templates.** Tailored formats for therapy sessions, legal meetings, job interviews, or casual conversations. @@ -122,9 +119,9 @@ Char's note-taking features specifically: - **AI chat for clarification.** Get immediate answers about action items, deadlines, or who said what. - **Verify with source analysis.** Hover over summaries to see the exact transcript quote behind every AI-generated point. -The difference is substantial. Instead of hoping Google's limited solution works with your workflow, you get a tool that adapts to how your team actually meets. +Instead of hoping Google's narrow solution fits your workflow, you get a tool that adapts to how your team actually meets. -Want to try Char? [Download it for free](/download)! +Want to try Anarlog? [Download it for free](https://anarlog.so)! ## Frequently asked questions @@ -142,9 +139,9 @@ The basic Gemini chatbot has free and paid versions, but automatic meeting note- ### 4. How do I transcribe Google Meet calls? -You have two main options: Google Meet's built-in transcription (which requires expensive Workspace subscriptions) or a third-party tool like Char. +You have two main options: Google Meet's built-in transcription (which requires expensive Workspace subscriptions) or a third-party tool like Anarlog. -Unlike cloud-based tools like Otter or Fireflies that send your conversations to their servers, Char provides unlimited real-time transcription completely free while keeping everything local on your device. +Unlike cloud-based tools like Otter or Fireflies that send your conversations to their servers, Anarlog provides unlimited real-time transcription completely free while keeping everything local on your device. ### 5. Does Google Meet transcription work in all languages? @@ -152,9 +149,9 @@ No. Google Meet only supports transcription in 9 languages: English, French, Ger ### 6. Is there a free AI note taker for Google Meet? -Yes—Char is a completely [free AI note taker](/blog/free-ai-notetakers) that works with Google Meet, Zoom, Teams, and any other meeting platform. +Yes—Anarlog is a completely [free AI note taker](/blog/free-ai-notetakers) that works with Google Meet, Zoom, Teams, and any other meeting platform. -Unlike Google's "Take notes with Gemini" feature, which requires expensive Workspace subscriptions, Char's core transcription and note-taking features are free forever. +Unlike Google's "Take notes with Gemini" feature, which requires expensive Workspace subscriptions, Anarlog's core transcription and note-taking features are free forever. Your conversations stay private since everything processes locally on your device instead of being sent to the cloud. diff --git a/apps/web/content/articles/granola-ai-alternatives.mdx b/apps/web/content/articles/granola-ai-alternatives.mdx index 979cfb40d3..6d9fa83546 100644 --- a/apps/web/content/articles/granola-ai-alternatives.mdx +++ b/apps/web/content/articles/granola-ai-alternatives.mdx @@ -2,7 +2,6 @@ meta_title: "6 Best Granola AI Alternatives in 2026" meta_description: "Looking for Granola AI alternatives? Compare top meeting assistants with better privacy, platform support, integrations & pricing." author: "John Jeong" -coverImage: "/api/assets/blog/granola-ai-alternatives/cover.png" category: "Comparisons" date: "2025-08-15" --- @@ -17,7 +16,7 @@ This list is for people who like the notepad-first workflow but want a better tr ## Our Top Picks -1. **Char:** Open-source AI notepad with zero lock-in and complete control over your data and AI stack +1. **Anarlog:** Open-source AI notepad with zero lock-in and complete control over your data and AI stack 2. **Jamie AI:** GDPR-compliant European alternative with 100+ language support 3. **Tactiq:** Chrome extension with real-time transcription and rich integrations 4. **Krisp:** Audio enhancement specialist with noise cancellation and accent conversion @@ -60,15 +59,15 @@ Granola's $18/month individual plan and limited free tier prompted us to look fo ## Review of The 6 Best Granola AI Alternatives -### 1. Char: The Open-Source Granola Alternative +### 1. Anarlog: The Open-Source Granola Alternative -**Best for:** High-agency professionals who demand complete control over their data, AI stack, and workflow—especially engineers, developers, and teams in regulated industries. +**Best for:** People who want full control over their data, AI stack, and workflow—engineers, developers, and teams in regulated industries. -Char +Anarlog -[Char](/) is an open-source AI notepad for meetings that gives you complete control over your data, AI stack, and workflow. Everything is stored as plain markdown files on your device—not in proprietary databases or cloud servers. +[Anarlog](/) is an open-source AI notepad for meetings. Your notes are plain markdown files on your device—not rows in a vendor's database. -You can inspect the code, customize functionality, and choose which AI processes your data. Zero lock-in, zero compromises. +You can read the code, change it, and pick which AI touches your data. No lock-in. #### Key Features @@ -81,20 +80,20 @@ You can inspect the code, customize functionality, and choose which AI processes - Currently macOS only (Windows and Linux coming in Q2 2026) - Unlimited AI summaries and conversational search -#### Char vs Granola Strengths +#### Anarlog vs Granola Strengths -You take notes, the AI enhances them with transcript insights, and you get polished summaries. The difference is who controls the stack. With Char, you own your files, choose your AI, and have zero lock-in. Granola sends your meetings to OpenAI and Anthropic with no way out. +You take notes, the AI enhances them with transcript insights, and you get polished summaries. The difference is who controls the stack. With Anarlog, you own your files, choose your AI, and have zero lock-in. Granola sends your meetings to OpenAI and Anthropic with no way out. -| Feature | Char | Granola | +| Feature | Anarlog | Granola | | ----------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -| **Ownership** | Plain markdown files on your device. Your data, your files. | Proprietary format locked in their platform | -| **AI Stack** | Your choice: managed cloud, bring your own keys, or run local models | Sends your meetings to OpenAI and Anthropic for processing | -| **Lock-in** | Zero. Open source, portable format, switch AI providers anytime | Third-party vendor lock-in | -| **Real-time Features** | Live transcription visible during meetings | Post-meeting processing only | -| **Pricing** | Free forever for local transcription, BYOK, and all core features. $25/month for managed cloud. | $10/month per user (adds up fast for teams) | -| **Transparency** | Open source—audit the code, customize functionality, and know exactly how your data is processed | Closed source—you have no idea how it works | +| **Ownership** | Plain markdown files on your device. Your data, your files. | Proprietary format locked in their platform | +| **AI Stack** | Your choice: managed cloud, bring your own keys, or run local models | Sends your meetings to OpenAI and Anthropic for processing | +| **Lock-in** | Zero. Open source, portable format, switch AI providers anytime | Third-party vendor lock-in | +| **Real-time Features** | Live transcription visible during meetings | Post-meeting processing only | +| **Pricing** | Free forever for local transcription, BYOK, and all core features. (Managed cloud: [Char](https://char.com)) | $10/month per user (adds up fast for teams) | +| **Transparency** | Open source—audit the code, customize functionality, and know exactly how your data is processed | Closed source—you have no idea how it works | -#### Char vs Granola Limitations +#### Anarlog vs Granola Limitations - No mobile app currently available - May not include the absolute latest AI capabilities if you prioritize cutting-edge features over privacy @@ -109,19 +108,19 @@ You take notes, the AI enhances them with transcript insights, and you get polis #### Jamie AI vs Granola -[Jamie AI](https://www.meetjamie.ai/) creates comprehensive meeting notes and action items across any meeting platform without using visible bots. It hosts all data in Europe and supports offline capabilities and 100+ languages for global teams. +[Jamie AI](https://www.meetjamie.ai/) creates meeting notes and action items across any platform without sending a visible bot. Data stays in Europe, it works offline, and it supports 100+ languages. #### Jamie vs Granola Strengths -Both tools deliver intelligent meeting assistance by combining transcription with AI-powered enhancement. Jamie focuses on privacy and European data hosting, while Granola prioritizes simplicity. +Both combine transcription with AI summaries. Jamie's edge is privacy and European data hosting. Granola's edge is simplicity. -| Feature | Jamie | Granola | +| Feature | Jamie | Granola | | ------------------------ | ----------------------------------------------------------- | ------------------------------------------------ | -| **Meeting Experience** | No bots, plus works offline for in-person meetings | No bots, clean meeting experience | -| **Privacy & Compliance** | GDPR-compliant with all data hosted in Europe | Data processed by OpenAI and Anthropic in the US | -| **Language Support** | 100+ languages with automatic detection and transcription | Limited language capabilities | -| **Offline Capability** | Works completely offline for in-person meetings | Requires internet for all AI processing | -| **Data Sovereignty** | European data residency meets stricter privacy requirements | US-based cloud processing | +| **Meeting Experience** | No bots, plus works offline for in-person meetings | No bots, clean meeting experience | +| **Privacy & Compliance** | GDPR-compliant with all data hosted in Europe | Data processed by OpenAI and Anthropic in the US | +| **Language Support** | 100+ languages with automatic detection and transcription | Limited language capabilities | +| **Offline Capability** | Works completely offline for in-person meetings | Requires internet for all AI processing | +| **Data Sovereignty** | European data residency meets stricter privacy requirements | US-based cloud processing | #### Jamie vs Granola Limitations @@ -140,20 +139,20 @@ Both tools deliver intelligent meeting assistance by combining transcription wit #### Tactiq vs Granola -[Tactiq](https://tactiq.io/) provides real-time AI meeting transcription for Google Meet, Zoom, and Microsoft Teams. It runs directly in your browser without requiring software installation, capturing live transcripts with speaker identification and generating AI-powered summaries and action items after meetings. +[Tactiq](https://tactiq.io/) is a real-time meeting transcriber for Google Meet, Zoom, and Microsoft Teams. It runs in your browser, no install required, captures live transcripts with speaker labels, and generates summaries and action items after the call. #### Tactiq vs Granola Strengths -Both tools provide intelligent meeting assistance. Tactiq operates as a Chrome extension and offers real-time transcription, while Granola requires a native desktop app installation. +Tactiq is a Chrome extension with real-time transcription. Granola is a native desktop app that processes after the meeting. -| Feature | Tactiq | Granola | +| Feature | Tactiq | Granola | | ------------------------- | --------------------------------------------------- | ---------------------------------------- | -| **Setup & Installation** | Simple Chrome extension, instant setup | Native desktop app installation required | -| **Real-time Features** | Live transcription visible during meetings | Post-meeting processing only | -| **Language Support** | 60+ languages with automatic detection | Limited language capabilities | -| **Integration Ecosystem** | Rich integrations (Slack, HubSpot, Jira, Linear) | Minimal third-party integrations | -| **Workflow Automation** | Custom AI actions and reusable prompts | Basic note enhancement | -| **Pricing** | $12/month for unlimited transcripts + 10 AI credits | $18/month for unlimited meetings | +| **Setup & Installation** | Simple Chrome extension, instant setup | Native desktop app installation required | +| **Real-time Features** | Live transcription visible during meetings | Post-meeting processing only | +| **Language Support** | 60+ languages with automatic detection | Limited language capabilities | +| **Integration Ecosystem** | Rich integrations (Slack, HubSpot, Jira, Linear) | Minimal third-party integrations | +| **Workflow Automation** | Custom AI actions and reusable prompts | Basic note enhancement | +| **Pricing** | $12/month for unlimited transcripts + 10 AI credits | $18/month for unlimited meetings | #### Tactiq vs Granola Limitations @@ -171,22 +170,22 @@ Both tools provide intelligent meeting assistance. Tactiq operates as a Chrome e #### Krisp vs Granola AI -[Krisp](https://krisp.ai/) combines strong AI noise cancellation with meeting transcription, delivering clear audio quality and accurate meeting documentation without visible bots. +[Krisp](https://krisp.ai/) pairs AI noise cancellation with meeting transcription. You get clear audio and notes, no bot in the room. #### Krisp vs Granola Strengths -Both tools provide bot-free meeting assistance. Krisp's unique focus on audio quality sets it apart from purely transcription-focused solutions. +Both are bot-free. Krisp's edge is audio quality — most transcription tools ignore that side completely. -| Feature | Krisp | Granola | +| Feature | Krisp | Granola | | --------------------------- | ----------------------------------------------------------- | --------------------------------- | -| **Audio Quality** | Bidirectional noise cancellation | Standard meeting audio processing | -| **Accent Support** | AI accent conversion for clearer communication | No accent assistance features | -| **Environment Flexibility** | Specifically designed for noisy environments | Works in quiet environments | -| **Meeting Coverage** | Virtual + in-person meetings with mobile apps | Virtual meetings only | -| **Pricing** | $16/month for unlimited features + noise cancellation | $18/month for unlimited meetings | -| **Language Support** | 16+ languages with multilingual transcription | Limited language capabilities | -| **Processing Approach** | Local audio processing | Cloud-based AI processing | -| **Integration Scope** | CRM integrations (Salesforce, HubSpot) + productivity tools | Minimal third-party connections | +| **Audio Quality** | Bidirectional noise cancellation | Standard meeting audio processing | +| **Accent Support** | AI accent conversion for clearer communication | No accent assistance features | +| **Environment Flexibility** | Specifically designed for noisy environments | Works in quiet environments | +| **Meeting Coverage** | Virtual + in-person meetings with mobile apps | Virtual meetings only | +| **Pricing** | $16/month for unlimited features + noise cancellation | $18/month for unlimited meetings | +| **Language Support** | 16+ languages with multilingual transcription | Limited language capabilities | +| **Processing Approach** | Local audio processing | Cloud-based AI processing | +| **Integration Scope** | CRM integrations (Salesforce, HubSpot) + productivity tools | Minimal third-party connections | #### Krisp vs Granola Limitations @@ -204,21 +203,21 @@ Both tools provide bot-free meeting assistance. Krisp's unique focus on audio qu #### Sonnet AI vs Granola AI -[Sonnet AI](https://www.sonnetai.com/) is an end-to-end meeting assistant that specializes in automatically updating CRM systems with meeting insights. Founded by Y Combinator alumni, Sonnet records device audio without visible bots and transforms conversations into structured CRM data, eliminating manual data entry while providing customizable AI-generated meeting notes. +[Sonnet AI](https://www.sonnetai.com/) is a meeting assistant built around CRM automation. Founded by Y Combinator alumni, it records device audio without a visible bot and pushes the conversation into your CRM as structured data, so you stop typing it in by hand. #### Sonnet AI vs Granola Strengths -Both tools provide bot-free meeting assistance. Sonnet AI's focus on CRM automation and relationship management sets it apart from general note-taking solutions. +Both are bot-free. Sonnet's edge is CRM automation and relationship tracking — not just notes. -| Feature | Sonnet AI | Granola | +| Feature | Sonnet AI | Granola | | --------------------------- | -------------------------------------------------------- | ----------------------------- | -| **CRM Integration** | Automatic CRM data population and updates | No built-in CRM functionality | -| **Pre-meeting Prep** | Participant research and background information | Basic meeting preparation | -| **Data Structure** | Structured CRM data and custom templates | General meeting notes format | -| **Relationship Management** | Comprehensive relationship tracking across conversations | Individual meeting focus | -| **Follow-up Automation** | Automated follow-up emails and task creation | Manual follow-up required | -| **Recording & Sharing** | Shareable recordings for absent team members | No recording functionality | -| **Workflow Integration** | End-to-end meeting workflow automation | Basic note enhancement | +| **CRM Integration** | Automatic CRM data population and updates | No built-in CRM functionality | +| **Pre-meeting Prep** | Participant research and background information | Basic meeting preparation | +| **Data Structure** | Structured CRM data and custom templates | General meeting notes format | +| **Relationship Management** | Comprehensive relationship tracking across conversations | Individual meeting focus | +| **Follow-up Automation** | Automated follow-up emails and task creation | Manual follow-up required | +| **Recording & Sharing** | Shareable recordings for absent team members | No recording functionality | +| **Workflow Integration** | End-to-end meeting workflow automation | Basic note enhancement | #### Sonnet AI vs Granola Limitations @@ -237,7 +236,7 @@ Both tools provide bot-free meeting assistance. Sonnet AI's focus on CRM automat #### tl;dv vs Granola AI -[tl;dv](https://tldv.io/) is a comprehensive AI meeting assistant that records video, provides sales coaching, and analyzes multiple meetings for patterns. It goes beyond simple note-taking to transform conversations into actionable insights with enterprise-grade video capabilities that bot-free alternatives cannot match. +[tl;dv](https://tldv.io/) records video, runs sales coaching, and analyzes patterns across multiple meetings. The video recording is the part bot-free alternatives can't match — it requires a bot in the call. > **Note:** tl;dv is the only bot-based tool in this comparison list. Unlike the other bot-free alternatives, tl;dv sends a visible bot to join your meetings to enable video recording and advanced features that aren't possible with device-level audio capture alone. > @@ -245,17 +244,17 @@ Both tools provide bot-free meeting assistance. Sonnet AI's focus on CRM automat #### tl;dv vs Granola Strengths -While both tools provide meeting intelligence, tl;dv's bot-based approach enables video capabilities and advanced features that device-level solutions cannot offer. +Both summarize meetings. tl;dv's bot-based approach unlocks video recording and features that device-level capture can't reach. -| Feature | tl;dv | Granola | +| Feature | tl;dv | Granola | | ------------------------------ | -------------------------------------------------------------------- | -------------------------------------- | -| **Video Recording** | High-quality video recording with unlimited meetings | No video capabilities | -| **Pricing Value** | $20/month for advanced features + unlimited video recordings | $18/month for basic unlimited meetings | -| **Sales Coaching** | Advanced playbook tracking, objection analysis, performance insights | Basic meeting notes only | -| **Multi-Meeting Intelligence** | Cross-meeting trend analysis and aggregated insights | Single meeting focus | -| **Content Sharing** | Video clips, highlight reels, and training content creation | Text-based notes sharing | -| **Language Support** | 30+ languages with high-accuracy transcription | Limited language capabilities | -| **Integration Depth** | 5,000+ tool integrations with workflow automation | Basic note enhancement | +| **Video Recording** | High-quality video recording with unlimited meetings | No video capabilities | +| **Pricing Value** | $20/month for advanced features + unlimited video recordings | $18/month for basic unlimited meetings | +| **Sales Coaching** | Advanced playbook tracking, objection analysis, performance insights | Basic meeting notes only | +| **Multi-Meeting Intelligence** | Cross-meeting trend analysis and aggregated insights | Single meeting focus | +| **Content Sharing** | Video clips, highlight reels, and training content creation | Text-based notes sharing | +| **Language Support** | 30+ languages with high-accuracy transcription | Limited language capabilities | +| **Integration Depth** | 5,000+ tool integrations with workflow automation | Basic note enhancement | #### tl;dv vs Granola Limitations @@ -271,6 +270,6 @@ The real question is not whether you want AI meeting notes. Granola already prov The real question is what you want to own. -If you want the closest thing to Granola with more control, Char is the cleanest fork in the road. You keep the notepad-first workflow, but your notes stay as files, your AI stack stays flexible, and you are not betting your whole system on one vendor's roadmap. +If you want the closest thing to Granola with more control, Anarlog is the cleanest fork in the road. You keep the notepad-first workflow, but your notes stay as files, your AI stack stays flexible, and you are not betting your whole system on one vendor's roadmap. -[Download Char](/download) if that trade-off sounds closer to how you actually want to work. +[Download Anarlog](https://anarlog.so) if that trade-off sounds closer to how you actually want to work. diff --git a/apps/web/content/articles/how-to-have-productive-one-on-one-meetings.mdx b/apps/web/content/articles/how-to-have-productive-one-on-one-meetings.mdx index a94146f169..325f161ba0 100644 --- a/apps/web/content/articles/how-to-have-productive-one-on-one-meetings.mdx +++ b/apps/web/content/articles/how-to-have-productive-one-on-one-meetings.mdx @@ -3,62 +3,55 @@ meta_title: "How to Run Productive One-on-One Meetings as a Manager" display_title: "How to Have Productive One-on-One Meetings: A Guide for Managers" meta_description: "Stop wasting time on status updates disguised as 1:1s. Learn how to run one-on-one meetings that actually develop your team without burning you out." author: "John Jeong" -coverImage: "/api/assets/blog/how-to-have-productive-one-on-one-meetings/cover.png" category: "Guides" date: "2025-11-10" --- One-on-ones should reduce your management overhead, not add to it. They should catch small problems before they become crises. They should help your team grow. -Instead, they've become just another meeting in your calendar. +Instead, they've become just another meeting on your calendar. -You're asking "how's everything going?" because you forgot what you talked about last time. - -You're making commitments you don't track. - -You're having the same conversation about career development every month with no actual progress. +You ask "how's everything going?" because you forgot what you talked about last time. You make commitments you don't track. You have the same career-development conversation every month with no actual progress. ## What makes one-on-ones actually productive -A productive 1:1 focuses on surfacing real concerns before they escalate, identifying patterns across conversations, and making concrete commitments that both of you actually follow through on. +A productive 1:1 surfaces real concerns before they escalate, finds patterns across conversations, and produces commitments both sides follow through on. -It's not a status update (that's what standups are for). It's not you doing all the talking. It's not checking boxes on generic development questions or rehashing things everyone already knows. +It is not a status update — that is what standups are for. It is not you doing all the talking. It is not a generic development checklist. -Instead, your direct report gets uninterrupted time to raise concerns. You identify patterns in what's blocking them across multiple conversations. Both of you make commitments and follow through. Your team member feels heard and supported. You understand where each person is heading and help them get there. +Your direct report gets uninterrupted time to raise concerns. You spot what is blocking them across multiple conversations. Both of you commit and follow through. -If your 1:1s are draining you, you're doing them wrong. +If your 1:1s are draining you, you are doing them wrong. ## 5 strategies to run better one-on-ones without burning out ### 1. Stop starting every conversation from scratch -Most 1:1s lose their first fifteen minutes to reconstruction. You're trying to remember what you talked about last time. Your direct report is doing the same thing. You're both pulling from vague memories instead of picking up where you left off. +Most 1:1s lose the first fifteen minutes to reconstruction. You are trying to remember what you talked about last time. Your report is doing the same. Both of you pull from vague memories instead of picking up where you left off. -This is why managers end up asking "so... how's everything going?" It's the default question when nobody actually remembers specifics from three weeks ago. +That is why managers default to "so... how's everything going?" It is the question you ask when nobody actually remembers specifics from three weeks ago. Before each 1:1, spend five minutes reviewing your actual conversation history with this person. Not bullet points you wrote down—the actual conversations. -If you're using Char AI Notetaker, open Contacts View in Finder and search the person's name. You'll see every 1:1 you've had with them, organized chronologically. Click on any past meeting and use AI Chat to ask: +If you're using Anarlog AI Notetaker, open Contacts View in Finder and search the person's name. You'll see every 1:1 you've had with them, organized chronologically. Click on any past meeting and use AI Chat to ask: - "What concerns has Alex raised in our last three conversations?" - "What did we commit to last time?" - "What career goals has she mentioned?" -You get specific answers pulled from actual transcripts, not your faulty reconstruction. This preparation takes five minutes but transforms the conversation. You walk in knowing exactly where you left off: "Last time you mentioned feeling blocked on the API refactor because docs were incomplete. How did that resolve?" +You get specific answers pulled from actual transcripts, not your faulty reconstruction. Five minutes of prep, and you walk in knowing where you left off: "Last time you mentioned feeling blocked on the API refactor because docs were incomplete. How did that resolve?" -Your direct report immediately knows you're paying attention and the conversation moves forward. +Your report knows you were paying attention. The conversation moves forward. ### 2. Be fully present—let AI handle the documentation -The biggest drain on 1:1s isn't the conversation itself. It's trying to listen, engage, and document simultaneously. - -You're having a meaningful discussion about someone's career concerns while frantically typing notes, worried you'll forget the important parts. Your attention is split. Your direct report can tell. +The drain on 1:1s is not the conversation. It is trying to listen, engage, and document at the same time. -Use Char to automatically capture the entire conversation. It's open source, stores everything as plain markdown files, and lets you choose your AI stack—critical for sensitive conversations about performance, compensation, or personal issues. No bot joins your call. No lock-in. +You are talking through someone's career concerns while frantically typing, worried you will forget the important parts. Your attention is split, and your report can tell. -After the meeting, Char gives you a complete transcript. Hover over any part of your AI-generated summary to see the exact quote from the conversation. Your manual notes get enhanced with full context without you having to choose between being present and capturing information. +Use Anarlog to capture the conversation. It is open source, stores everything as plain markdown files on your device, and lets you choose your AI stack — which matters for conversations about performance, compensation, or personal issues. No bot joins the call. - +After the meeting, you get a full transcript. Hover over any line of the AI summary to see the exact quote behind it. You stop choosing between being present and remembering what was said. ### 3. Use templates to structure your 1:1s consistently @@ -66,7 +59,7 @@ Every manager develops patterns for their one-on-ones. You ask certain questions That framework lives in your head, which means it's inconsistent across team members and forgotten when you're rushed. -With Char's custom templates, you can define exactly how your one-on-one notes should be structured. A 1:1 template might include: +With Anarlog's custom templates, you can define exactly how your one-on-one notes should be structured. A 1:1 template might include: - What they wanted to discuss (their agenda, not yours) - Current blockers and what's preventing resolution @@ -75,19 +68,17 @@ With Char's custom templates, you can define exactly how your one-on-one notes s - Commitments made (both manager and direct report) - Concerns or tensions that need addressing -To create a custom template, define your sections and add system instructions. For instance: "For every commitment made, note who owns it and extract the specific deadline mentioned. Highlight any recurring concerns that appeared in previous conversations." +To build a custom template, define your sections and add system instructions. For example: "For every commitment made, note who owns it and pull the deadline. Flag any concern that has come up in past conversations." -Set this as your default template in Settings, and every 1:1 automatically gets summarized in this format. No more inconsistent notes. No more forgetting to cover important areas. - -Your direct reports also benefit. When they know the structure, they can prepare better. When notes are consistent, they can track their own progress over time. +Set this as your default template in Settings. Every 1:1 gets summarized in the same format. Your reports benefit too — when they know the structure, they can prepare for it. ### 4. Document commitments immediately (both yours and theirs) -Your direct report mentions a blocker. You say "I'll look into that." Then you forget. They bring it up again next time. You forgot again. +Your report mentions a blocker. You say "I'll look into that". Then you forget. They bring it up again next time. You forgot again. -Same thing happens in reverse. They say "I'll draft that proposal." Next 1:1, you don't ask about it. They didn't do it, but they didn't have to explain why. The accountability is missing on both sides. +Same thing in reverse. They say "I'll draft that proposal". Next 1:1, you do not ask about it. They did not do it, and they never had to explain why. -Right after each 1:1, capture what needs follow-up action. With Char, ask AI Chat: "What commitments did I make?" and "What commitments did Alex make?" +Right after each 1:1, capture what needs follow-up action. With Anarlog, ask AI Chat: "What commitments did I make?" and "What commitments did Alex make?" You'll get specific items: @@ -96,15 +87,15 @@ You'll get specific items: - **Alex:** Draft team charter by next Tuesday - **Alex:** Send feedback on the API proposal -Add these to your task system with specific language. Not vague items like "follow up on Alex's stuff" but concrete tasks: "Email design lead about feedback turnaround time - due Friday." +Add these to your task system with specific language. Not "follow up on Alex's stuff" but "Email design lead about feedback turnaround time — due Friday". -Share the action items with your direct report right after the meeting. Send them a quick message: "Here's what we both committed to - me: design feedback and HR policy by Friday. You: team charter and API review by Tuesday." Now you're both clear on what needs to happen before the next 1:1. +Share the action items with your report right after the meeting. Something like: "Here's what we both committed to — me: design feedback and HR policy by Friday. You: team charter and API review by Tuesday". Now both of you know what needs to happen before the next 1:1. ### 5. Separate career conversations from tactical ones Most managers try to pack everything into one 1:1: status updates, immediate blockers, long-term development, career goals, feedback, team dynamics. -The result: You spend thirty minutes on tactical issues and rush through "so, uh, how are you thinking about your career?" in the last five minutes. The answer is always generic: "Yeah, I want to grow, maybe move toward senior engineer eventually." +The result: thirty minutes on tactical issues, then a rushed "so, uh, how are you thinking about your career?" in the last five minutes. The answer is always generic: "Yeah, I want to grow, maybe move toward senior engineer eventually". Nobody benefits from that conversation. @@ -114,13 +105,13 @@ Keep it tight. If you're spending more than 30 minutes on tactical issues every **Monthly career conversations (60 minutes):** Deep dive on development, growth, and trajectory. These need space and focus, not five rushed minutes at the end of a tactical meeting. -Use Char to track development over time. Search across all your career conversations with someone to see: What goals did they set six months ago? Did they actually work toward them? What barriers kept coming up? +Use Anarlog to track development over time. Search across all your career conversations with someone to see: What goals did they set six months ago? Did they actually work toward them? What barriers kept coming up? This longitudinal view is impossible to maintain in your head across multiple reports. But it's exactly what makes career conversations productive instead of generic. ## The manager's system: putting it all together -Here's what the actual workflow looks like when you're using Char: +Here's what the actual workflow looks like when you're using Anarlog: **Before each 1:1 (5 minutes):** @@ -132,7 +123,7 @@ Here's what the actual workflow looks like when you're using Char: - Listen more than you talk - Ask follow-up questions on what they raise, not your prepared script -- Let Char capture the conversation automatically—stay present, don't take frantic notes +- Let Anarlog capture the conversation automatically—stay present, don't take frantic notes **After the 1:1 (3 minutes):** @@ -147,22 +138,19 @@ Here's what the actual workflow looks like when you're using Char: - Identify recurring themes and patterns by scanning through summaries - Prepare for career conversations with specific examples and observations -This system reduces prep time while improving quality. You're not scrambling before each meeting because context is always available. You're not forgetting commitments because they're systematically tracked and shared. You're not missing patterns because you can review conversation history in one place. +Less prep, better meetings. You stop scrambling before each one because context is already there. You stop forgetting commitments because they are tracked and shared. You catch patterns because the history is in one place. -Your mental energy goes to the actual conversation, not the overhead of managing the conversation. +Your mental energy goes to the conversation, not the overhead around it. ## Stop treating one-on-ones like they're optional -Management advice hasn't evolved, but the tools have. - -You don't need to manually track everyone's development in spreadsheets. You don't need to remember every conversation across eight direct reports. You don't need to start each 1:1 from scratch, reconstructing context from memory. +Management advice has not evolved. The tools have. -Use AI to handle the overhead that's not actually management. Let Char capture conversations automatically—it's open source with zero lock-in, storing everything as plain markdown files you fully own. +You do not need to track everyone's development in spreadsheets, remember every conversation across eight reports, or rebuild context from memory before every 1:1. -Search across all your 1:1s to identify patterns. Ask AI about recurring themes and commitments. Track what each person cares about over time without drowning in manual documentation. +Let AI handle the overhead. Anarlog captures conversations, stores them as plain markdown on your device, and is open source. No vendor lock-in. -Your brain should be doing what it does best: connecting with people, identifying what they need, helping them grow. Not trying to be a human database of scattered conversations. +Search across all your 1:1s for patterns. Ask AI about recurring themes and commitments. Track what each person cares about over time without becoming a human database. -[Download Char free](/) and run one-on-ones that actually develop your team without burning you out. +[Download Anarlog free](/) and run one-on-ones that actually develop your team without burning you out. - \ No newline at end of file diff --git a/apps/web/content/articles/how-to-participate-in-meetings-effectively.mdx b/apps/web/content/articles/how-to-participate-in-meetings-effectively.mdx index a1f14eb726..78f5bd5309 100644 --- a/apps/web/content/articles/how-to-participate-in-meetings-effectively.mdx +++ b/apps/web/content/articles/how-to-participate-in-meetings-effectively.mdx @@ -3,21 +3,18 @@ meta_title: "Top 5 Strategies to Participate in Meetings Effectively" display_title: "How to Participate in Meetings Effectively?" meta_description: "Learn how to contribute meaningfully in meetings without exhausting your brain. Strategies for speaking strategically, building on context, and following through." author: "John Jeong" -coverImage: "/api/assets/blog/how-to-participate-in-meetings-effectively/cover.png" featured: false category: "Guides" date: "2025-11-07" --- -You've heard the advice. "Be an active listener." "Come prepared." "Ask thoughtful questions." +You have heard the advice. "Be an active listener". "Come prepared". "Ask thoughtful questions". -Solid advice, sure. But it doesn't help much when you're in your sixth meeting of the day, someone asks you a direct question, and you realize you have no idea what the last ten minutes were about because your brain was still processing the previous meeting. +Solid advice. Useless when you are in your sixth meeting of the day, someone asks you a direct question, and you realize you have no idea what the last ten minutes were about because your brain is still chewing on the meeting before this one. -Standard meeting participation advice assumes you're operating at peak cognitive capacity—rested, focused, with unlimited mental bandwidth to engage deeply and track everything simultaneously. +Standard meeting advice assumes you are rested, focused, and operating at full cognitive capacity. You are not. You are context-switching between seven projects, pulled into this call with five minutes' notice, trying to look engaged while your brain begs for a break. -In reality, you're context-switching between seven different projects, pulled into this meeting with five minutes' notice, trying to look engaged while your brain screams for a break. - -So let's talk about what actually works when you need to participate effectively but you're running on cognitive fumes. +Let's talk about what actually works when you are running on fumes. ## The real problem with meeting participation @@ -30,21 +27,21 @@ Effective participation requires doing multiple things at once: - Maintaining awareness of group dynamics - Documenting important points for later -That's a lot of cognitive load. When you're trying to do all of it simultaneously, you end up doing none of it particularly well. +That is a lot of cognitive load. Try to do all of it at once and you do none of it well. -Research from Stanford shows that multitasking can reduce productivity by up to 40%. Yet meetings expect people to effectively "multitask" their attention: listening, thinking, responding, and documenting all at once. +Research from Stanford shows multitasking can drop productivity by up to 40%. But meetings expect exactly that: listening, thinking, responding, and documenting all at once. -The solution isn't to "focus harder." It's to build systems that reduce the cognitive overhead of participation so your brain can focus on what actually matters: thinking and contributing. +The fix is not "focus harder". It is building systems that take the overhead off your brain so you can think and contribute. ## Meeting participation strategies that work ### 1. Pre-load context instead of reconstructing it live -You walk into a meeting and spend the first ten minutes trying to remember what was discussed last time. Who said what. What was decided. What's still unresolved. +You walk into a meeting and burn the first ten minutes trying to remember what was decided last time and what is still unresolved. -While you're mentally reconstructing context, the conversation has already moved on. You're three steps behind, and by the time you catch up, you've missed the critical moment to contribute. +While you are reconstructing context, the conversation has already moved on. By the time you catch up, you have missed the moment to contribute. -Instead, prep before the meeting starts. +Prep before the meeting starts instead. Open your notes from the last relevant conversation and recap: @@ -52,91 +49,90 @@ Open your notes from the last relevant conversation and recap: - What concerns did people raise that might resurface? - What did I commit to that needs an update? -If you're using [Char AI Notetaker](/), use Search (cmd + k) to find every past mention of the project or topic. Type the project name, and you'll see every note where it was discussed, with the exact context of who said what and when. +If you're using [Anarlog AI Notetaker](/), use Search (cmd + k) to find every past mention of the project or topic. Type the project name, and you'll see every note where it was discussed, with the exact context of who said what and when. Then use AI Chat to ask targeted questions: "What were the main objections to the pricing model?" or "What did Sarah say about the technical constraints?" You get specific answers pulled from actual transcripts, not your faulty memory. -This takes five minutes. Those five minutes mean you walk in prepared to contribute immediately instead of spending the first chunk of the meeting getting oriented. +Five minutes of prep. You walk in ready to contribute instead of catching up. **Want to optimize your entire meeting workflow?** Check out our [meeting preparation checklist](/blog/meeting-preparation-checklist) for the pre-meeting work that sets up better participation. ### 2. Use strategic silence, not constant commentary -Effective participation doesn't mean contributing often. The people who talk the most in meetings aren't necessarily the most effective participants. Often they're thinking out loud, working through their ideas in real-time at everyone else's expense. +Contributing often is not the same as participating well. The people who talk the most are usually thinking out loud at everyone else's expense. -The most effective participants speak strategically: +The effective participants speak strategically: -- **Ask the question everyone's thinking but no one wants to ask.** "Before we go further, can we confirm we all have budget approval for this?" This saves twenty minutes of discussion on something that might be blocked anyway. -- **Redirect when the conversation drifts.** "This is interesting, but I want to make sure we answer the original question first." You don't need to be the meeting leader to do this. -- **Surface the unspoken tension.** "I'm sensing some hesitation about this approach. Can we talk about that directly?" Naming the dynamic everyone feels but no one acknowledges moves conversations forward faster than pretending everything's fine. -- **Bridge perspectives.** "It sounds like engineering is concerned about timeline while marketing needs this for the launch. Can we discuss what's actually negotiable?" This identifies the actual problem to solve. +- **Ask the question everyone is thinking but nobody wants to ask.** "Before we go further, can we confirm we all have budget approval for this?" Saves twenty minutes on something that might be blocked anyway. +- **Redirect when the conversation drifts.** "This is interesting, but can we answer the original question first?" You do not need to be the meeting leader to do this. +- **Name the unspoken tension.** "I'm sensing some hesitation. Can we talk about that directly?" Naming the dynamic moves things forward faster than pretending it is not there. +- **Bridge perspectives.** "Engineering is worried about timeline. Marketing needs this for the launch. What is actually negotiable?" This finds the real problem. -The rest of the time, listen actively, track what's being said, and let others take the floor. Your silence is strategic, not disengagement. +The rest of the time, listen and let others take the floor. Silence is a contribution, not disengagement. ### 3. Separate capture from processing -The biggest cause of [meeting fatigue](/blog/how-to-reduce-meeting-fatigue) is trying to participate and document simultaneously. +The biggest cause of [meeting fatigue](/blog/how-to-reduce-meeting-fatigue) is trying to participate and document at the same time. -You're in the middle of making a point, and someone drops a critical piece of information. Do you stop mid-sentence to write it down and lose your train of thought? Keep talking and hope you remember later? Try to hold it in working memory while finishing your point? +You are mid-point and someone drops a critical detail. Do you stop and write it down? Keep talking and hope to remember? Try to hold it in working memory while finishing your sentence? -This is why people take terrible notes. They're not bad at note-taking; they're trying to do two incompatible tasks at once. +This is why people take bad notes. They are not bad at note-taking. They are trying to do two incompatible things at once. -During the meeting, focus entirely on participation. Use a tool like Char that runs locally on your device, automatically capturing both what's said and what you're thinking. No bot joining the call, no data leaving your machine, just automatic documentation. +During the meeting, focus on participation. Use a tool like Anarlog that runs locally and captures both what is said and what you typed. No bot in the call, nothing leaving your machine. -If you prefer manual note-taking, jot down fragments, keywords, reactions. But don't try to create polished, comprehensive notes in real-time. You can use Char to enhance your manual notes using the meeting transcript afterward. +If you prefer manual notes, jot down fragments and reactions. Do not try to write polished comprehensive notes live. Anarlog can enhance your fragments with the transcript afterward. -After the meeting, process what was captured. This is when you review the transcript, identify action items, connect dots, and organize information. Your AI-generated summary is already waiting. Hover over any part to see the exact quote from the conversation. +After the meeting, process what was captured. Review the transcript, pull action items, organize. Hover over any line of the AI summary to see the exact quote behind it. -Ask AI Chat "What are my action items from this meeting?" instead of hunting through notes. Search across meetings to track how topics have evolved over time. +Ask AI Chat "What are my action items from this meeting?" instead of scrolling notes. Search across meetings to see how a topic has evolved. -Your brain is fully engaged during the conversation, and organizing happens later when you have the mental space for it. +Your brain is fully on the conversation. Organizing happens later, when you have space for it. ### 4. Manage your energy, not just your calendar -You can't participate effectively if you're mentally exhausted. And you will be mentally exhausted if you're trying to engage deeply in eight back-to-back meetings. +You can not participate well if you are mentally fried. And you will be fried after eight back-to-back meetings. -The standard advice is "schedule breaks between meetings." Except you don't control half your calendar. Meetings get added, extended, moved with no regard for your carefully planned buffer time. +The standard advice — "schedule breaks between meetings" — assumes you control your calendar. You don't. Meetings get added, extended, and moved with no regard for your buffer time. -Instead of controlling your schedule, manage your participation energy strategically: +So manage your participation energy instead of your schedule: -- **Identify which meetings need your best thinking.** A status update needs your attention but not your deepest analytical thinking. A strategic planning session does. -- **Frontload your energy to high-stakes meetings.** If you have a critical client call at 2 PM, don't schedule three cognitively demanding meetings before it. -- **Use lighter participation modes when you're drained.** If you're running on fumes, you can still contribute by asking clarifying questions, taking on action items for later execution, or providing specific factual information. -- **Actually take the micro-breaks you have.** Between meetings, resist the urge to immediately process what just happened or prep for what's next. Take sixty seconds to look away from your screen. Stand up. Your brain needs transition time. +- **Identify which meetings need your best thinking.** A status update needs attention. A strategic planning session needs analysis. They are not the same. +- **Frontload energy for high-stakes meetings.** If you have a critical client call at 2 PM, do not stack three cognitively heavy meetings before it. +- **Use lighter modes when drained.** Ask clarifying questions, volunteer for action items, contribute factual information. +- **Take the micro-breaks you have.** Between meetings, do not immediately process or prep. Sixty seconds away from the screen. Your brain needs transition time. -Use Char's automatic capture so you're not doing the mental gymnastics of trying to remember everything from each meeting. +Use Anarlog's automatic capture so you are not also doing memory gymnastics on top of everything else. ### 5. Close the loop on your commitments immediately -You say "I'll look into that" in a meeting. The meeting ends. You move to the next thing. Three days later someone asks about it, and you've completely forgotten. +You say "I'll look into that" in a meeting. The meeting ends. You move on. Three days later someone asks about it, and you have completely forgotten. -It's not that you didn't care. You relied on memory instead of systems. +You did not stop caring. You relied on memory instead of a system. Right after the meeting: -- **Review your action items immediately.** Don't wait until later when everything blurs together. With Char, ask AI Chat "What are my action items?" and get them instantly from your meeting transcript. -- **Add them to your task system with specific deadlines.** "Look into X" is not actionable. "Research X and send summary to team by Thursday 3 PM" is. -- **Block time to actually do them.** An action item without calendar time is wishful thinking. +- **Review your action items now.** Not later, when everything blurs together. With Anarlog, ask AI Chat "What are my action items?" and get them straight from the transcript. +- **Add them with specific deadlines.** "Look into X" is not actionable. "Research X and send summary to team by Thursday 3 PM" is. +- **Block time to do them.** An action item without calendar time is wishful thinking. Before the next meeting: -- **Check what you committed to in the last one.** Search your past notes for action items with your name. With Char's Search, find every instance where you said "I'll handle this" or "I'll follow up on that." -- **Update the group proactively.** Don't wait to be asked. "Quick update on the database research I said I'd do—found three options, here's my recommendation." +- **Check what you committed to last time.** Search past notes for action items with your name. Anarlog's Search finds every "I'll handle this" or "I'll follow up on that". +- **Update the group before they ask.** "Quick update on the database research — found three options, here is my recommendation". -When you consistently deliver on commitments, people take your contributions seriously. When you don't, your participation becomes background noise. +Deliver on commitments and people take your contributions seriously. Don't, and your participation becomes background noise. ## Stop participating like it's 2015 -Meeting participation advice hasn't evolved, but the tools have. +Meeting advice has not evolved. The tools have. -You don't need to frantically take notes while trying to contribute. You don't need to reconstruct context from memory. You don't need to manually track every commitment across dozens of conversations. +You do not need to frantically take notes while trying to contribute, reconstruct context from memory, or track every commitment across dozens of conversations by hand. -Use AI to handle the cognitive overhead that's not actually thinking. Let Char capture conversations automatically, locally on your device without any data leaving your machine. Search across all your past meetings to find context instantly. Ask AI specific questions about what was discussed instead of hunting through transcripts. +Let AI handle the overhead. Anarlog captures conversations locally on your device. Search across past meetings for context. Ask AI questions instead of scrolling transcripts. -Your brain should do what it does best: connecting ideas, spotting patterns, contributing unique insights. Not being a human tape recorder. +Your brain should be connecting ideas and spotting patterns, not being a human tape recorder. -Meetings haven't changed. Your system for participating in them can. +Meetings have not changed. Your system for participating in them can. -[Download Char free](/download) and participate in meetings without the cognitive overhead. +[Download Anarlog free](https://anarlog.so) and participate in meetings without the cognitive overhead. - \ No newline at end of file diff --git a/apps/web/content/articles/how-to-reduce-meeting-fatigue.mdx b/apps/web/content/articles/how-to-reduce-meeting-fatigue.mdx index 1887a6f385..d5450966c7 100644 --- a/apps/web/content/articles/how-to-reduce-meeting-fatigue.mdx +++ b/apps/web/content/articles/how-to-reduce-meeting-fatigue.mdx @@ -2,66 +2,65 @@ meta_title: "How to Reduce Meeting Fatigue When Meetings Are Your Job" meta_description: "Can't cut meetings? Learn how AI note-taking and smart systems reduce meeting fatigue for sales, consulting, and customer-facing roles." author: "John Jeong" -coverImage: "/api/assets/blog/how-to-reduce-meeting-fatigue/cover.png" category: "Guides" date: "2025-10-31" --- -You've read the articles. You know the drill. "Cut unnecessary meetings!" "Block focus time!" "Just say no!" +You have read the articles. "Cut unnecessary meetings!" "Block focus time!" "Just say no!" -Great advice. Truly. Except when your job literally is meetings. +Good advice — except when your job literally is meetings. -If you're in sales, customer success, consulting, recruiting, or any role where human conversation is the actual work—not a distraction from it—then the standard productivity advice feels backwards. It's like telling a marathon runner to just run less. Sure, that would help with fatigue, but you wouldn't finish the race. +If you are in sales, customer success, consulting, or recruiting, conversation is the actual work. The standard productivity advice is backwards. It is like telling a marathon runner to run less. Sure, less running, less fatigue. You also don't finish the race. -So let's talk about the meeting fatigue nobody wants to acknowledge: the kind that happens even when every single meeting on your calendar matters. You optimize your schedule, batch your calls, and still end each day feeling like your brain has been through a blender. +This is the meeting fatigue nobody talks about: the kind that hits even when every meeting on your calendar matters. You optimize your schedule, batch your calls, and still end the day feeling like your brain went through a blender. ## What causes meeting fatigue? The science behind Zoom exhaustion -[Microsoft Research ran studies](https://www.microsoft.com/en-us/worklab/work-trend-index/brain-research) using EEG monitoring and found something striking: back-to-back video meetings cause stress to build up over time, with beta wave activity (associated with stress and anxiety) increasing progressively throughout the day. +[Microsoft Research used EEG monitoring](https://www.microsoft.com/en-us/worklab/work-trend-index/brain-research) and found that back-to-back video meetings build stress over time. Beta wave activity (linked to stress and anxiety) climbs progressively through the day. -Even brief transitions between meetings—a short walk, some light stretching—don't clear this buildup. Your mental state stays stuck between calls. +Brief transitions — a short walk, some stretching — do not clear it. Your mental state stays stuck between calls. -The real issue isn't the meetings themselves. It's the cognitive load of trying to capture and retain information while simultaneously participating in the conversation. You're listening, responding thoughtfully, asking follow-up questions, and frantically typing notes you hope to decipher later. Your brain is juggling three jobs at once: processing, engaging, and documenting. +The issue is not the meetings. It is the cognitive load of capturing and retaining information while participating in the conversation. You are listening, responding, asking follow-ups, and typing notes you hope to decipher later. Three jobs at once: processing, engaging, documenting. -Then the meeting ends. You've got seven minutes before your next call. Do you polish your notes while everything's fresh? Actually process what you learned? Take a breath and reset? Or are you already in your next meeting? +Then the meeting ends. Seven minutes before the next call. Polish notes while it is fresh? Process what you learned? Take a breath? Or jump straight into the next call? -Most people end up in option D. By day's end, you've had eight substantive conversations with fragments of notes scattered across documents, half-remembered insights, and a vague sense that someone said something important in the 2 PM call but you can't quite recall what. +Most people pick the next call. By the end of the day you have had eight substantive conversations, fragments of notes scattered across documents, half-remembered insights, and a vague sense someone said something important in the 2 PM call. -The fatigue isn't from talking. It's from the constant effort of trying to be present and preserve information simultaneously. +The fatigue is not from talking. It is from trying to be present and preserve information at the same time. ## 5 ways to reduce meeting fatigue without cutting meetings If you can't reduce your meeting load, focus on reducing the cognitive overhead around meetings. -(Note: Char appears a few times below because I built it to solve these exact problems.) +(Note: Anarlog appears a few times below because I built it to solve these exact problems.) ### 1. Use AI note-taking tools to stop multitasking during calls -Your brain excels at thinking but struggles with storage. Holding meeting details in your head while participating in new conversations drains attention and energy. +Your brain is good at thinking and bad at storage. Holding meeting details in your head while running into the next conversation drains both. -Better systems eliminate the need for memory altogether. [AI meeting assistants](/blog/best-ai-meeting-assistant-for-taking-notes) like Char run entirely on your device, transcribing conversations in real-time without you doing anything. No bot joins your calls. No data leaves your machine. Just automatic documentation of what was said. +Better systems remove the need for memory altogether. [AI meeting assistants](/blog/best-ai-meeting-assistant-for-taking-notes) like Anarlog run on your device and transcribe conversations in real time. No bot in the call. No data leaving your machine. -When the meeting ends, you don't need to do anything immediately. Take that two-minute break. Actually transition. The conversation is captured for later. +When the meeting ends, you do not need to do anything. Take the two-minute break. Actually transition. The conversation is captured. -If you're someone who feels productive taking manual notes during meetings, keep doing it. Char still helps by enhancing your fragments with full transcript context. Hover over any part of the AI-generated summary to see the exact quote from the conversation—you get active note-taking without worrying you missed something important. +If manual note-taking helps you stay engaged, keep doing it. Anarlog enhances your fragments with full transcript context. Hover over any line of the AI summary to see the exact quote behind it. ### 2. Use voice notes instead of typing when possible -Speaking for an hour, then switching to typing mode, then back to speaking, then typing again for follow-ups burns mental energy with each switch. +Speaking for an hour, then typing, then speaking, then typing again for follow-ups burns mental energy at every switch. Use voice notes instead: -- **Before the meeting**: Record your prep thoughts—context you want to remember, questions to ask, background on attendees -- **During the meeting**: Keep it running to capture the conversation -- **After the meeting**: Capture your immediate reactions and insights before switching tasks +- **Before**: Record your prep — context you want to remember, questions to ask, background on attendees +- **During**: Keep it running to capture the conversation +- **After**: Capture your immediate reactions before moving on -Tools like Char weave together your pre-meeting context, the conversation itself, and your post-meeting thoughts into one cohesive summary. You're not constantly shifting between communication modes, which means less cognitive friction as the day progresses. +Tools like Anarlog merge pre-meeting context, the conversation, and your post-meeting thoughts into one summary. You are not switching modes all day. ### 3. Review meeting notes strategically using AI -You've got maybe 10 minutes between meetings. Don't scroll through transcripts or organize scattered notes. Use AI to extract what you actually need. +You have maybe 10 minutes between meetings. Don't scroll through transcripts or organize notes. Use AI to pull what you need. -With Char, each meeting already has a summary with action items. When you need to review, you can: +With Anarlog, each meeting already has a summary with action items. When you need to review, you can: - Ask AI Chat "What are my action items?" instead of hunting through notes - Search across all meetings with cmd + k to find what someone said about a specific topic @@ -69,53 +68,50 @@ With Char, each meeting already has a summary with action items. When you need t Spend your between-meeting time on thinking and follow-up, not reconstructing what happened or figuring out what to do next. - - ### 4. Use meeting templates to reduce prep fatigue -Templates prevent you from starting from scratch each time, wondering how to structure the conversation. Your brain focuses on the actual discussion, not the meta-work of designing it. +Templates stop you from starting from scratch every time. Your brain focuses on the discussion, not the meta-work of designing it. -Create standard templates for recurring meeting types. Customer discovery calls might follow: intro (5 min), pain points (15 min), solution fit (15 min), next steps (5 min). 1-on-1s: wins, blockers, growth, action items. Demos: context questions, walkthrough, Q&A, close. +Build standard templates for recurring meetings. Customer discovery: intro (5 min), pain points (15 min), solution fit (15 min), next steps (5 min). 1-on-1s: wins, blockers, growth, action items. Demos: context questions, walkthrough, Q&A, close. -Char includes built-in templates for common meeting types, and you can create custom ones. Set a default template in Settings so every meeting gets summarized in that format, or select a template after finishing a recording for specific meetings. +Anarlog ships with templates for common meeting types and lets you build your own. Set a default in Settings so every meeting gets summarized in that format, or pick a template after recording. -To create a custom template, define your sections and add system instructions for the AI. If you do user interviews, your instruction could be: "For every bullet point, attach the user's actual quote as a reference." Every user interview summary then comes with supporting evidence automatically. For sales calls, output in JSON format with fields like pain points, budget, timeline, and decision makers—perfect for pushing data to a CRM. +To build a custom template, define your sections and add system instructions. For user interviews: "For every bullet point, attach the user's actual quote as a reference". Every summary comes with evidence. For sales calls, output in JSON with fields like pain points, budget, timeline, and decision makers — push it straight to a CRM. ### 5. Stop reinventing the wheel in every single meeting -You've given hundreds of demos and handled dozens of objections. All that data is captured. But you treat every conversation as if it's the first time. +You have given hundreds of demos and handled dozens of objections. All of that is captured. But you treat every conversation like it is the first. -With AI notetaking, create a system to learn from your patterns, identify what worked, and refine it with each subsequent conversation: +Build a system that learns from your patterns: -- Search past successful demos to see exactly how you explained complex features when prospects got excited -- Ask AI about specific objections and how you've handled them before. Use what worked instead of improvising the same responses from scratch -- Before any call, search the person's name to see your complete conversation history. What they cared about, what questions they asked, where you left off -- Use the Contacts View in Finder to see all meetings with a company organized in one place. Spot patterns in what resonates with that organization +- Search past demos to see exactly how you explained a feature the time a prospect got excited +- Ask AI how you have handled a specific objection before. Use what worked. +- Before a call, search the person's name to see the full history — what they cared about, what they asked, where you left off +- Use Contacts View in Finder to see every meeting with a company in one place -Each conversation gets easier because you're building on what worked instead of starting from scratch every time. +Each conversation gets easier because you are building on what worked. ## Meeting fatigue vs burnout: what's the difference? -Meeting fatigue is the daily exhaustion from too many conversations without adequate breaks or processing time. Burnout is a chronic state of physical and emotional exhaustion that doesn't improve with rest. +Meeting fatigue is daily exhaustion from too many conversations without adequate breaks or processing time. Burnout is chronic — it does not improve with rest. -Meeting fatigue can contribute to burnout, but they're different. Meeting fatigue typically improves with the strategies above, better meeting hygiene (breaks, scheduling), and rest. +Meeting fatigue can contribute to burnout. They are not the same. Meeting fatigue improves with the strategies above plus better meeting hygiene and rest. -Burnout requires deeper intervention: job changes, therapy, or significant life restructuring. +Burnout needs deeper intervention: job changes, therapy, real life restructuring. -If you're implementing all the strategies in this article and still feeling chronically exhausted, you might be dealing with burnout rather than just meeting fatigue. That's worth addressing with a professional. +If you are doing everything in this article and still feel chronically exhausted, you are probably dealing with burnout, not meeting fatigue. Talk to a professional. ## Reducing meeting fatigue -The advice to "have fewer meetings" isn't wrong. If you can cut unnecessary meetings, do it. +"Have fewer meetings" is not wrong. If you can cut, cut. -But if that's not possible, reduce the cognitive overhead around meetings. Stop trying to listen, engage, document, and remember everything at once. +If you can't, reduce the cognitive overhead around the ones you keep. Stop trying to listen, engage, document, and remember everything at once. -I built Char because I was exhausted from the mental gymnastics: frantically typing notes, wondering if I captured things correctly, reconstructing conversations from fragments. It wasn't the talking that drained me—it was everything else. +I built Anarlog because I was tired of the mental gymnastics: frantically typing, wondering if I caught the right thing, reconstructing conversations from fragments. The talking did not drain me. Everything else did. -Your meetings don't need to change. Your system does. Let AI handle documentation and organization so your brain can focus on the actual conversation. +Your meetings do not need to change. Your system does. -Six months in, meetings still tire me out. But it's the good kind of tired—from doing meaningful work, not from trying to be a human tape recorder. +Six months in, meetings still tire me out. But it is the good kind of tired — from doing the work, not from trying to be a human tape recorder. -**Ready to stop the mental juggling?** [Download Char for free](/download). It runs on your Mac, keeps everything local, and handles the exhausting parts automatically. +**Ready to stop the mental juggling?** [Download Anarlog for free](https://anarlog.so). It runs on your Mac, keeps everything local, and handles the exhausting parts automatically. - \ No newline at end of file diff --git a/apps/web/content/articles/how-to-transcribe-zoom-calls.mdx b/apps/web/content/articles/how-to-transcribe-zoom-calls.mdx index 47b2e9f4eb..2f5fe95a16 100644 --- a/apps/web/content/articles/how-to-transcribe-zoom-calls.mdx +++ b/apps/web/content/articles/how-to-transcribe-zoom-calls.mdx @@ -2,14 +2,13 @@ meta_title: "How to Transcribe Zoom Calls Automatically?" meta_description: "Learn how to automatically transcribe Zoom calls using Zoom's built-in feature or powerful third-party AI tools. Step-by-step guide with accuracy comparisons." author: "Harshika" -coverImage: "/api/assets/blog/how-to-transcribe-zoom-calls/cover.png" category: "Guides" date: "2025-09-16" --- -Transcribing Zoom calls manually is time-consuming and error-prone. You can automate this process using either Zoom's built-in transcription feature or third-party AI tools. +Transcribing Zoom calls by hand is slow and error-prone. You can automate it with Zoom's built-in transcription or a third-party AI tool. -This article covers both approaches and helps you decide which works for your workflow. +This article covers both, and helps you pick. ## Method 1: Transcribing Zoom calls using Zoom's built-in transcription @@ -48,83 +47,83 @@ If you're interested in Zoom's meeting summaries feature, see our [Zoom AI Compa ## Method 2: Using third-party tools to transcribe Zoom calls -Many excellent options exist for Zoom transcription. See our comprehensive guide on the [best AI notetakers for Zoom](/blog/best-ai-notetaker-for-zoom) for a detailed comparison. Char is a complete AI notetaking solution that goes beyond simple transcription. +There are plenty of options for Zoom transcription. See our [best AI notetakers for Zoom](/blog/best-ai-notetaker-for-zoom) guide for a side-by-side. Anarlog goes past transcription into note-taking. -### What is Char? +### What is Anarlog? -Char is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files with zero lock-in. This matters for: +Anarlog is an open-source AI notepad for meetings. You control your data and your AI stack. Notes live as plain markdown files, no lock-in. This matters for: - **Privacy-sensitive conversations** -- Healthcare professionals, lawyers, counselors, and anyone handling confidential information - **Compliance-heavy industries** -- Organizations meeting HIPAA, GDPR, SOX, or CCPA requirements - **Offline functionality** -- Works without internet connectivity - **Universal compatibility** -- Works with all meeting platforms without requiring bots -Char transcribes your meeting in real-time and generates actionable summaries automatically, without bots joining your call. +Anarlog transcribes in real time and generates summaries with action items, without a bot in the call. -### How to transcribe Zoom meetings with Char +### How to transcribe Zoom meetings with Anarlog -#### Step 1: Download and install the Char app +#### Step 1: Download and install the Anarlog app -download Char app for Zoom +download Anarlog app for Zoom -1. Visit [char.com](/) and click "Download" +1. Visit [anarlog.so](/) and click "Download" 2. Download the macOS app (Windows and Linux coming in Q2 2026) -3. Open the DMG file and drag Char to your Applications folder -4. Launch Char from Applications +3. Open the DMG file and drag Anarlog to your Applications folder +4. Launch Anarlog from Applications #### Step 2: Start recording your meeting -How does Char work for Zoom +How does Anarlog work for Zoom -1. Open Char before or during your Zoom call +1. Open Anarlog before or during your Zoom call 2. Press the red recording button to start listening -3. Char captures both system audio (speakers/headphones) and your microphone +3. Anarlog captures both system audio (speakers/headphones) and your microphone 4. Real-time transcript appears in the right panel as people speak **Important:** Always ask for consent, especially in all-party consent states like California. #### Step 3: Let AI create your notes -Char for zoom calls +Anarlog for zoom calls 1. After your meeting ends, press the stop button -2. Char automatically processes the transcript using its custom HyperLLM-V1 model +2. Anarlog automatically processes the transcript using its custom HyperLLM-V1 model 3. AI generates a personalized summary with key points, action items, and insights 4. Review and edit the enhanced notes as needed 5. Export in multiple formats (Markdown, PDF, Rich text) -### Why Char outperforms Zoom's native transcription +### Why Anarlog outperforms Zoom's native transcription #### 1. Accuracy and reliability Zoom native transcription review *Source* -Zoom users often spend hours editing 45-60 minute transcripts due to poor accuracy and formatting issues. Char's AI models deliver better transcription quality with minimal manual cleanup. +Zoom users often spend hours editing 45-60 minute transcripts because of accuracy and formatting issues. Anarlog produces cleaner transcripts with less manual cleanup. #### 2. Privacy and control -Zoom processes conversations in the cloud, raising compliance concerns for sensitive industries. Char keeps everything on your device with no data leaving your computer. +Zoom processes conversations in the cloud, raising compliance concerns for sensitive industries. Anarlog keeps everything on your device with no data leaving your computer. #### 3. Structured notes vs. raw text -Zoom delivers raw transcript files that require manual organization. Char extracts key points, decisions, and action items automatically. +Zoom delivers raw transcript files that require manual organization. Anarlog extracts key points, decisions, and action items automatically. #### 4. Long-term value -Zoom transcripts sit in isolation. Char builds a searchable knowledge base where you can find patterns, track decisions, and understand how projects evolved. +Zoom transcripts sit in isolation. Anarlog builds a searchable knowledge base where you can find patterns, track decisions, and understand how projects evolved. #### 5. Interactive intelligence -Char lets you ask questions about your meeting notes through AI chat—"What are my action items?" or "What exactly did Sarah say about the budget?"—without scrolling through pages of text. Zoom gives you static files. +Anarlog lets you ask questions about your meeting notes through AI chat—"What are my action items?" or "What exactly did Sarah say about the budget?"—without scrolling through pages of text. Zoom gives you static files. #### 6. Flexibility -Char works with any meeting platform, offers custom templates, and gives you complete deployment flexibility—local-only processing, on-premises deployment, or private cloud options. Zoom locks you into their ecosystem. +Anarlog works with any meeting platform, supports custom templates, and lets you deploy how you want — local only, on-prem, or private cloud. Zoom locks you into Zoom. -Char offers unlimited AI summaries and transcription free forever. +Anarlog offers unlimited AI summaries and transcription free forever. -[Download Char for macOS](/download) +[Download Anarlog for macOS](https://anarlog.so) ## Best practices for automatic Zoom transcription and note-taking @@ -156,32 +155,32 @@ Regardless of which method you choose, follow these practices to get the best re Zoom's native transcription feature only works with cloud recordings. You cannot generate transcripts without recording the meeting first. -Third-party tools like Char can capture and transcribe Zoom audio in real-time without requiring cloud recording to Zoom's servers. +Third-party tools like Anarlog can capture and transcribe Zoom audio in real-time without requiring cloud recording to Zoom's servers. ### 2. Where are Zoom transcripts stored? -Zoom transcripts are stored in Zoom's cloud servers alongside your recorded meetings. You can access them through the Zoom web portal under "Recordings." +Zoom transcripts are stored in Zoom's cloud servers alongside your recorded meetings. You can access them through the Zoom web portal under "Recordings". The transcripts are saved in VTT format, which you can download and convert to other formats like Word documents or PDFs. This cloud storage raises privacy concerns for sensitive meetings. ### 3. What's the best alternative to Zoom's transcription? -For users who want complete control, Char offers the best alternative with zero lock-in—plain markdown files, your choice of AI stack, and true file ownership. +If you want full control, Anarlog gives you plain markdown files, your choice of AI stack, and no lock-in. -For those comfortable with cloud processing, other popular alternatives include Otter AI, Fathom, and Tactiq. See our comprehensive guide on the [best AI notetakers for Zoom](/blog/best-ai-notetaker-for-zoom) for a detailed comparison. +If you are fine with cloud processing, popular alternatives include Otter AI, Fathom, and Tactiq. See our [best AI notetakers for Zoom](/blog/best-ai-notetaker-for-zoom) for a side-by-side. ### 4. How accurate is Zoom's native transcription? -Zoom's transcription has significant accuracy issues. [Users report](https://community.zoom.com/t5/Zoom-Meetings/Transcription-Highly-Inaccurate/m-p/130271) incorrect speaker identification, common words requiring editing even with clear speech, and frequent formatting errors. +Zoom's transcription has accuracy issues. [Users report](https://community.zoom.com/t5/Zoom-Meetings/Transcription-Highly-Inaccurate/m-p/130271) wrong speaker labels, common words requiring edits even with clear speech, and frequent formatting errors. ### 5. Can I transcribe Zoom calls offline? Zoom's native transcription requires an internet connection and cloud processing, so offline transcription isn't possible. -Char can transcribe Zoom calls completely offline using local AI models, making it ideal for situations with unreliable internet. +Anarlog can transcribe Zoom calls completely offline using local AI models, making it ideal for situations with unreliable internet. ### 6. Do I need a paid Zoom account for transcription? Yes, Zoom's transcription feature requires a Pro, Business, Education, or Enterprise account. The basic free Zoom plan does not include transcription. -Char offers unlimited transcription and AI summaries free forever, regardless of your Zoom account type. +Anarlog offers unlimited transcription and AI summaries free forever, regardless of your Zoom account type. diff --git a/apps/web/content/articles/how-we-build-with-ai.mdx b/apps/web/content/articles/how-we-build-with-ai.mdx index 360f060bb8..3591c57193 100644 --- a/apps/web/content/articles/how-we-build-with-ai.mdx +++ b/apps/web/content/articles/how-we-build-with-ai.mdx @@ -1,19 +1,19 @@ --- -meta_title: "How We Build With AI at Char" -display_title: "How We Build With AI at Char — And Why AI-First Teams Win" -meta_description: "A personal look inside how Char works with AI every day. Why engineers who embrace AI become unstoppable, why business generalists are losing leverage, and why small AI-native teams now outperform large ones." +meta_title: "How We Build With AI at Anarlog" +display_title: "How We Build With AI at Anarlog — And Why AI-First Teams Win" +meta_description: "A personal look inside how Anarlog works with AI every day. Why engineers who embrace AI become unstoppable, why business generalists are losing leverage, and why small AI-native teams now outperform large ones." author: "John Jeong" category: "Engineering" date: "2025-12-01" --- -For the past year, I've heard endless takes about how "engineers are dead" because of AI. But from where I'm sitting, building Char day and night, the real bottleneck has shifted. It's the business generalists who are struggling. +For the past year, I've heard a lot of takes about how "engineers are dead" because of AI. From where I'm sitting, building Anarlog day and night, the bottleneck has moved. It is the business generalists who are struggling. Our team has been running full-speed with AI in our development workflow. [Devin](https://devin.ai), [Cursor](https://cursor.com), [Zed](https://zed.dev), [Claude Code](https://www.claude.com/product/claude-code), [Warp](https://warp.dev), [GitButler](http://gitbutler.com) — we use all of them, sometimes at the same time. Our commits per hour exploded. Hiring more engineers stopped making sense. The velocity just isn't the bottleneck anymore. -AI tools used in Char +AI tools used in Anarlog -Once you embrace AI deeply, the limiting factors become taste — design taste, product taste, code review taste. Someone who can't contribute meaningfully to those areas slows things down. Negative velocity is real. +Once you go all in on AI, the limit is taste — design, product, code review. People who can't contribute there slow things down. Negative velocity is real. Engineers who lean into AI become unstoppable. They write more. Ship more. Break fewer things. Learn absurdly fast. Their growth has no ceiling. Only how quickly they can refine judgment matters. @@ -25,9 +25,9 @@ Builders win. AI-augmented builders win harder. ## Why I'm Writing This Series -I want to document how we actually work at Char — not the polished PR version, but the real workflows that help a tiny team ship like a much larger one. +I want to document how we actually work at Anarlog — not the polished PR version, but the real workflows that help a tiny team ship like a much larger one. -We don't add AI to our stack. We build with AI as the default. Char sits at the center of that. Every conversation becomes captured locally. Every decision becomes context we can revisit. Every idea gets distilled and carried forward. AI creates output fast, but Char gives it memory and direction. +We don't add AI to our stack. AI is the default. Anarlog sits at the center of that. Every conversation gets captured locally. Every decision becomes context we can revisit later. AI produces output fast — Anarlog gives it memory and direction. This series shares that openly — the good parts, the messy parts, the stuff I wish someone told me a year ago. @@ -37,12 +37,12 @@ If you've ever wondered how small teams can now outperform big ones, this series ## What's Coming Next -In the next post, I'll break down our full engineering stack — Devin → Cursor → Zed → Char → GitButler — and how each piece fits into a single loop, with actual examples from how we ship features every day. +In the next post, I'll break down our full engineering stack — Devin → Cursor → Zed → Anarlog → GitButler — and how each piece fits into a single loop, with actual examples from how we ship features every day. After that, we'll cover: - how we do AI-assisted code reviews -- how we use Char as our shared company memory +- how we use Anarlog as our shared company memory - how our design cycles got 5× faster - why small, sharp teams are the new superpower @@ -50,6 +50,6 @@ This will be a living, honest series. No hype, just how we actually operate. ## A Small Personal Note -I'm writing this because I want more teams — especially small ones — to feel this kind of momentum. AI didn't remove the need to think. It removed the need to drown. Now the real differentiator is clarity, taste, and courage. +I'm writing this because I want more teams — especially small ones — to feel this kind of momentum. AI didn't remove the need to think. It removed the need to drown. The real differentiator now is taste and conviction. -If that resonates with you, stick around. And if you want a tool that keeps your team's thinking sharp and your conversations durable, give Char a try. It's how we work every single day. \ No newline at end of file +If that resonates with you, stick around. And if you want a tool that keeps your team's thinking sharp and your conversations durable, give Anarlog a try. It's how we work every single day. \ No newline at end of file diff --git a/apps/web/content/articles/hyprnote-is-now-char.mdx b/apps/web/content/articles/hyprnote-is-now-char.mdx index 81a57d9c2f..fc8c82df4c 100644 --- a/apps/web/content/articles/hyprnote-is-now-char.mdx +++ b/apps/web/content/articles/hyprnote-is-now-char.mdx @@ -3,7 +3,6 @@ meta_title: "Hyprnote Is Now Char" display_title: "Hyprnote Is Now Char" meta_description: "From sales notes to AI notepad — why we renamed Hyprnote to Char, the story behind the name, and what it means for our users." author: "John Jeong" -coverImage: "/api/assets/blog/announcement.jpg" featured: true category: "Founders' notes" date: "2026-02-14" @@ -29,7 +28,7 @@ In December, we received a cease & desist from another company with a similarly The cease & desist pushed us to act. But we were already there. -Over the past year, something became clear: we hadn't built just another privacy tool. We'd built something people couldn't get anywhere else—complete control. +Over the past year, something became clear: we hadn't built another privacy tool. We'd built something people couldn't get anywhere else — complete control. Control over which AI models process their data. Control over their notes as actual files on their machine, not entries in someone else's database. Control over whether their information ever touches the cloud. @@ -61,4 +60,3 @@ We're doubling down on direct control. All wrapped in something simple and clean If that sounds like what you've been looking for, come check out what we're building. - diff --git a/apps/web/content/articles/is-ai-notetaking-legal.mdx b/apps/web/content/articles/is-ai-notetaking-legal.mdx index d20cfd595f..ee66f8898c 100644 --- a/apps/web/content/articles/is-ai-notetaking-legal.mdx +++ b/apps/web/content/articles/is-ai-notetaking-legal.mdx @@ -3,15 +3,14 @@ meta_title: "Is AI note-taking legal? Everything You Need to Know" meta_description: "AI note-taking is legal, but selecting the wrong tool or missing compliance requirements can expose you to privacy and compliance risks. Learn more." author: - "Harshika" -coverImage: "/api/assets/blog/is-ai-notetaking-legal/cover.png" featured: false category: "Guides" date: "2025-08-26" --- -AI notetaking is legal, but the devil's in the details. While you can absolutely use these tools, certain missteps can trigger privacy violations, consent law breaches, and compliance nightmares that cost millions. +AI notetaking is legal. The catch is that a few wrong moves can trigger privacy violations, consent law breaches, and compliance fines that run into the millions. -Most compliance issues stem from overlooking consent requirements and choosing tools without understanding their data practices. Get these fundamentals right, and you can stay productive while staying protected. +Most of those issues come from skipping consent and picking tools without reading what they do with your data. Handle those two right and you're mostly fine. **TL;DR:** @@ -19,7 +18,7 @@ Most compliance issues stem from overlooking consent requirements and choosing t - **Data practices matter:** Many tools record, train AI, and store conversations—creating legal exposure. - **Privilege risks:** Using cloud AI can waive attorney-client, medical, or corporate confidentiality. - **Industry compliance:** Different laws (HIPAA, GDPR, CCPA, FERPA, SOX) impose strict safeguards. -- **Safer choice:** Cloud-based tools demand heavy due diligence, while zero-lock-in solutions (like Char) minimize compliance risks with complete control over your data and AI stack. +- **Safer choice:** Cloud-based tools demand heavy due diligence, while zero-lock-in solutions (like Anarlog) minimize compliance risks with complete control over your data and AI stack. 👉 **Bottom line:** Get consent, vet your vendor, protect confidentiality, and if compliance is key, use an open-source AI notepad with zero lock-in. @@ -27,31 +26,31 @@ Most compliance issues stem from overlooking consent requirements and choosing t ### 1. Consent requirements: you must comply with the strictest applicable state law -Recording consent laws form the foundation of AI note-taking legality, and they're more complex than most people realize. +Recording consent law is messier than most people assume. At the federal level, the Electronic Communications Privacy Act allows "[one-party consent](https://detectiveservices.com/2012/02/state-by-state-recording-laws/)", meaning you can legally record a conversation if you're a participant or if one participant consents with full knowledge. A majority of states require only one-party consent—any party to the conversation can record it without getting the consent of anyone else. However, 11-13 states require "[all-party consent](https://detectiveservices.com/2012/02/state-by-state-recording-laws/)", meaning everyone involved must agree to be recorded. These states include California, Connecticut, Delaware, Florida, Illinois, Maryland, Massachusetts, Michigan, Montana, Nevada, New Hampshire, Pennsylvania, and Washington. -When participants are in different states, the strictest standard generally applies. This means obtaining consent from all parties is necessary to ensure compliance. +When participants are in different states, the strictest standard usually applies. Get consent from everyone. ### 2. Data processing: understanding where your data goes and how it's used -Many cloud-based AI note-taking tools like Otter AI don't just transcribe your meetings—they learn from them. This creates data processing issues most organizations never consider. +Cloud tools like Otter AI don't just transcribe your meetings, they learn from them. Most organizations don't think about that until it's a problem. -A [recent lawsuit against Otter](/blog/is-otter-ai-safe) alleges the service automatically joins meetings through calendar integrations "without obtaining the affirmative consent from any meeting participant." The recordings are then used to train Otter's AI systems without participants knowing their private conversations became training data. +A [recent lawsuit against Otter](/blog/is-otter-ai-safe) alleges the service auto-joins meetings through calendar integrations "without obtaining the affirmative consent from any meeting participant". Those recordings then train Otter's AI without participants knowing. -Cross-border data transfers add another layer of complexity. If your AI note-taking service processes data outside your home country, you may need to comply with additional regulations about international data transfers, especially under frameworks like GDPR. +Cross-border transfers make this worse. If your tool processes audio outside your home country, GDPR and similar frameworks pull you into a separate compliance regime. ### 3. Third-party access: what your vendor really does with your data -Most people using AI note-taking tools have never read their vendor's data processing agreements, and this oversight can be costly. +Most people using AI note-takers have never read their vendor's data processing agreement. That oversight gets expensive. -Businesses should purchase licensed AI applications and ensure meeting recordings and AI outputs are retained in their own AI cloud instance, used to train AI only for their own purposes, and encrypted during transmission and at rest. +The baseline a business should demand: recordings and AI outputs stay in your own cloud instance, training is opt-in and scoped to you, encryption applies in transit and at rest. -Many popular AI note-taking tools don't offer this level of control in their standard plans. They may store your data indefinitely on their servers, use your conversations to improve their AI models, share data with third-party partners for analysis, or lack proper encryption for data in transit and at rest. +Most popular tools don't offer that on their standard plans. They store data indefinitely, train on your conversations, share with third-party partners, or skip encryption somewhere in the pipeline. -This makes Business Associate Agreements (BAAs) crucial for organizations in regulated industries. Businesses should structure their relationship with AI providers through appropriate data processing agreements, which ensure compliance with cybersecurity and privacy laws and outline the parties' respective roles and liabilities. +For regulated industries, Business Associate Agreements (BAAs) and data processing agreements are how you allocate liability and prove compliance. The vendor security assessment should cover: @@ -65,19 +64,19 @@ The vendor security assessment should cover: ### 4. Privilege protection: when AI recording breaks confidentiality -Attorney-client privilege can be waived if confidential information is shared with a third party. Since AI transcription services may store transcripts, privilege could be inadvertently waived if a third party outside the circle of privilege has access to that data storage. +Attorney-client privilege breaks when confidential information is shared with a third party. If your AI transcription vendor stores transcripts and someone outside the privilege circle can reach that storage, privilege is gone. -The implications extend beyond legal conversations. Healthcare providers risk violating doctor-patient confidentiality, financial advisors may breach client confidentiality requirements, and any organization could inadvertently disclose trade secrets or sensitive business information. +This goes beyond legal work. Healthcare providers risk doctor-patient confidentiality, financial advisors breach client confidentiality, and any company can leak trade secrets through a transcript. -AI-produced content could be seen as a neutral document and easily [discoverable by opposing parties during legal proceedings](https://www.huffpost.com/entry/ai-notetaker-meetings-privacy_l_683dda81e4b0cceca4075fc6), whereas human-generated notes may fall under attorney-client privilege or other confidentiality safeguards. +AI-generated transcripts are also [discoverable in legal proceedings](https://www.huffpost.com/entry/ai-notetaker-meetings-privacy_l_683dda81e4b0cceca4075fc6) in ways that handwritten notes often aren't. ### 5. Compliance standards: navigating industry-specific regulations -Different industries face different regulatory requirements, and AI note-taking can trigger compliance obligations you might not expect. +Different industries trigger different obligations. AI note-taking pulls you into more of them than you might expect. #### HIPAA (Healthcare) -Healthcare organizations face some of the strictest requirements. HIPAA's Security Rule mandates specific technical, administrative, and physical safeguards for electronic protected health information (ePHI), including end-to-end encryption, access controls, audit logging, and Business Associate Agreements with AI vendors. +Healthcare gets the strictest treatment. HIPAA's Security Rule mandates technical, administrative, and physical safeguards for electronic protected health information (ePHI): end-to-end encryption, access controls, audit logging, and BAAs with every AI vendor in the chain. Any AI note-taking tool used in healthcare settings must: @@ -87,13 +86,13 @@ Any AI note-taking tool used in healthcare settings must: - Allow for patient data deletion upon request - Meet HITRUST validation requirements -HIPAA violations carry severe penalties, ranging from thousands to millions of dollars, plus potential criminal charges. +HIPAA penalties run from thousands to millions of dollars, plus potential criminal charges. #### GDPR (Global/EU) -The General Data Protection Regulation applies to any organization serving EU residents and requires explicit consent for processing personal data, data minimization principles, and the ability for individuals to access, correct, and delete their personal information. +GDPR applies to any organization serving EU residents. It requires explicit consent for processing personal data, data minimization, and the right for individuals to access, correct, and delete their information. -For AI note-taking, GDPR creates several specific challenges: +For AI note-taking, that translates to a few specific problems: - Recording a person's voice itself constitutes personal data processing - Explicit consent is required from all EU participants @@ -103,25 +102,25 @@ For AI note-taking, GDPR creates several specific challenges: #### CCPA/CPRA (California) -California's privacy laws require businesses to provide notice at collection of personal information, including the categories collected and purposes of use. The California Privacy Rights Act expands protections for "sensitive personal information" including biometric data. +California requires businesses to give notice at collection: what they're collecting and why. CPRA expands that to "sensitive personal information", which includes biometric data. -Voice recordings can be considered biometric identifiers, so AI note-taking may trigger CCPA's sensitive data provisions, requiring additional consent and opt-out mechanisms. +Voice recordings can count as biometric identifiers, so AI note-taking often trips CCPA's sensitive-data provisions. That means extra consent and opt-out mechanisms. #### SOX (financial services) -Financial services firms using AI note-taking for client communications must ensure proper data governance and retention policies. Client conversations may need to be preserved for regulatory examination while remaining protected from unauthorized access. +Financial firms using AI note-taking for client calls need data governance and retention that holds up to a regulator. Client conversations may need to be preserved for examination while still being locked down from unauthorized access. #### FERPA (education) -Educational institutions must be particularly careful about AI note-taking in settings where student information might be discussed. Student records and information are protected under FERPA, and unauthorized disclosure can result in loss of federal funding. +Schools have to be careful any time student information might come up in a recorded discussion. FERPA protects student records, and unauthorized disclosure can cost an institution its federal funding. #### SOC 2 (system and organization controls) -SOC 2 is a security framework that evaluates how organizations handle customer data. For AI note-taking tools, SOC 2 Type II compliance demonstrates that the vendor has implemented proper security controls around data processing, access management, and system monitoring. +SOC 2 evaluates how a vendor handles customer data. SOC 2 Type II is the version that matters: it shows the controls actually held up over time, not just on paper. ## AI note-taking legal compliance checklist -Use this checklist to ensure your AI note-taking practices meet legal requirements and protect your organization from compliance risks. +Run through this before recording anything sensitive. - ✅ Get consent from all meeting participants before recording (assume all-party consent required) - ✅ Verify where your data is stored and whether it's used for AI training @@ -136,23 +135,21 @@ Use this checklist to ensure your AI note-taking practices meet legal requiremen ## Choosing the right AI note-taking solution -Your choice of AI note-taking tool largely determines whether compliance is a complex ongoing challenge or a manageable one-time setup. +Your tool choice decides whether compliance is an ongoing project or a one-time setup. -With most tools, you don't get a say in how your data is handled. Your audio goes to their servers, gets processed by their AI, and lives in their database. Every regulation you need to comply with becomes your vendor's problem to solve and yours to verify. +With most tools, you don't get a say. Audio goes to their servers, gets processed by their AI, and sits in their database. Every regulation becomes their problem to solve and yours to verify. -Char works differently. You decide how your data is processed — [local models on your device](/blog/local-ai-meeting-notes/), your own cloud API keys, or Char's managed service. Everything saves as plain markdown files on your device, and IT teams can audit the open-source code. When you control the stack, you control the compliance surface. +Anarlog works differently. You decide how your data is processed: [local models on your device](/blog/local-ai-meeting-notes/) or your own cloud API keys. Everything saves as plain markdown on your device, and IT can audit the open-source code. When you control the stack, you control the compliance surface. -For organizations serious about both productivity and legal protection, that level of control makes both feasible. +## About Anarlog's AI notetaker -## About Char's AI notetaker +![Anarlog ai notetaker](/api/assets/blog/is-ai-notetaking-legal/legal-1.webp) -![Char ai notetaker](/api/assets/blog/is-ai-notetaking-legal/legal-1.webp) +Anarlog has an Apple Notes-like interface, with AI running in the background. -Char has an Apple Notes-like interface that feels instantly familiar, with AI running in the background. +Start a meeting and it transcribes in real time. No bots, no calendar permissions. When the meeting ends, you get a structured summary with decisions, action items, and themes. -When you launch a meeting, it starts transcribing speech in real-time — no bots joining your call, no calendar permissions required. When the meeting ends, it presents a structured summary with key decisions, action items, and discussion themes. - -The AI works hands-off if you want it to. You can also jot down key points yourself, and Char will expand your notes with context from the transcript. +You can let the AI do the whole thing, or jot your own points and have Anarlog fill in context from the transcript. **Key benefits:** @@ -163,4 +160,4 @@ The AI works hands-off if you want it to. You can also jot down key points yours - Enterprise features including on-premises deployment and SSO integration - Open-source code your IT and security teams can audit -[Download Char for MacOS](/download) to take control of your meeting data. \ No newline at end of file +[Download Anarlog for MacOS](https://anarlog.so) to take control of your meeting data. \ No newline at end of file diff --git a/apps/web/content/articles/is-fireflies-ai-safe.mdx b/apps/web/content/articles/is-fireflies-ai-safe.mdx index 80ee117ce5..b484a17dec 100644 --- a/apps/web/content/articles/is-fireflies-ai-safe.mdx +++ b/apps/web/content/articles/is-fireflies-ai-safe.mdx @@ -3,7 +3,6 @@ meta_title: "Is Fireflies AI Safe? The No BS Truth" meta_description: "Thinking about using Fireflies AI? We dug into their data collection practices and found some red flags you should know about." author: - "John Jeong" -coverImage: "/api/assets/blog/is-fireflies-ai-safe/cover.png" featured: false category: "Comparisons" date: "2025-09-23" @@ -19,9 +18,9 @@ We decided to find out. ![Fireflies](/api/assets/blog/is-fireflies-ai-safe/fireflies-1.webp) -Fireflies AI is a cloud-based service that automatically joins your video calls as a bot participant. It records everything, transcribes the conversation, and uses AI to create summaries and insights. The bot integrates with popular platforms like Zoom, Google Meet, and Microsoft Teams. +Fireflies AI is a cloud service that joins your video calls as a bot. It records, transcribes, and runs the conversation through AI for summaries. It plugs into Zoom, Google Meet, and Microsoft Teams. -Everything gets processed on Fireflies' servers, not your device. Your conversations become their data. [Otter AI raises similar concerns](/blog/is-otter-ai-safe/) with its own cloud-based recording practices. +Everything is processed on Fireflies' servers, not your device. Your conversations become their data. [Otter AI raises similar concerns](/blog/is-otter-ai-safe/). ## What does Fireflies actually do with your data? @@ -51,25 +50,25 @@ I went through their privacy policy and terms of service. Here's what I found. **Access to your other accounts:** -When you connect Google Calendar or other services, Fireflies can access and store "any content that you have provided to and stored in your Third-Party Account" - including your contact lists. +When you connect Google Calendar or any other service, Fireflies can access and store "any content that you have provided to and stored in your Third-Party Account", including your contact lists. ### 2. The fine print that should worry you -**They're not responsible if their AI messes up:** Their terms explicitly say that while they use AI to process your conversations, "Fireflies is not liable for any loss or harm resulting from the user's use of AI." If something goes wrong with how they handle your data, you're on your own. +**They're not responsible if their AI messes up:** Their terms say "Fireflies is not liable for any loss or harm resulting from the user's use of AI". If something goes wrong, you're on your own. -**Your data becomes theirs forever:** Fireflies keeps "all rights to aggregated and anonymous data derived from your use of the Service" with no expiration date. Even if they can't identify you personally, they own insights from your conversations permanently. +**Your data becomes theirs forever:** Fireflies keeps "all rights to aggregated and anonymous data derived from your use of the Service" with no expiration. Even if they can't identify you personally, they own insights from your conversations permanently. -**They share your data widely:** Your information gets shared with their business partners, service providers, any company that might buy Fireflies in the future, or government authorities if they think it's necessary. +**They share your data widely:** Your information goes to their business partners, service providers, any company that might buy Fireflies, or government authorities if they think it's necessary. -**You're legally responsible for everything:** Their terms say "You are responsible for compliance with all recording laws." If you accidentally record someone without consent, that's your legal problem, not theirs. This becomes especially problematic when users can't actually control when Fireflies records—you're still legally liable even if their bot shows up uninvited. +**You're legally responsible for everything:** Their terms say "You are responsible for compliance with all recording laws". Accidentally record someone without consent and it's your problem, not theirs, even if their bot showed up uninvited. ### 3. Their liability? Basically nothing -Even if Fireflies completely screws up and causes major damage, their maximum liability is capped at either what you paid them in the last six months or $100—whichever is less. Your confidential business meeting gets leaked and costs you a major client, and Fireflies owes you maybe $100. +Even if Fireflies causes serious damage, their maximum liability is whatever you paid them in the last six months or $100, whichever is less. Your confidential meeting leaks, you lose a major client, Fireflies owes you maybe $100. ## Real privacy problems users are experiencing with Fireflies AI -The privacy policy issues aren't just theoretical. Real users are reporting some concerning problems: +The privacy policy issues aren't theoretical. Users keep reporting the same problems: ### 1. It won't go away @@ -77,7 +76,7 @@ The privacy policy issues aren't just theoretical. Real users are reporting some Source: [Reddit](https://www.reddit.com/r/sales/comments/1gw2xpk/how_to_uninvite_fireflies_from_my_meetings/) -Multiple people report that Fireflies keeps showing up in their meetings even after they deactivated their accounts, tried to uninstall it, asked their IT department for help, or explicitly requested it to stop recording. One user described it as "trying to remove a deer tick from your leg—messy and painful." +People report Fireflies keeps showing up in meetings after they deactivated accounts, uninstalled the app, asked IT for help, or explicitly told it to stop recording. One user described it as "trying to remove a deer tick from your leg—messy and painful". ### 2. It shares transcripts with people who never signed up @@ -85,7 +84,7 @@ Multiple people report that Fireflies keeps showing up in their meetings even af Source: [Reddit](https://www.reddit.com/r/SaaS/comments/1dznmxo/fireflies_is_a_nightmare/) -Fireflies automatically sends meeting summaries to everyone on the calendar invite, including people who never consented to being recorded, external clients and partners, former employees who still have access to recurring meetings, and anyone who happened to be invited but didn't even attend. +Fireflies sends meeting summaries to everyone on the calendar invite. That includes people who never consented to being recorded, external clients and partners, former employees still on recurring meetings, and people who didn't attend at all. ### 3. It installs itself without permission @@ -93,7 +92,7 @@ Fireflies automatically sends meeting summaries to everyone on the calendar invi Source: [Reddit](https://www.reddit.com/r/SaaS/comments/1dznmxo/fireflies_is_a_nightmare/) -Several users report that just by joining a meeting where someone else used Fireflies, the software somehow installed itself on their computers and started recording their future meetings without their knowledge. +Users report that just by joining a meeting where someone else used Fireflies, the software installed itself on their computers and started recording their future meetings without their knowledge. ## Should I trust Fireflies with sensitive information? @@ -125,17 +124,17 @@ Even then, everyone in the meeting needs to consent to being recorded, and you'r ## Is there a safer Fireflies alternative? -If you need AI-powered meeting transcription but don't want to hand over your conversations to a cloud service, there's a better way. [Local AI meeting notetakers](/blog/local-ai-meeting-notes/) process everything on your device. +If you need AI meeting transcription but don't want to hand your conversations to a cloud service, [local AI notetakers](/blog/local-ai-meeting-notes/) process everything on your device. -### Meet Char: Zero lock-in AI note-taker +### Meet Anarlog: Zero lock-in AI note-taker -![Char](/api/assets/blog/is-fireflies-ai-safe/fireflies-5.webp) +![Anarlog](/api/assets/blog/is-fireflies-ai-safe/fireflies-5.webp) -Char is an an open-source AI notepad for meetings that gives you complete control. While Fireflies locks you into their cloud, Char stores everything as plain markdown files and lets you choose your AI stack. Zero lock-in, zero compromises. It's the only truly open-source AI notepad for meetings. +Anarlog is an open-source AI notepad for meetings. Fireflies locks you into their cloud. Anarlog stores everything as plain markdown files and lets you pick your AI stack. Zero lock-in. -Check out our [Fireflies AI Alternatives](/blog/fireflies-ai-alternatives) article for a detailed Char vs Fireflies review. +Check out our [Fireflies AI Alternatives](/blog/fireflies-ai-alternatives) article for a detailed Anarlog vs Fireflies review. -### How Char keeps you safe: +### How Anarlog keeps you safe: **Your choice of AI stack:** @@ -157,25 +156,25 @@ Meets HIPAA, GDPR, SOX, and other privacy requirements. Perfect for healthcare, On-premises deployment options, SSO integration and user management, custom branding available, and consent management built-in. -### What Char collects (for full transparency): +### What Anarlog collects (for full transparency): -Unlike Fireflies, Char collects minimal, anonymous usage data that you can opt out of: +Unlike Fireflies, Anarlog collects minimal, anonymous usage data that you can opt out of: - **Optional analytics:** Anonymous session IDs and feature usage patterns (via PostHog)—disable in Settings - **Optional crash reports:** Error traces to fix bugs (via Sentry)—disable in Settings - **Website interactions:** If you use live chat or submit feedback - **Payment info:** Only for paid users, processed through Stripe -Char never collects your recordings, transcripts, notes, meeting participant information, conversations, or personal files. +Anarlog never collects your recordings, transcripts, notes, meeting participant information, conversations, or personal files. -[Check Char's take on Privacy](/privacy) +[Check Anarlog's take on Privacy](https://char.com/legal/privacy) ## Is Fireflies AI Safe? -Fireflies AI might seem convenient, but convenience comes at a steep privacy cost. Their extensive data collection, broad sharing permissions, minimal liability, and documented issues with user control make it risky for most business conversations. +Fireflies AI is convenient, and the convenience is expensive. Heavy data collection, wide sharing permissions, near-zero liability, and a long track record of bots that won't leave make it a bad fit for most business conversations. -If you need AI-powered meeting assistance, choose a solution with zero lock-in that gives you complete control. Char proves you don't have to sacrifice ownership for functionality. +If you need AI meeting assistance, pick a tool with zero lock-in. Anarlog shows you don't have to trade ownership for the feature set. -Your conversations are valuable. Don't give them away for free. +Your conversations are worth more than free transcription.   diff --git a/apps/web/content/articles/is-otter-ai-safe.mdx b/apps/web/content/articles/is-otter-ai-safe.mdx index 4ef72c7e09..befb0ae62b 100644 --- a/apps/web/content/articles/is-otter-ai-safe.mdx +++ b/apps/web/content/articles/is-otter-ai-safe.mdx @@ -2,16 +2,15 @@ meta_title: "Is Otter AI Safe: What You Need to Know" meta_description: "Otter AI faces a class-action lawsuit over privacy violations and secret recordings. Is it safe to use? Discover user complaints, data risks, and alternatives." author: "John Jeong" -coverImage: "/api/assets/blog/is-otter-ai-safe/cover.png" category: "Comparisons" date: "2025-08-18" --- -[A federal class-action lawsuit](https://www.npr.org/2025/08/15/g-s1-83087/otter-ai-transcription-class-action-lawsuit) filed in August 2025 has thrust popular AI transcription service Otter AI into the spotlight, with allegations that the company "deceptively and surreptitiously" records private conversations without proper consent. +[A federal class-action lawsuit](https://www.npr.org/2025/08/15/g-s1-83087/otter-ai-transcription-class-action-lawsuit) filed in August 2025 alleges Otter AI "deceptively and surreptitiously" records private conversations without proper consent. -The lawsuit, filed in the U.S. District Court for the Northern District of California, represents a growing pattern of privacy concerns surrounding the Mountain View-based company that has processed more than 1 billion meetings since 2016 for its 25 million users. +The case was filed in the U.S. District Court for the Northern District of California. The Mountain View company has processed more than 1 billion meetings since 2016 for 25 million users. -The case raises fundamental questions about consent, privacy, and data use in the age of AI-powered workplace tools—questions that extend far beyond a single company to the entire automated transcription industry. +The questions it raises about consent, privacy, and data reuse don't stop at Otter. They apply to the entire automated transcription industry. --- @@ -27,25 +26,25 @@ The case raises fundamental questions about consent, privacy, and data use in th Otter AI Lawsuit Source: [NPR](https://www.npr.org/2025/08/15/g-s1-83087/otter-ai-transcription-class-action-lawsuit) -The lawsuit centers on plaintiff Justin Brewer of San Jacinto, California, who alleges his privacy was "severely invaded" upon realizing Otter was secretly recording a confidential conversation. +The lawsuit centers on plaintiff Justin Brewer of San Jacinto, California, who alleges his privacy was "severely invaded" when he realized Otter was secretly recording a confidential conversation. -The complaint alleges that Otter Notebook, which provides real-time transcriptions of Zoom, Google Meet, and Microsoft Teams meetings, "by default does not ask meeting attendees for permission to record and fails to alert participants that recordings are shared with Otter to improve its artificial intelligence systems." +The complaint says Otter Notebook, which transcribes Zoom, Google Meet, and Microsoft Teams meetings in real time, "by default does not ask meeting attendees for permission to record and fails to alert participants that recordings are shared with Otter to improve its artificial intelligence systems". -The system can automatically join meetings. As detailed in the lawsuit: +The system can auto-join meetings. From the filing: > "If the meeting host is an Otter accountholder who has integrated their relevant Google Meet, Zoom, or Microsoft Teams accounts with Otter, an Otter Notetaker may join the meeting without obtaining the affirmative consent from any meeting participant, including the host." -The legal filing claims this practice violates both state and federal privacy and wiretap laws, with lawyers arguing that Otter uses these recordings to "derive financial gain" through AI model training. +The plaintiffs argue this violates state and federal privacy and wiretap laws, and that Otter uses these recordings to "derive financial gain" through AI model training. -The lawsuit also challenges Otter's "de-identification" process. It argues that "Otter's deidentification process does not remove confidential information or guarantee speaker anonymity" and that the company "provides no public explanation of its 'de-identifying' process." +The lawsuit also challenges Otter's "de-identification" process, arguing it "does not remove confidential information or guarantee speaker anonymity" and that the company "provides no public explanation of its 'de-identifying' process". ## Other User Incidents Concerning Otter's Consent and Recording Issues -Real users have reported their own experiences with Otter's privacy issues, providing concrete examples of the problems outlined in the lawsuit: +The lawsuit isn't operating in a vacuum. Users keep posting variations of the same story. ### 1. The VC Meeting Data Leak -Alex Bilzerian described a particularly damaging incident in a viral tweet: +Alex Bilzerian described a damaging incident in a viral tweet. Otter AI Lawsuit 2 @@ -59,7 +58,7 @@ Attorney Anessa Allen Santos [warned of organizational security risks in a Linke ### 3. Otter Spam Warning -A Reddit user in r/projectmanagement [described how Otter automatically places links in meetings](https://www.reddit.com/r/projectmanagement/comments/1j0cfei/do_not_join_otterai_unless_you_want_your_whole) to share notes with others, causing confusion for those unfamiliar with the service: +A Reddit user in r/projectmanagement [described how Otter drops links in meetings](https://www.reddit.com/r/projectmanagement/comments/1j0cfei/do_not_join_otterai_unless_you_want_your_whole) to share notes with everyone, confusing people who've never used it: Otter AI Lawsuit 4 @@ -73,7 +72,7 @@ One Reddit user shared [how they lost a job opportunity](https://www.reddit.com/ ### 5. Gartner Community Response -IT professionals on [Gartner's peer community](https://www.gartner.com/peer-community/post/how-managing-risk-ai-transcription-bots-like-otter-ai-fireflies-ai) raised concerns about the tool. [Fireflies AI faces similar scrutiny](/blog/is-fireflies-ai-safe/) from users reporting comparable privacy issues. +IT professionals on [Gartner's peer community](https://www.gartner.com/peer-community/post/how-managing-risk-ai-transcription-bots-like-otter-ai-fireflies-ai) flagged the tool. [Fireflies AI gets similar treatment](/blog/is-fireflies-ai-safe/) for the same reasons. Otter AI Lawsuit 6 @@ -87,29 +86,29 @@ A Director of Engineering added: ## What Does Otter's Privacy Policy Explain About These Privacy Issues? -Otter.ai's official privacy policy and privacy & security pages reveal practices that many users may not fully understand. +Otter.ai's privacy policy and privacy & security pages describe practices most users haven't read. -While the company emphasizes security and privacy protections, the detailed policies show extensive data collection, usage, and sharing that goes well beyond simple transcription. +The company markets security and privacy hard. The actual policies describe data collection, usage, and sharing that go well beyond transcription. ### 1. Data Collection and AI Training -[Otter's privacy policy](https://otter.ai/privacy-policy) openly states it uses customer data for AI training purposes: +[Otter's privacy policy](https://otter.ai/privacy-policy) states they train on customer data: > "We train our proprietary artificial intelligence technology on de-identified audio recordings. We also train our technology on transcriptions to provide more accurate services, which may contain Personal Information." Otter AI Lawsuit 7 -The policy also reveals manual review of recordings when users provide consent: +The policy also says human reviewers listen to recordings when users opt in: > "We obtain explicit permission (e.g. when you rate the transcript quality and check the box to give Otter ai and its third-party service provider(s) permission to access the conversation for training and product improvement purposes) for manual review of specific audio recordings to further refine our model training data." -The [privacy & security FAQ](https://otter.ai/privacy-security) claims that "audio recordings and transcripts are not manually reviewed by a human" as part of the automatic training process, yet the privacy policy clearly states that manual review occurs when users provide explicit permission. This creates ambiguity about when human access to recordings actually happens. +The [privacy & security FAQ](https://otter.ai/privacy-security) says "audio recordings and transcripts are not manually reviewed by a human" during automatic training. The privacy policy says manual review does happen with explicit permission. The two pages don't line up cleanly. Otter AI Lawsuit 8 ### 2. Extensive Third-Party Data Sharing -The breadth of third-party data sharing outlined in Otter's privacy policy is significant. The company shares personal information with multiple categories of external parties, including: +The privacy policy lists a long roster of third parties Otter shares personal information with: - "Cloud service providers who we rely on for compute and data storage, including Amazon Web Services, based in the United States" - "Data labeling service providers who provide annotation services and use the data we share to create training and evaluation data for Otter's product features" @@ -120,13 +119,13 @@ The breadth of third-party data sharing outlined in Otter's privacy policy is si ### 3. The De-identification Problem -Otter claims to use "a proprietary method to de-identify user data before training our models so that an individual user cannot be identified," but provides no public explanation of how this process works. +Otter says they use "a proprietary method to de-identify user data before training our models so that an individual user cannot be identified", but they don't publish how it works. -The privacy policy also acknowledges that transcriptions used for training "may contain Personal Information," which raises questions about anonymization effectiveness. +The same policy admits that training transcriptions "may contain Personal Information". That tension is the core of the lawsuit. ### 4. Automatic Data Collection -The privacy policy reveals extensive automatic data collection beyond recordings and transcripts: +Beyond recordings and transcripts, Otter collects: - **Usage Information:** "When you use the Services, you generate information pertaining to your use, including timestamps, such as access, record, share, edit and delete events, app use information, screenshots/screen captures taken during the meeting, interactions with our team, and transaction records" - **Device Information:** "We assign a unique user identifier ('UUID') to each mobile device that accesses the Services" @@ -136,25 +135,21 @@ The privacy policy reveals extensive automatic data collection beyond recordings ## How Safe is Otter AI -The privacy concerns surrounding Otter.ai reflect broader issues that affect anyone using AI-powered workplace tools. +Otter's situation isn't unique. Anyone running cloud AI on workplace conversations is exposed to a version of this. -Organizations in heavily regulated industries face potential violations when sensitive conversations are automatically recorded and processed by third-party services without proper consent mechanisms or data controls. +In regulated industries, automatic recording by a third-party service without proper consent is a compliance violation waiting for a regulator. One employee installing Otter exposes the whole org, since it shares with multiple processors and rarely has enterprise-grade controls. For teams that need transcription without that risk, [local AI notetakers](/blog/local-ai-meeting-notes/) keep data on the device. -Individual employee adoption of cloud-based AI tools can create organization-wide data exposure, especially when these tools share information with multiple external processors and lack enterprise-grade security controls. For teams that need transcription without these risks, [local AI meeting notetakers](/blog/local-ai-meeting-notes/) keep data on your device entirely. +Auto-recording wrecks relationships. People notice when a bot they didn't agree to shows up on the call. They notice more when the transcript hits their inbox. -Automatic recording features can damage business relationships, compromise confidential discussions, and create legal exposure when private conversations are captured without all participants' knowledge. - -The fundamental issue extends beyond any single company. As AI tools become more automated, the gap between user expectations and actual data practices widens, creating risks that users may not realize they're accepting. +The deeper problem: as these tools get more automatic, what users expect drifts further from what the policies actually allow. ## Is There a Safer Otter AI Alternative? -The privacy concerns around Otter AI highlight the need for transcription solutions that prioritize user privacy and data control. - -For organizations and individuals seeking AI-powered transcription without vendor lock-in, open-source alternatives offer a compelling option. +If you want AI transcription without the vendor lock-in, open-source is the obvious move. -**Char** is the [best Otter AI alternative](/blog/otter-ai-alternatives) and an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. +**Anarlog** is the [best Otter AI alternative](/blog/otter-ai-alternatives). It's an open-source AI notepad for meetings. You control the data and the AI stack. Everything is stored as plain markdown files, not a proprietary database. -This approach addresses the core issues raised in the Otter AI lawsuit and user complaints: +This is the inverse of what the Otter lawsuit is fighting: - **Zero lock-in:** Plain markdown files, open source, portable format—your data works with any tool - **Complete control:** Choose your AI stack: managed cloud, bring your own keys, or run fully local @@ -162,6 +157,6 @@ This approach addresses the core issues raised in the Otter AI lawsuit and user - **Simplified compliance:** Reduces the compliance surface area by minimizing dependency on cloud vendors, making audits easier - **Complete transparency:** As an open-source solution, IT teams can audit the code and you can use the AI provider your security team approves -Char is free forever for local transcription, BYOK, and all core features. For enterprises navigating compliance frameworks like HIPAA, SOC 2, or GDPR, Char's zero-lock-in architecture makes it easier to fulfill data subject rights, maintain audit trails, and enforce access controls. +Anarlog is free forever for local transcription, BYOK, and all core features. For enterprises on HIPAA, SOC 2, or GDPR, the zero-lock-in architecture makes it easier to fulfill data subject rights, keep audit trails, and enforce access controls. -[Download Char for MacOS](/download) and experience meeting notes with zero lock-in. +[Download Anarlog for MacOS](https://anarlog.so) and experience meeting notes with zero lock-in. diff --git a/apps/web/content/articles/krisp-alternatives.mdx b/apps/web/content/articles/krisp-alternatives.mdx index aa74085e4a..9834c3d989 100644 --- a/apps/web/content/articles/krisp-alternatives.mdx +++ b/apps/web/content/articles/krisp-alternatives.mdx @@ -12,46 +12,44 @@ Krisp started as the noise cancellation app, the one that filtered out backgroun But Krisp in 2026 is a different product. It bundles noise cancellation, real-time transcription, AI summaries, accent conversion, CRM integrations, and call center tools into a single subscription. The Core plan runs $8/month on annual billing. Advanced is $15/month. Enterprise is custom-quoted. The old free tier with 60 minutes of daily noise cancellation is gone, replaced by a 7-day trial that requires a calendar connection during signup. -That's a lot of product for people who only need one piece of it. Some just want noise cancellation without the meeting suite. Others want meeting notes but don't want to hand over calendar permissions. Some need their data to stay on their own machine. I looked at seven tools that cover different slices of what Krisp does. Not all of them replace every feature, but depending on what brought you to Krisp in the first place, one of them probably does the job better. +That's a lot of product for people who only need one piece of it. Some just want noise cancellation without the meeting suite. Others want meeting notes but don't want to hand over calendar permissions. Some need their data to stay on their own machine. I looked at seven tools that cover different slices of Krisp. None replace every feature, but depending on what brought you to Krisp in the first place, one of them probably does that job better. ## Top Krisp Alternatives Compared - -| | | | +| | | | | ---------------- | ---------------------------------------------------------------- | ---------------------------------------- | -| **Tool** | **Best For** | **Pricing** | -| **Char** | Engineers who want open-source, local files, and zero lock-in | Free (unlimited local); Pro $25/mo | -| **Otter.ai** | Live collaborative transcription you can edit during the meeting | Free (limited); Pro $8.49/mo | -| **Fathom** | The best free option: unlimited recording and summaries at $0 | Free (unlimited); Premium $19/mo | -| **Fireflies.ai** | Sales teams that need every call auto-logged in Salesforce | Free (limited); Pro $18/mo | -| **Granola** | Client-facing calls where a bot would kill the conversation | Free (25 meetings); Business $14/user/mo | -| **tl;dv** | Distributed teams sharing video clips across time zones | Free (unlimited recordings); Pro $18/mo | -| **Utterly** | Just noise cancellation on Mac. Nothing else, dirt cheap | Free (basic); Elevate $5/mo | - +| **Tool** | **Best For** | **Pricing** | +| **Anarlog** | Engineers who want open-source, local files, and zero lock-in | Free forever (OSS, fully local + BYOK). | +| **Otter.ai** | Live collaborative transcription you can edit during the meeting | Free (limited); Pro $8.49/mo | +| **Fathom** | The best free option: unlimited recording and summaries at $0 | Free (unlimited); Premium $19/mo | +| **Fireflies.ai** | Sales teams that need every call auto-logged in Salesforce | Free (limited); Pro $18/mo | +| **Granola** | Client-facing calls where a bot would kill the conversation | Free (25 meetings); Business $14/user/mo | +| **tl;dv** | Distributed teams sharing video clips across time zones | Free (unlimited recordings); Pro $18/mo | +| **Utterly** | Just noise cancellation on Mac. Nothing else, dirt cheap | Free (basic); Elevate $5/mo | ## Detailed Reviews of the Best Krisp Alternatives -### 1. Char +### 1. Anarlog -![Char (formerly Hyprnote)](/api/assets/blog/blog/char-summary.png "char-editor-width=80") +![Anarlog (formerly Anarlog)](/api/assets/blog/blog/anarlog-summary.png "anarlog-editor-width=80") -[Char](char.com) is open-source and built on a different set of assumptions than everything else on this list. It captures system audio without joining your call and without requiring calendar permissions, then stores everything on your device. Not in a proprietary database. Not on someone else's server. +[Anarlog](https://anarlog.so) is open-source and built on a different set of assumptions than everything else on this list. It captures system audio without joining your call and without requiring calendar permissions, then stores everything on your device. Not in a proprietary database. Not on someone else's server. -You can grep them, version-control them, or pipe them into whatever workflow you've already built. The AI stack is yours to choose: use Char's managed cloud service, bring your own API keys (OpenAI, Anthropic, Deepgram), or run everything through local models via Ollama. If your company has banned cloud-based meeting tools for security reasons, Char is one of the few options that can run fully offline. +You can grep them, version-control them, or pipe them into whatever workflow you've already built. The AI stack is yours to choose: use a managed cloud option, bring your own API keys (OpenAI, Anthropic, Deepgram), or run everything through local models via Ollama. If your company has banned cloud-based meeting tools for security reasons, Anarlog is one of the few options that can run fully offline. -The honest limitations: it's macOS and Linux only (no Windows yet), there's no video recording, no mobile app, no built-in CRM integrations, and no noise cancellation or accent conversion. Char focuses on transcription, notes, and AI summaries, and does them with complete data ownership. Free for unlimited local transcription or BYOK setups. Pro is $25/month for managed cloud. It supports 45+ languages. +The honest limitations: it's macOS and Linux only (no Windows yet), there's no video recording, no mobile app, no built-in CRM integrations, and no noise cancellation or accent conversion. Anarlog focuses on transcription, notes, and AI summaries, and does them with complete data ownership. Free for unlimited local transcription or BYOK setups. It supports 45+ languages. -**Right for:** Char isn't trying to compete on features with Fireflies or Fathom. It's built for prosumers who care about ownership: their files, their AI stack, and their workflow. +**Right for:** Anarlog isn't trying to compete on features with Fireflies or Fathom. It's built for prosumers who care about ownership: their files, their AI stack, and their workflow. ### 2. Otter.ai -![Otter ai vs Krisp](https://help.otter.ai/hc/article_attachments/8775272002967 "char-editor-width=80") +![Otter ai vs Krisp](https://help.otter.ai/hc/article_attachments/8775272002967 "anarlog-editor-width=80") [Otter](https://otter.ai/) is one of the longest-running names in AI transcription, and its defining feature is something most competitors still don't offer: a live, collaborative transcript you can edit and annotate while the meeting is happening. It's like Google Docs for your conversation. You see words appearing in real time, highlight key moments, and add comments, all before the call ends. -Where it falls short is accuracy in noisy or multi-speaker environments. Accents trip it up more often than I'd like, and the AI summaries lean generic. "The team discussed project timelines" isn't useful when you were in the room. These are the exact problems Krisp's noise cancellation and accent conversion were built to solve, and Otter doesn't touch either of those. It's purely a transcription and notes tool. +Where it falls short is accuracy in noisy or multi-speaker rooms. Accents trip it up more often than I'd like, and the AI summaries lean generic. "The team discussed project timelines" isn't useful when you were in the room. Those are exactly the problems Krisp's noise cancellation and accent conversion were built for, and Otter doesn't do either. It's just transcription and notes. -There's also a privacy issue worth knowing about. In August 2025, [Otter was hit with a federal class action](https://char.com/blog/is-otter-ai-safe/) lawsuit alleging it records conversations without proper consent from all participants, then uses those recordings to train its AI. NPR and The Register both covered it. The University of Massachusetts banned the tool entirely. Otter says users agree via a checkbox in the privacy policy, but the people on the other end of those meetings never checked any box. +Also worth knowing: in August 2025, [Otter was hit with a federal class action](https://anarlog.so/blog/is-otter-ai-safe/) alleging it records conversations without consent from all participants, then trains its AI on those recordings. NPR and The Register covered it. UMass banned the tool entirely. Otter says users agree via a checkbox in the privacy policy. The people on the other end of those calls never checked any box. The free plan caps at around 300 minutes per month. Pro starts at $8.49/user/month. OtterPilot, its meeting bot, is visible to everyone on the call, which changes the dynamic for sensitive conversations. @@ -59,7 +57,7 @@ The free plan caps at around 300 minutes per month. Pro starts at $8.49/user/mon ### 3. Fathom -![Fathom vs Krisp](/api/assets/blog/blog/image-4.png "char-editor-width=80") +![Fathom vs Krisp](/api/assets/blog/blog/image-4.png "anarlog-editor-width=80") [Fathom's](https://www.fathom.ai/) Zoom integration is the smoothest of any tool on this list. It runs natively inside Zoom, so no bot appears in the participant list. Your client doesn't see "Fathom Notetaker" pop up. Your prospect doesn't ask who just joined. Summaries land within 30 seconds of the meeting ending, structured into key decisions, action items, and follow-ups. Google Meet and Teams still get the bot treatment, so the experience varies by platform. @@ -71,13 +69,13 @@ Fathom recently launched "Ask Fathom" for account-wide search across every meeti ### 4. Fireflies.ai -![Fireflies.ai vs Krisp](https://fireflies.ai/website/assets/_next/image?url=%2Fapi%2Fmedia%2Ffile%2Fhero-desktop.svg&w=3840&q=75 "char-editor-width=80") +![Fireflies.ai vs Krisp](https://fireflies.ai/website/assets/_next/image?url=%2Fapi%2Fmedia%2Ffile%2Fhero-desktop.svg&w=3840&q=75 "anarlog-editor-width=80") [Fireflies](https://fireflies.ai/) is built for teams that live in their CRM. It records your meetings, generates searchable transcripts, and pushes summaries, action items, and call metadata directly into Salesforce, HubSpot, or Zoho. For a sales team doing 20+ external calls a week, that automation alone justifies the cost. The topic detection is surprisingly good, auto-tagging segments as "pricing discussion" or "feature request" without any setup. It also does conversation analytics: talk-time ratios, question frequency, sentiment tracking. For sales managers reviewing team performance, that's the kind of data Krisp doesn't offer even on its Enterprise plan. Where Fireflies can't compete with Krisp is the audio layer: there's no noise cancellation, no accent conversion, and transcription accuracy drops noticeably with non-native English speakers. -Fireflies also has a legal issue. It's facing a BIPA class action in Illinois federal court for allegedly collecting voiceprints from meeting participants without consent. The complaint points out that Fireflies records and identifies speakers even when those people never created an account or agreed to any terms. If you're in a regulated industry, this is worth flagging before you roll it out. +Fireflies has its own legal problem: a BIPA class action in Illinois federal court for allegedly collecting voiceprints from meeting participants without consent. The complaint notes Fireflies records and identifies speakers even when those people never created an account or agreed to terms. In a regulated industry, flag this before rolling it out. The bot is visible to all participants. The free tier gives you 800 minutes of storage total (not monthly), which vanishes in a couple of weeks. Pro runs $18/user/month. @@ -85,19 +83,19 @@ The bot is visible to all participants. The free tier gives you 800 minutes of s ### 5. Granola -![Granola vs Krisp](/api/assets/blog/blog/image-6.png "char-editor-width=80") +![Granola vs Krisp](/api/assets/blog/blog/image-6.png "anarlog-editor-width=80") [Granola](https://www.granola.ai/) takes a fundamentally different approach: no bot joins your call. It captures audio directly from your device's speakers and microphone, transcribes locally, and then enhances whatever rough notes you took during the meeting into structured summaries. Your shorthand bullet points go in; organized decisions, action items, and key quotes come out. It's notepad-first, not transcript-first. For a lot of people, that distinction matters. -The company just raised $125 million in March 2026 at a $1.5 billion valuation. But that same month, Granola encrypted its local database, breaking every agent workflow that read notes directly from the local cache. Guido Appenzeller, a partner at a16z, posted about it on X and it went viral: 'In a world where notes are managed by agents, the app now has zero value.' Granola's co-founder responded that MCP access was available, but developers wanted a proper API, not a proprietary protocol. A public API has since been announced. But the incident is a reminder that 'local-first' doesn't mean 'yours' if the vendor controls the format. +The company just raised $125 million in March 2026 at a $1.5 billion valuation. The same month, Granola encrypted its local database, breaking every agent workflow that read notes from the local cache. Guido Appenzeller, a partner at a16z, posted about it on X and it went viral: "In a world where notes are managed by agents, the app now has zero value". Granola's co-founder responded that MCP access was available; developers wanted a proper API, not a proprietary protocol. A public API has since been announced. The incident is a reminder that "local-first" doesn't mean "yours" if the vendor controls the format. -Moreover, Granola doesn't do speaker identification well in multi-person calls, doesn't save audio for playback, and offers no noise cancellation or accent conversion, so audio quality depends entirely on your conferencing app. The free plan covers only 25 meetings lifetime (not monthly). Business runs $14/user/month, which is cheaper than most competitors. Available on Mac, Windows, and iOS. +Granola also doesn't handle speaker identification well in multi-person calls, doesn't save audio for playback, and has no noise cancellation or accent conversion, so audio quality depends entirely on your conferencing app. The free plan covers 25 meetings lifetime (not monthly). Business runs $14/user/month, cheaper than most competitors. Available on Mac, Windows, and iOS. **Right for:** anyone in sensitive, client-facing meetings where discretion matters more than feature count. ### 6. tl;dv -![tl;dv vs Krisp](https://b2729162.smushcdn.com/2729162/wp-content/uploads/2022/11/Transcript-and-Notes.png?lossy=1&strip=1&webp=1 "char-editor-width=80") +![tl;dv vs Krisp](https://b2729162.smushcdn.com/2729162/wp-content/uploads/2022/11/Transcript-and-Notes.png?lossy=1&strip=1&webp=1 "anarlog-editor-width=80") [tl;dv's](https://tldv.io/) strength is making meetings shareable. You can highlight any moment in a transcript and instantly generate a video clip. That's useful for async teams where not everyone was on the call, or for sales managers who want to review specific moments without watching an hour-long recording. The "Reels" feature lets you stitch clips into a highlight reel, which is genuinely clever for stakeholder updates. @@ -109,7 +107,7 @@ Like the other notetakers here, tl;dv doesn't touch the audio quality side of wh ### 7. Utterly -![Utterly vs Krisp](https://mac-cdn.softpedia.com/screenshots/Utterly_3.jpg "char-editor-width=80") +![Utterly vs Krisp](https://mac-cdn.softpedia.com/screenshots/Utterly_3.jpg "anarlog-editor-width=80") If all you ever used Krisp for was the noise cancellation, and the meeting notes, accent conversion, and CRM features were just clutter, [Utterly](https://www.utterly.app/) does that one job for a fraction of the price. It's a Mac app that filters background noise in real time, processes everything on your device, and works with any meeting app as a virtual microphone. No cloud processing, no account required on the free tier. @@ -121,14 +119,14 @@ The catch is it's Mac-only, development has been slow, and it doesn't perform as ## So Which One Actually Replaces Krisp? -Honestly, none of them replace all of it, and that's kind of the point. Krisp bundles noise cancellation, transcription, accent conversion, CRM sync, and enterprise compliance into one subscription. Most people use maybe two of those features and pay for all of them. +Honestly, none of them replace all of it. That's the point. Krisp bundles noise cancellation, transcription, accent conversion, CRM sync, and enterprise compliance into one subscription. Most people use two of those features and pay for all of them. If noise cancellation is what brought you to Krisp and the rest is clutter, Utterly does that one job on Mac for $5/month or free. -If meeting notes are the thing, Fathom is the strongest starting point for individuals. The Zoom integration is invisible, and the free tier is more generous than anything else in the category, though the 5-summary cap means heavy users will upgrade fast. Fireflies wins for sales teams who need CRM automation, but the BIPA lawsuit and bot visibility are real considerations. Otter is the pick if live collaborative transcription matters to you, though the privacy lawsuit and data training practices deserve scrutiny. +If meeting notes are the thing, Fathom is the strongest starting point for individuals. The Zoom integration is invisible and the free tier is more generous than anything else in the category, though the 5-summary cap means heavy users will upgrade fast. Fireflies wins for sales teams who need CRM automation, but the BIPA lawsuit and bot visibility are real. Otter is the pick if live collaborative transcription matters to you, though the privacy lawsuit and training practices deserve scrutiny. -Granola is the answer for client-facing calls where a bot would kill the conversation. It's well-funded and expanding fast, but the database encryption incident is a reminder that local-first doesn't mean yours if the vendor controls the format. tl;dv is the best option for distributed teams that need to share meeting clips across time zones, especially in multiple languages. +Granola is the answer for client-facing calls where a bot would kill the conversation. It's well-funded and expanding fast, but the database encryption incident is a reminder that local-first doesn't mean yours if the vendor controls the format. tl;dv is the best option for distributed teams that need to share meeting clips across time zones. -And if data ownership is what actually matters to you, like choosing your own AI provider, keeping files on your own machine, and never worrying about a vendor encrypting your database or training models on your conversations, Char is the only tool here that gives you that. It's open-source. Your files are yours. Nobody else gets a vote. +If data ownership is the actual issue, picking your own AI provider, keeping files on your machine, never worrying about a vendor encrypting your database or training on your conversations, Anarlog is the only tool here that does that. It's open-source. Your files are yours. Nobody else gets a vote. -Char is free for unlimited local transcription. [Download Char for macOS](https://char.com/download/apple-silicon) and try it on your next meeting. \ No newline at end of file +Anarlog is free for unlimited local transcription. [Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and try it on your next meeting. \ No newline at end of file diff --git a/apps/web/content/articles/local-ai-meeting-notes.mdx b/apps/web/content/articles/local-ai-meeting-notes.mdx index 87e849d524..ba6fa7179b 100644 --- a/apps/web/content/articles/local-ai-meeting-notes.mdx +++ b/apps/web/content/articles/local-ai-meeting-notes.mdx @@ -8,37 +8,35 @@ category: "Comparisons" date: "2026-04-10" --- -Every meeting notetaker calls itself "local" or "private" now. The word has become a marketing checkbox. But local means wildly different things depending on which tool you pick. +Every meeting notetaker calls itself "local" or "private" now. The word is a marketing checkbox. Local means very different things depending on which tool you pick. -Some tools transcribe on your device but send the transcript to a cloud LLM for summarization. Some store notes locally but make them accessible via a shareable link by default. Some process everything on-device but only work on one operating system, or only on recent hardware. And some are fully local until you look at the fine print and discover your data trains their models unless you opt out. +Some transcribe on-device but send the transcript to a cloud LLM. Some store notes locally but make them accessible via a shareable link by default. Some process everything on-device but only on one OS, or only on recent hardware. Some are fully local until you read the fine print and find out your data trains their models unless you opt out. -I tested five tools that take different positions on this spectrum. For each one, I dug into what "local" actually means in practice: what stays on your machine, what leaves, what the tradeoffs are, and what it costs. +I tested five tools across that spectrum. For each one, I worked out what "local" actually means: what stays on your machine, what leaves, what the tradeoffs are, what it costs. ## How These Local AI Meeting Notetakers Compare - -| | | | +| | | | | -------------- | ------------------------------------------------------------------ | ------------------------------ | -| **Tool** | **Best For** | **Pricing** | -| **Char** | Users who want complete control over data and AI stack | Free (local/BYOK), Pro $25/mo | -| **Meetily** | Teams on Windows or Mac who want a polished open-source option | Community free, Pro $10/mo | -| **Shadow** | Client-facing roles who need screen capture alongside audio | Free transcription, Plus $8/mo | -| **Talat** | People who want Granola's UX without the cloud, for a one-time fee | $49 pre-release, $99 at 1.0 | -| **Screenpipe** | Developers who want meeting notes as part of full desktop memory | $400 lifetime | -| | | | - +| **Tool** | **Best For** | **Pricing** | +| **Anarlog** | Users who want complete control over data and AI stack | Free forever (OSS, local + BYOK). | +| **Meetily** | Teams on Windows or Mac who want a polished open-source option | Community free, Pro $10/mo | +| **Shadow** | Client-facing roles who need screen capture alongside audio | Free transcription, Plus $8/mo | +| **Talat** | People who want Granola's UX without the cloud, for a one-time fee | $49 pre-release, $99 at 1.0 | +| **Screenpipe** | Developers who want meeting notes as part of full desktop memory | $400 lifetime | +| | | | ## 5 Tools for Local AI Meeting Notes: A Closer Look -### 1. Char +### 1. Anarlog -![](/api/assets/blog/blog/char-summary.png "char-editor-width=80") +![](/api/assets/blog/blog/anarlog-summary.png "anarlog-editor-width=80") -[Char](https://char.com) is an open-source AI notepad for meetings, built for people who want complete control over their data and AI stack. It captures system audio from any meeting platform without joining your call and without requiring calendar permissions. +[Anarlog](https://anarlog.so) is an open-source AI notepad for meetings, built for people who want complete control over their data and AI stack. It captures system audio from any meeting platform without joining your call and without requiring calendar permissions. -You can take notes during the meeting in Char's built-in editor while it transcribes in the background, and the AI combines both into a structured summary after the call. +You can take notes during the meeting in Anarlog's built-in editor while it transcribes in the background, and the AI combines both into a structured summary after the call. -The AI stack is yours to choose: Char's managed cloud service, your own API keys from OpenAI, Anthropic, or Deepgram, or fully local models through Ollama or LM Studio for offline operation. Every audio file, transcript, and note lives on your computer. You decide if data ever leaves your device. +The AI stack is yours to choose: a managed cloud option, your own API keys from OpenAI, Anthropic, or Deepgram, or fully local models through Ollama or LM Studio for offline operation. Every audio file, transcript, and note lives on your computer. You decide if data ever leaves your device. #### How local is it actually @@ -47,26 +45,26 @@ The AI stack is yours to choose: Char's managed cloud service, your own API keys - Storage: all data stored locally on your device. Your files, your folder structure, zero lock-in. - Open-source codebase. Your security team can audit exactly how data is handled. -If you run Char with local models, nothing leaves your machine. If you plug in an API key, only the transcript goes to that provider for summarization. For companies that have banned cloud-based meeting tools, Char is one of the few options where "local-first" means exactly what it says. +Run Anarlog with local models and nothing leaves your machine. Plug in an API key and only the transcript goes to that provider for summarization. For companies that banned cloud meeting tools, Anarlog is one of the few options where "local-first" means what it says. #### Where it falls short - macOS and Linux only. No Windows client yet. - No mobile app, no video recording. -- No built-in CRM integrations. If you need notes pushed into Salesforce, you will need to build that yourself. +- No built-in CRM integrations. If you need notes pushed into Salesforce, you'll need to build it. - No team dashboard or cross-meeting cloud search. Your notes live in your file system. #### Pricing -Free for unlimited loacl transcription and AI. A lite plan at $8/month with Char cloud and pro at $25/month for integrations, advanced templates, and more. +Free for unlimited local transcription and AI. ### 2. Meetily -![meetily review](/api/assets/blog/blog/image-20.png "char-editor-width=80") +![meetily review](/api/assets/blog/blog/image-20.png "anarlog-editor-width=80") [Meetily](https://meetily.ai) is an open-source meeting assistant (MIT license) that runs transcription entirely on your device using Whisper or Parakeet models. -It captures system audio [without a bot](/blog/bot-free-ai-meeting-assistants/), works with Zoom, Teams, Meet, and anything else that produces audio on your machine. Parakeet is the default engine and runs significantly faster than Whisper with lower resource usage, with GPU acceleration built in for Apple Silicon, NVIDIA, and AMD hardware. +It captures system audio [without a bot](/blog/bot-free-ai-meeting-assistants/) and works with Zoom, Teams, Meet, and anything else that produces audio. Parakeet is the default engine and runs faster than Whisper with lower resource usage, with GPU acceleration on Apple Silicon, NVIDIA, and AMD. Summaries are pluggable: use Ollama for fully local processing, or connect your own API key to Claude, Groq, or OpenRouter. The app is built in Rust with a Next.js frontend and ships as a single installer for macOS and Windows. Linux users can build from source. @@ -77,7 +75,7 @@ Summaries are pluggable: use Ollama for fully local processing, or connect your - Storage: local SQLite database. Audio files stored on your device. - GPU acceleration: Apple Silicon (Metal), NVIDIA (CUDA), AMD/Intel (Vulkan). -The team is transparent that local model summarization does not match cloud quality yet, especially on longer meetings. They document the tradeoffs in their blog and are actively working on closing the gap. +The team is upfront that local summarization doesn't match cloud quality yet, especially on long meetings. They document the gap on their blog and are working on it. #### Where it falls short @@ -92,11 +90,11 @@ Community Edition is free forever. Pro is at $10/month, billed annually, include ### 3. Shadow -![shadow.do review](/api/assets/blog/blog/Screenshot-2026-04-10-at-7.43.38-PM.png "char-editor-width=80") +![shadow.do review](/api/assets/blog/blog/Screenshot-2026-04-10-at-7.43.38-PM.png "anarlog-editor-width=80") -[Shadow](https://shadow.do) captures both channels of a meeting: what is said and what is shown on screen. It runs on macOS, records system audio without a bot, and automatically takes timestamped screenshots of whatever is displayed during your call: slides, code, dashboards, design mockups. Those screenshots are embedded directly in your notes alongside the transcript. +[Shadow](https://shadow.do) captures both channels of a meeting: what's said and what's shown on screen. It runs on macOS, records system audio without a bot, and takes timestamped screenshots of whatever is displayed during your call: slides, code, dashboards, design mockups. Those screenshots get embedded directly in your notes alongside the transcript. -This matters the moment someone says "look at the numbers on slide four" and you need to know what those numbers were. Every other tool on this list captures audio only. Shadow also has autopilot mode that detects meetings and starts recording without you pressing anything, real-time speaker identification, and custom post-meeting AI "Skills" that run automatically. +That matters the moment someone says "look at the numbers on slide four" and you need to know what those numbers were. Every other tool on this list captures audio only. Shadow also has an autopilot mode that detects meetings and starts recording without you pressing anything, real-time speaker identification, and custom post-meeting AI "Skills" that run automatically. #### How local is it actually @@ -105,7 +103,7 @@ This matters the moment someone says "look at the numbers on slide four" and you - Summarization: cloud LLM APIs by default. Can be disabled, but you lose summaries, action items, and the "Ask Shadow" chatbot. - Export: .md files to a local folder. Works with Obsidian out of the box, webhooks for Zapier/Make/n8n. -Shadow is local for capture but not fully local for intelligence. The AI features route through external APIs. You can turn them off and keep just the transcript and screenshots, but then you lose the analysis layer. +Shadow is local for capture but not for intelligence. The AI features route through external APIs. Turn them off and you keep transcript and screenshots, but lose the analysis layer. #### Where it falls short @@ -120,11 +118,11 @@ Free for unlimited transcription and 25 lifetime meetings for AI features. Plus, ### 4. Talat -![talat notetaker review](/api/assets/blog/blog/image-25.png "char-editor-width=80") +![talat notetaker review](/api/assets/blog/blog/image-25.png "anarlog-editor-width=80") -[Talat](https://talat.app) runs transcription and summarization entirely on your Mac's Neural Engine. It captures both sides of your call (system audio and microphone), transcribes in real time with speaker identification, and generates summaries using a local LLM (Qwen3-4B-4bit by default). The whole app is 20 MB. It detects when a conferencing app grabs your microphone and starts recording in the background. +[Talat](https://talat.app) runs transcription and summarization on your Mac's Neural Engine. It captures both sides of your call (system audio and mic), transcribes in real time with speaker identification, and summarizes with a local LLM (Qwen3-4B-4bit by default). The whole app is 20 MB. It detects when a conferencing app grabs your mic and starts recording in the background. -The focus is configurability. You choose your LLM provider: the built-in local model, OpenAI, Anthropic, OpenRouter, or Ollama. You can write custom summarization prompts, auto-export notes to Obsidian, fire webhooks when a meeting ends, and run an MCP server so AI coding tools can query your meeting history. No analytics, no telemetry, no data collection. +The focus is configurability. Pick your LLM provider: the built-in local model, OpenAI, Anthropic, OpenRouter, or Ollama. Write custom summarization prompts, auto-export to Obsidian, fire webhooks on meeting end, and run an MCP server so AI coding tools can query your meeting history. No analytics, no telemetry, no data collection. #### How local is it actually @@ -148,15 +146,15 @@ $49 one-time purchase (pre-release price). $99 at version 1.0. 10 hours of free ### 5. Screenpipe -![screenpipe review](/api/assets/blog/blog/image-26.png "char-editor-width=80") +![screenpipe review](/api/assets/blog/blog/image-26.png "anarlog-editor-width=80") [Screenpipe](https://screenpi.pe) records your screen and audio 24/7, transcribes everything locally with Whisper, and indexes it in a searchable local database. -It is not a meeting tool. It is an open-source AI memory layer for your entire desktop that happens to be very good at meeting notes, because meeting notes are a side effect of it always running. +It's not a meeting tool. It's an open-source AI memory layer for your entire desktop that happens to be very good at meeting notes, because meeting notes are a byproduct of it always running. -After a call, you ask the AI: "Summarize my meeting from 2 to 3pm and list action items." Screenpipe searches your audio transcripts and screen content from that time window. +After a call, you ask the AI: "Summarize my meeting from 2 to 3pm and list action items". Screenpipe searches your audio transcripts and screen content from that window. -It captures what audio-only tools miss: the URL someone dropped in the Zoom chat, the spreadsheet someone shared, the code someone walked through in a terminal. It also runs as an MCP server, so Cursor, Claude Code, and Cline can query your meeting history directly. +It captures what audio-only tools miss: the URL someone dropped in Zoom chat, the spreadsheet someone shared, the code someone walked through in a terminal. It also runs as an MCP server, so Cursor, Claude Code, and Cline can query your meeting history directly. #### How local is it actually @@ -179,33 +177,33 @@ $400 lifetime (one-time). All features, all future updates. $600 lifetime + 1 ye **Which Local AI Meeting Notetaker Is Right for You?** -Local processing means accepting tradeoffs. Local models are not as accurate as cloud transcription on messy audio, speaker diarization is still maturing across the board, and most of these tools are macOS-only or macOS-first. The gap is closing fast, though, and for anyone handling sensitive conversations, the architectural guarantee that your audio never left your device is worth more than a privacy policy that can change with a terms-of-service update. +Local processing has tradeoffs. Local models aren't as accurate as cloud transcription on messy audio, speaker diarization is still maturing, and most of these tools are macOS-only or macOS-first. The gap is closing fast. For anyone handling sensitive conversations, an architectural guarantee that audio never left your device beats a privacy policy that can change with a terms-of-service update. -Char is the strongest choice if you want complete control: open-source, your choice of AI provider, and data that lives on your device in a format you own. +Anarlog is the strongest choice if you want complete control: open-source, your choice of AI provider, and data that lives on your device in a format you own. Meetily is the best open-source option if you need Windows support or want a free polished app. Shadow is the only tool that captures what is on screen alongside what is said. -Talat is for people who loved Granola but could not accept cloud dependency, and it is the only one-time purchase purpose-built meeting tool here. +Talat is for people who loved Granola but couldn't accept the cloud dependency, and it's the only one-time-purchase meeting tool here. -Screenpipe is the power user's option: not just meeting notes but a searchable memory of your entire workday. +Screenpipe is the power user's option: not just meeting notes but a searchable memory of your entire workday. -If you want to start with local AI meeting notes that give you full ownership, [try Char](https://char.com). It is free, open-source, and works offline with local models. Your first meeting's notes will land on your device, readable by any tool you already use. +If you want local AI meeting notes that give you full ownership, [try Anarlog](https://anarlog.so). It's free, open-source, and works offline with local models. Your first meeting's notes will land on your device, readable by any tool you already use. ## Frequently Asked Questions ### 1. Is Granola a local AI notetaker? -No. Granola captures system audio locally, which is why it gets grouped with local tools, but the similarity ends there. Your audio is sent to Deepgram for transcription, your transcript is processed through GPT-4o or Claude for summarization, and your notes are stored on AWS servers. There is no offline mode and no option to run local models. Granola is a cloud tool with a local audio capture step. +No. Granola captures system audio locally, which is why it gets grouped with local tools, but the similarity ends there. Your audio is sent to Deepgram for transcription, your transcript runs through GPT-4o or Claude for summarization, and your notes sit on AWS. There's no offline mode and no option for local models. Granola is a cloud tool with a local audio capture step. ### 2. Can I use these tools for in-person meetings? -Yes. Tools that capture microphone input (Char, Meetily, Talat, Screenpipe) work for in-person conversations. Place your laptop near the speakers and let the mic pick up the room. Speaker diarization accuracy varies since it was primarily designed for virtual calls with separate audio channels. +Yes. Tools that capture microphone input (Anarlog, Meetily, Talat, Screenpipe) work for in-person conversations. Place your laptop near the speakers and let the mic pick up the room. Speaker diarization accuracy varies since it was primarily designed for virtual calls with separate audio channels. ### 3. Do local meeting notetakers work offline? -If both transcription and summarization use local models, yes. Char with Ollama, Meetily with Parakeet + Ollama, Talat with its built-in LLM, and Screenpipe with local Whisper all work without an internet connection. Shadow's transcription works offline but the AI summary features need a connection. +If transcription and summarization both run on local models, yes. Anarlog with Ollama, Meetily with Parakeet + Ollama, Talat with its built-in LLM, and Screenpipe with local Whisper all work without an internet connection. Shadow's transcription works offline; its AI summary features need a connection. ### 4. What hardware do I need to run local transcription? @@ -213,8 +211,8 @@ Most tools work on any Mac from the last five years. Apple Silicon (M1 or later) ### 5. Is it legal to record meetings without telling participants? -Recording laws vary by jurisdiction. In many US states and most of Europe, you need consent from all parties to record a conversation. The fact that these tools do not send a bot into the meeting makes them less visible, but that does not remove your legal obligation to inform participants. Always tell the people on your call that you are recording. +Recording laws vary by jurisdiction. In many US states and most of Europe, you need consent from all parties. These tools don't send a bot into the meeting, which makes them less visible, but that doesn't remove your obligation to inform participants. Tell the people on your call you're recording. ### 6. Are local AI meeting notes as accurate as cloud-based tools? -It depends on the model and your hardware. Cloud transcription services like Deepgram still have an edge on messy audio, heavy accents, and cross-talk. But local models like Parakeet and Whisper Large V3 have closed the gap significantly, especially on clear audio from a decent microphone. For most one-on-one and small group calls, the difference is negligible. \ No newline at end of file +Depends on the model and your hardware. Cloud services like Deepgram still have an edge on messy audio, heavy accents, and cross-talk. Local models like Parakeet and Whisper Large V3 have closed most of the gap, especially on clear audio from a decent mic. For most one-on-one and small group calls, the difference is negligible. \ No newline at end of file diff --git a/apps/web/content/articles/local-ai-privacy-tools.mdx b/apps/web/content/articles/local-ai-privacy-tools.mdx index 8ccc66a65f..fe40d7e884 100644 --- a/apps/web/content/articles/local-ai-privacy-tools.mdx +++ b/apps/web/content/articles/local-ai-privacy-tools.mdx @@ -2,30 +2,27 @@ meta_title: "The Rise of Local AI: How the Next Wave of Privacy Tools Will Be Built" meta_description: "From DuckDuckGo to Proton, privacy-first apps have earned user trust. Now, local AI is carrying that vision forward." author: "John Jeong" -coverImage: "/api/assets/blog/local-ai-privacy-tools/cover.png" category: "Engineering" date: "2025-07-28" --- ## Intro -In the past decade, we've watched a new generation of software companies win not by collecting more data, but by collecting none. [DuckDuckGo](https://duckduckgo.com/) built a search engine without profiling users. [Brave](https://brave.com/) gave people a browser that blocked trackers by default. [Proton](https://proton.me) delivered email and cloud storage that not even they could read. +A new generation of software companies has won by collecting no data at all. [DuckDuckGo](https://duckduckgo.com/) built a search engine without profiling users. [Brave](https://brave.com/) shipped a browser that blocks trackers by default. [Proton](https://proton.me) delivered email and cloud storage that not even they could read. -Each of these tools rejected the tradeoffs that Big Tech made years ago—usability traded for privacy, performance traded for control. They proved that privacy isn't just a niche preference. It's a product category. +They rejected the trades Big Tech made years ago: usability for privacy, performance for control. Privacy isn't a niche preference. It's a product category. -Now, AI is testing that principle again. +AI is testing that principle again. ## Privacy, revisited -The AI boom has delivered impressive capabilities but also renewed surveillance. A meeting you record with an AI notetaker gets sent to the cloud. A contract you upload for AI review gets logged on someone else's server. Even just asking ChatGPT a question reveals more about you than most search queries ever did. +The AI boom shipped real capabilities and renewed surveillance at the same time. The meeting you record with an AI notetaker goes to the cloud. The contract you upload for review gets logged on someone else's server. Asking ChatGPT a question reveals more about you than most search queries ever did. -The current generation of AI tools is fundamentally cloud-first. To use them is to surrender control over what you share, where it goes, and who might see it later. +This generation of AI is cloud-first. Using it means giving up control over what you share, where it goes, and who sees it later. -Where are the privacy-first tools in AI? +Where are the privacy-first tools for AI? -Just as DuckDuckGo and Brave offered new choices for search and browsing, and Proton reimagined communications, we now need tools that bring the same principles into this new era. - - +DuckDuckGo and Brave reset search and browsing. Proton reset email. AI needs the same. ## Why local AI works @@ -35,7 +32,7 @@ Two things make this work: ### 1. Physical assurance -When AI runs on your device, no data ever needs to leave it. Cloud-based models promise "we won't look." Local AI guarantees "we can't look." This shifts privacy from policy to physics. +When AI runs on your device, no data needs to leave it. Cloud models promise "we won't look". Local AI guarantees "we can't look". That shifts privacy from policy to physics. ### 2. No training risk @@ -59,15 +56,15 @@ AI is the next interface ripe for that shift. Your data is wanted by everyone -## Where Char comes in +## Where Anarlog comes in -At [Char](/), we're building an open-source AI notepad for meetings that gives you complete control. Everything is stored as plain markdown files. You choose your AI stack—managed cloud, bring your own keys, or run local models. Zero lock-in, zero compromises. +At [Anarlog](/), we're building an open-source AI notepad for meetings that gives you complete control. Everything is stored as plain markdown files. You choose your AI: bring your own keys or run local models. Zero lock-in, zero compromises. We open-sourced our core app so anyone can audit it. We support offline mode by default, and for teams that need collaboration, we offer self-hosted options that never touch external servers. You can even bring your own models. Our users include salespeople, engineers, lawyers, doctors, and founders—people who deal with sensitive conversations every day. -Char isn't just about note-taking. It lets AI enhance your work without owning it. +Anarlog isn't just about note-taking. It lets AI enhance your work without owning it. ## The privacy stack is evolving @@ -81,7 +78,7 @@ The early stack looked like: With the rise of AI, a new layer is forming: -- **Cognition** → Char (and others like it) +- **Cognition** → Anarlog (and others like it) This stack isn't defined by features. It's defined by where your data lives and who controls it. Users are increasingly choosing tools where they can answer "me" to both questions. @@ -91,6 +88,5 @@ Cloud-first AI is colliding with growing demand for control and ownership. As mo Local AI is the inevitable future, driven by both performance and principle. Just as early privacy pioneers reshaped the web, we're helping reshape AI. -If you've ever used DuckDuckGo, Brave, or Proton and wished there was something similar for your meetings, we built Char for you. +If you've ever used DuckDuckGo, Brave, or Proton and wished there was something similar for your meetings, we built Anarlog for you. - \ No newline at end of file diff --git a/apps/web/content/articles/mac-productivity-apps.mdx b/apps/web/content/articles/mac-productivity-apps.mdx index 0f4117c7d5..04969a7e27 100644 --- a/apps/web/content/articles/mac-productivity-apps.mdx +++ b/apps/web/content/articles/mac-productivity-apps.mdx @@ -27,29 +27,27 @@ If you're a Mac user looking to actually get more done (not just feel productive ## My favorite productivity apps for Mac - -| Tool | Best For | Pricing | +| Tool | Best For | Pricing | | ---------------- | ----------------------------------------------------------------------- | ----------------------------------- | -| Char | AI note-taker with complete control over data and AI stack | Free, Pro $25/mo | -| Cowork | AI agent that handles tedious file work and research | Claude Pro/Team/Enterprise required | -| Raycast | Replacing Spotlight with a command center that actually does things | Free, Pro $10/mo | -| Obsidian | Building a personal knowledge base with files you actually own | Free, Sync $8/mo | -| Things 3 | Task management that's beautiful enough to actually open daily | $50 Mac, $10 iPhone (one-time) | -| CleanShot X | Screenshots and screen recordings for people who share a lot of visuals | $29 one-time, Pro $8/mo | -| Keyboard Maestro | Automating repetitive tasks you do every single day | $36 one-time | -| Hazel | File organization that runs on autopilot forever | $42 one-time | -| Reeder | Following blogs and content without algorithmic feeds | $10/year | - +| Anarlog | AI note-taker with complete control over data and AI stack | Free (OSS, local + BYOK). Managed cloud: [Char](https://char.com) | +| Cowork | AI agent that handles tedious file work and research | Claude Pro/Team/Enterprise required | +| Raycast | Replacing Spotlight with a command center that actually does things | Free, Pro $10/mo | +| Obsidian | Building a personal knowledge base with files you actually own | Free, Sync $8/mo | +| Things 3 | Task management that's beautiful enough to actually open daily | $50 Mac, $10 iPhone (one-time) | +| CleanShot X | Screenshots and screen recordings for people who share a lot of visuals | $29 one-time, Pro $8/mo | +| Keyboard Maestro | Automating repetitive tasks you do every single day | $36 one-time | +| Hazel | File organization that runs on autopilot forever | $42 one-time | +| Reeder | Following blogs and content without algorithmic feeds | $10/year | ## Best Mac productivity apps in 2026 -### 1. Char (formerly Hyprnote): best Mac app for meeting notes +### 1. Anarlog (formerly Anarlog): best Mac app for meeting notes -![Char review](/api/assets/blog/articles/mac-productivity-apps/image-1.png) +![Anarlog review](/api/assets/blog/articles/mac-productivity-apps/image-1.png) -Meetings suck, but they're inevitable. The next best thing is making sure you never miss a detail with a note-taker that doesn't lock your data in someone else's cloud. I built Char for this reason. +Meetings suck, but they're inevitable. The next best thing is making sure you never miss a detail with a note-taker that doesn't lock your data in someone else's cloud. I built Anarlog for this reason. -[Char](https://char.com/) is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. You decide if your audio, transcripts, or notes ever leave your device. You pick your preferred STT and LLM provider, which means you can go completely local if you want to. No forced stack. No lock-in. +[Anarlog](https://anarlog.so/) is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. You decide if your audio, transcripts, or notes ever leave your device. You pick your preferred STT and LLM provider, which means you can go completely local if you want to. No forced stack. No lock-in. #### Key Features: @@ -81,7 +79,7 @@ Meetings suck, but they're inevitable. The next best thing is making sure you ne #### Pricing: -Unlimited free plan with local transcription or bring-your-own-key. Pro is $25/month for managed cloud service. +Free forever, fully local + BYOK, open source. ### 2. Cowork: best AI agent for file management and research on Mac @@ -93,7 +91,7 @@ Unlimited free plan with local transcription or bring-your-own-key. Pro is $25/m - Local file access: Give Claude permission to specific folders and it'll read, edit, organize, and create files without you manually uploading anything - Multi-step task execution: Set it loose on a project and it'll work through multiple steps autonomously, checking in when it needs guidance -- Built-in skills: Comes with pre-loaded expertise for creating presentations, spreadsheets, and documents with proper formatting +- Built-in skills: Comes with pre-loaded skills for creating presentations, spreadsheets, and documents with proper formatting - Works with your connectors: Integrates with MCP servers you already have set up (like Google Drive, Slack) - Task queueing: Drop multiple requests and let Claude work through them in parallel—feels less like a conversation, more like delegating to a coworker @@ -101,7 +99,7 @@ Unlimited free plan with local transcription or bring-your-own-key. Pro is $25/m - Handles tedious work you'd normally grind through for hours (organizing downloads, processing receipts, restructuring notes) - No bot joining your Zoom calls or needing calendar permissions -- Works seamlessly with tools like Obsidian, Notion, VS Code since everything's just files +- Works well with tools like Obsidian, Notion, VS Code since everything's just files - Can handle complex workflows like "find all my unpublished blog drafts that are almost done" #### Cons: @@ -140,7 +138,7 @@ Available to paid subscribers, with pricing starting at $17/month for Pro users, - Can map any action to a global hotkey that works system-wide - Active development with constant updates and new features - Clean, modern interface that doesn't feel cluttered despite doing so much -- Works seamlessly across macOS versions +- Works smoothly across macOS versions #### Cons: @@ -176,7 +174,7 @@ When everything's markdown files on your computer and you can extend it however - Your data is actually yours - Works with any sync service (iCloud, Dropbox, Google Drive) or use Obsidian Sync - Free for personal use with no feature restrictions -- Pairs perfectly with markdown-based tools like Char +- Pairs perfectly with markdown-based tools like Anarlog - Active community constantly building new plugins and themes #### Cons: @@ -397,10 +395,10 @@ $10/year subscription (roughly $1/month). Reeder Classic remains available as se Here's what nobody tells you about productivity apps: at some point, you have to stop optimizing your workflow and actually do the work. -I've spent years building this setup. Some of these apps have been with me so long I forget what it was like before them. Others are new enough that I'm still discovering features. But they all have one thing in common—they make me think less about how I'm working so I can focus on what I'm working on. +I've spent years building this setup. Some of these apps have been with me so long I forget what it was like before them. Others are new enough that I'm still discovering features. But they all have one thing in common: they make me think less about how I'm working so I can focus on what I'm working on. That's the only metric that matters. Download what sounds useful. Try it for two or three weeks. If you're not using it without thinking about it by then, delete it and move on. Your dock doesn't need another icon. You need tools that disappear. -If you're drowning in meetings and losing track of what was said, try Char. +If you're drowning in meetings and losing track of what was said, try Anarlog. diff --git a/apps/web/content/articles/markdown-note-taking-apps.mdx b/apps/web/content/articles/markdown-note-taking-apps.mdx index 1a82d5e6ef..92ecc5f925 100644 --- a/apps/web/content/articles/markdown-note-taking-apps.mdx +++ b/apps/web/content/articles/markdown-note-taking-apps.mdx @@ -16,31 +16,29 @@ This list covers five apps, each strong for a specific use case, so you can find ## Top markdown note-taking apps compared - -|   |   |   |   |   | +|   |   |   |   |   | | -------- | ------------------------------ | --------------- | --------------- | ----------------- | -| **App** | **Best For** | **Open Source** | **Local Files** | **Price** | -| Char | Meeting notes | ✅ | ✅ | Free / $25/mo | -| Obsidian | Personal knowledge management | ❌ | ✅ | Free / $4/mo sync | -| Logseq | Daily journaling & outlining | ✅ | ✅ | Free | -| Joplin | Cross-device sync with privacy | ✅ | ✅ | Free / 2.99€/mo | -| Inkdrop | Developers | ❌ | ❌ | $8.31/mo | - +| **App** | **Best For** | **Open Source** | **Local Files** | **Price** | +| Anarlog | Meeting notes | ✅ | ✅ | Free (OSS, local + BYOK). Managed cloud: [Char](https://char.com) | +| Obsidian | Personal knowledge management | ❌ | ✅ | Free / $4/mo sync | +| Logseq | Daily journaling & outlining | ✅ | ✅ | Free | +| Joplin | Cross-device sync with privacy | ✅ | ✅ | Free / 2.99€/mo | +| Inkdrop | Developers | ❌ | ❌ | $8.31/mo | ## The best markdown note-taking apps, reviewed -### 1. Char: best markdown note-taking app for meetings +### 1. Anarlog: best markdown note-taking app for meetings ![](/api/assets/blog/articles/markdown-note-taking-apps/image-1.png) -**[Char](https://char.com/)** (formerly Hypernote) is an open-source AI notepad built specifically for meetings. It transcribes your conversations in real-time, and when the meeting ends, it combines the transcript with your manual notes to create the perfect summary. No bots join your calls, everything is stored as plain markdown files with zero lock-in, and you get to choose your preferred STT and LLM provider. +**[Anarlog](https://anarlog.so/)** (formerly Hypernote) is an open-source AI notepad built specifically for meetings. It transcribes your conversations in real-time, and when the meeting ends, it combines the transcript with your manual notes to create the perfect summary. No bots join your calls, everything is stored as plain markdown files with zero lock-in, and you get to choose your preferred STT and LLM provider. #### Key Features: - System audio capture - No bots joining your calls. No calendar permissions needed. Works on Zoom, Teams, phone calls, and in-person conversations - Real-time transcription - Live transcript generates as the meeting happens, so you can follow along instead of furiously typing - AI summaries - Combines your own notes with the transcript to produce structured summaries with action items -- Your choice of AI stack - Use Char's managed cloud service, bring your own API keys (OpenAI, Anthropic, Deepgram, others), or run fully local via Ollama or LM Studio +- Your choice of AI stack - Use a managed cloud option, bring your own API keys (OpenAI, Anthropic, Deepgram, others), or run fully local via Ollama or LM Studio - Plain markdown files - Every meeting is a .md file stored on your computer. Open it in Obsidian, VS Code, Notion, anywhere - Custom templates - Set up templates for recurring meeting types so your summaries are always structured the same way - AI chat - Query your transcripts after the fact. Ask what was decided, what's pending, what someone said @@ -64,7 +62,7 @@ This list covers five apps, each strong for a specific use case, so you can find #### Pricing: -Free plan with local transcription or bring-your-own-key. Pro is $25/month for the managed cloud service. +Free forever, fully local + BYOK, open source. ### 2. Obsidian - best markdown note-taking app for personal knowledge management @@ -91,8 +89,8 @@ Opening one note pulls you into three others you forgot existed but suddenly nee - Free for personal use with zero feature restrictions, no sign-up required - Sync works with whatever you already use (iCloud, Dropbox, Google Drive) or pay for Obsidian Sync if you want something purpose-built - Plugin ecosystem means the app grows with your needs -- [Meeting notes](/blog/obsidian-meeting-notes/) from Char land directly in your vault since both use markdown -- Active community with real depth - forums, Discord, YouTube channels dedicated to workflows +- [Meeting notes](/blog/obsidian-meeting-notes/) from Anarlog land directly in your vault since both use markdown +- Active community with real depth: forums, Discord, YouTube channels dedicated to workflows #### Cons: @@ -131,7 +129,7 @@ Every bullet point is a block that can be linked, referenced, and surfaced anywh #### Pros: - The daily journal approach cuts friction. You stop procrastinating on "where to put this" and just write -- Bidirectional linking is more fluid here than elsewhere - connections emerge naturally rather than being manually managed +- Bidirectional linking is more fluid here than elsewhere; connections emerge naturally rather than being manually managed - PDF annotation is class-leading and built in natively, not a plugin afterthought - Free forever for personal use, open source, data stays local - The longer you use it, the more useful the graph becomes @@ -170,7 +168,7 @@ Free for personal use. Logseq Sync is in beta. No paid tiers beyond sync. #### Pros: -- The sync flexibility is genuinely unmatched - no other free app lets you bring your own backend with E2EE +- The sync flexibility is hard to beat: no other free app lets you bring your own backend with E2EE - Desktop app is solid and reliable, users regularly report six-plus years of daily use without issues - Completely free with no note limits, no device limits, no paywall surprises - Rich text editor means markdown is optional - lower barrier than most apps in this list @@ -213,10 +211,10 @@ Where Obsidian gives you a blank canvas to build whatever system you want, Inkdr #### Pros: -- The most polished cross-platform experience in this list - desktop and mobile feel consistent, sync is instant and reliable +- The most polished cross-platform experience in this list: desktop and mobile feel consistent, sync is instant and reliable - Developer-specific features are native, not bolted on via plugins - Offline-first, so the app is always ready regardless of connectivity -- UI is genuinely clean - users consistently praise how unobtrusive it feels +- UI is genuinely clean; users consistently praise how unobtrusive it feels #### Cons: @@ -250,7 +248,7 @@ $8.31/month billed annually. 30-day free trial, no credit card required. ## What are the best markdown apps for note-taking? -If you're in back-to-back meetings and losing track of what was said and decided, start with **Char**. Everything else on this list is for organizing knowledge you've already captured - Char is for capturing it in the first place. +If you're in back-to-back meetings and losing track of what was said and decided, start with **Anarlog**. Everything else on this list is for organizing knowledge you've already captured - Anarlog is for capturing it in the first place. If you want to build a knowledge base that gets more valuable the longer you use it, **Obsidian** is the answer. It takes investment to set up, but nothing compounds like a well-linked vault over years. diff --git a/apps/web/content/articles/meetily-review.mdx b/apps/web/content/articles/meetily-review.mdx index 99ddb30e26..4a9d494661 100644 --- a/apps/web/content/articles/meetily-review.mdx +++ b/apps/web/content/articles/meetily-review.mdx @@ -8,7 +8,7 @@ category: "Guides" date: "2026-04-15" --- -Meetily is a [local AI meeting tool](https://char.com/blog/local-ai-meeting-notes/) launched in December 2024. It now has over 11,000 GitHub stars and more than 88,000 downloads. It runs on macOS and Windows. It transcribes your meetings locally, no cloud required. +Meetily is a [local AI meeting tool](https://anarlog.so/blog/local-ai-meeting-notes/) launched in December 2024. It now has over 11,000 GitHub stars and more than 88,000 downloads. It runs on macOS and Windows. It transcribes your meetings locally, no cloud required. The pitch is simple: a private meeting recorder that works out of the box, costs nothing to start, and never sends your audio to a third-party server. @@ -16,22 +16,20 @@ This review covers what Meetily actually does, what it lacks, and whether it is ## **Meetily at a Glance** - -| | | +| | | | ----------------- | ------------------------------------------------------- | -| | **Meetily at a glance** | -| **Developer** | Zackriya Solutions | -| **License** | MIT | -| **GitHub stars** | [11,081](https://github.com/Zackriya-Solutions/meetily) | -| **Platform** | macOS, Windows (Linux: build from source) | -| **Free tier** | Yes, full community edition. No account required. | -| **Pro price** | $10/month or $120/year. 14-day free trial. | -| **Transcription** | Parakeet or Whisper, 100% local | -| **Summaries** | Ollama (local) or BYOK (Claude, Groq, OpenRouter) | -| **Storage** | Local SQLite database | -| **Calendar** | Not available | -| **Speaker ID** | In development | - +| | **Meetily at a glance** | +| **Developer** | Zackriya Solutions | +| **License** | MIT | +| **GitHub stars** | [11,081](https://github.com/Zackriya-Solutions/meetily) | +| **Platform** | macOS, Windows (Linux: build from source) | +| **Free tier** | Yes, full community edition. No account required. | +| **Pro price** | $10/month or $120/year. 14-day free trial. | +| **Transcription** | Parakeet or Whisper, 100% local | +| **Summaries** | Ollama (local) or BYOK (Claude, Groq, OpenRouter) | +| **Storage** | Local SQLite database | +| **Calendar** | Not available | +| **Speaker ID** | In development | ## **What Is Meetily?** @@ -82,7 +80,7 @@ This matters more for Meetily than for most tools, so it is worth being specific ## **What Works Well in Meetily** -**Windows support.** This is the single biggest practical advantage. Meetily is one of the only serious local-first meeting tools that runs on Windows. Char, Granola, Talat, and most others are macOS-only. If you or your team is on Windows, Meetily is the answer. +**Windows support.** This is the single biggest practical advantage. Meetily is one of the only serious local-first meeting tools that runs on Windows. Anarlog, Granola, Talat, and most others are macOS-only. If you or your team is on Windows, Meetily is the answer. **Zero setup for the free tier.** Download the app, open it, start a meeting. Ollama handles local summarization. You do not need an API key, an account, or any configuration to get a working transcript and summary. @@ -90,7 +88,7 @@ This matters more for Meetily than for most tools, so it is worth being specific **MIT license.** You can fork it, modify it, self-host it, or build on top of it with no restrictions. For teams with strict compliance requirements, this matters more than it sounds. -**Genuinely free community tier.** It is not a trial and it is not crippled. The core functionality, record, transcribe, summarize, works without paying anything. +**Genuinely free community tier.** It is not a trial and it is not crippled. The core functionality (record, transcribe, summarize) works without paying anything. ## **Meetily Limitations** @@ -134,6 +132,6 @@ Meetily does what it says. Local transcription, local summaries, nothing leaving The gaps are real. No calendar, summarization struggles on long calls, no search across sessions, no integrations, and no mobile app. -If you want a focused local recorder and especially if you are on Windows, Meetily is the best option in its category. If you are on Mac and want meetings integrated into your broader workflow, with calendar context, a note editor, and developer tooling, [Char](https://char.com) covers that ground instead. +If you want a focused local recorder and especially if you are on Windows, Meetily is the best option in its category. If you are on Mac and want meetings integrated into your wider workflow, with calendar context, a note editor, and developer tooling, [Anarlog](https://anarlog.so) covers that ground instead. -Deciding between the two? Read our [Char vs Meetily](https://char.com/blog/char-vs-meetily/) comparison for a full head-to-head breakdown. \ No newline at end of file +Deciding between the two? Read our [Anarlog vs Meetily](https://anarlog.so/blog/anarlog-vs-meetily/) comparison for a full head-to-head breakdown. \ No newline at end of file diff --git a/apps/web/content/articles/meeting-minutes-software.mdx b/apps/web/content/articles/meeting-minutes-software.mdx index 54a61ec990..3b5187029d 100644 --- a/apps/web/content/articles/meeting-minutes-software.mdx +++ b/apps/web/content/articles/meeting-minutes-software.mdx @@ -2,43 +2,41 @@ meta_title: "Best Meeting Minutes Software in 2026" meta_description: "Compare the best meeting minutes software for 2026. We tested AI and manual tools to find the top solutions for note-taking, collaboration, and governance." author: "Harshika" -coverImage: "/api/assets/blog/meeting-minutes-software/cover.png" featured: false category: "Comparisons" date: "2026-01-10" --- -If you're worried about missing important details while taking minutes of the meeting, here are some tools you'll find useful. +If you're worried about missing details while taking minutes of the meeting, here are tools you'll find useful. ## Meeting minutes software comparison -|   |   |   | +|   |   |   | | -------------- | ------------------------------ | --------------------------- | -| **Tool** | **Best For** | **Starting Price** | -| Char | Control over data and AI stack | Free. $25/month for Pro | -| Fellow | Remote Teams | $7/user/mo | -| MeetGeek | Sales Teams | $19/user/mo | -| Diligent | Board Meetings | Custom | -| Fireflies.ai | Transcription | $10/user/mo | -| Beenote | Structured Meetings | $552/yr (10 users) | -| MeetingBooster | Enterprise | Custom | -| Fathom | CRM Integration | Free. $20/month for Premium | -| tl;dv | Video recordings | Free. $18/month for Pro | - +| **Tool** | **Best For** | **Starting Price** | +| Anarlog | Control over data and AI stack | Free (OSS, local + BYOK). Managed cloud: [Char](https://char.com) | +| Fellow | Remote Teams | $7/user/mo | +| MeetGeek | Sales Teams | $19/user/mo | +| Diligent | Board Meetings | Custom | +| Fireflies.ai | Transcription | $10/user/mo | +| Beenote | Structured Meetings | $552/yr (10 users) | +| MeetingBooster | Enterprise | Custom | +| Fathom | CRM Integration | Free. $20/month for Premium | +| tl;dv | Video recordings | Free. $18/month for Pro | ## Detailed reviews of meeting minutes software -### 1. Char - control over your data and AI stack +### 1. Anarlog - control over your data and AI stack -![Char (foremly Char)](/api/assets/blog/blog/char-summary.png) +![Anarlog (foremly Anarlog)](/api/assets/blog/blog/anarlog-summary.png) -**[Char](https://char.com)** is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. +**[Anarlog](https://anarlog.so)** is an open-source AI note-taker that stores your data locally and gives you complete control over the AI stack. You decide if your audio, transcripts, or notes ever leave your device. You pick your preferred STT and LLM provider, which means you can go completely local if you want to. #### How it works -Char captures system audio without joining meetings as a bot. During meetings, it transcribes conversations in real-time and combines any notes you've taken with transcripts to create a summary when the meeting ends. +Anarlog captures system audio without joining meetings as a bot. It transcribes conversations in real time and combines any notes you've taken with transcripts to create a summary when the meeting ends. #### Key Features @@ -66,7 +64,7 @@ Char captures system audio without joining meetings as a bot. During meetings, i #### Pricing -Unlimited free plan with local transcription or bring-your-own-key. Pro is $25/month for managed cloud service. +Free forever, fully local + BYOK, open source. ### 2. Fellow - for remote teams @@ -109,9 +107,9 @@ Free plan includes 10 AI recordings per user per month. Paid plans start at $7/u ![](/api/assets/blog/articles/meeting-minutes-software/image-2.png) -**[MeetGeek](https://meetgeek.ai/)** is designed for sales teams and enterprises that want conversation analytics and performance tracking beyond transcription. +**[MeetGeek](https://meetgeek.ai/)** is built for sales teams and enterprises that want conversation analytics and performance tracking beyond transcription. -It captures data-driven insights into communication patterns, not just meeting notes. +It captures insights about communication patterns, not just meeting notes. #### Key features @@ -144,7 +142,7 @@ Free plan includes 5 hours of transcription per month with 3 months of transcrip ![](/api/assets/blog/articles/meeting-minutes-software/image-3.png) -**[Diligent](https://www.diligent.com/solutions/board-and-leadership-collaboration)** is built specifically for corporate boards, executive committees, and governance-focused organizations. It's designed for high-stakes board-level governance, not general meetings. +**[Diligent](https://www.diligent.com/solutions/board-and-leadership-collaboration)** is built for corporate boards, executive committees, and governance-focused organizations. It's for high-stakes board-level work, not general meetings. #### Key features @@ -156,11 +154,11 @@ Free plan includes 5 hours of transcription per month with 3 months of transcrip #### Pros -- Industry-leading security with enterprise-grade encryption and compliance -- Comprehensive governance features beyond basic meeting management -- Trusted by Fortune 500 companies and major organizations globally -- AI-powered analytics provide board-level insights on critical issues -- Seamless mobile access from any device +- Strong security with enterprise-grade encryption and compliance +- Governance features beyond basic meeting management +- Used by Fortune 500 companies +- AI-powered analytics for board-level insights +- Mobile access from any device #### Cons @@ -177,7 +175,7 @@ Diligent offers custom pricing based on organization size and requirements. Cont ![](/api/assets/blog/articles/meeting-minutes-software/image-4.png) -**[Fireflies.ai](http://fireflies.ai)** excels at helping you find specific information months later through advanced search functionality. While many tools transcribe meetings, Fireflies focuses on searchable archives and powerful search capabilities across all conversations. +**[Fireflies.ai](http://fireflies.ai)** is built to help you find information months later through search. Many tools transcribe meetings; Fireflies focuses on searchable archives across all conversations. #### Key features @@ -191,10 +189,10 @@ Diligent offers custom pricing based on organization size and requirements. Cont #### Pros -- Excellent search functionality across entire meeting history -- Sentiment analysis provides conversation insights -- Strong integration ecosystem (7,000+ apps via Zapier) -- Works well for both online and in-person meetings with mobile apps +- Search works well across entire meeting history +- Sentiment analysis for conversation insights +- Large integration ecosystem (7,000+ apps via Zapier) +- Works for online and in-person meetings via mobile apps #### Cons @@ -211,7 +209,7 @@ Free plan includes 800 minutes of storage with 3 months of transcript retention ![](/api/assets/blog/articles/meeting-minutes-software/image-5.png) -**[Beenote](https://beenote.io/)** focuses on collaborative planning, real-time note-taking, and organized follow-up with a generous free tier. It's designed for teams that need structured meeting workflows without AI automation. +**[Beenote](https://beenote.io/)** focuses on collaborative planning, real-time note-taking, and follow-up, with a generous free tier. It's for teams that need structured meeting workflows without AI automation. #### Key features @@ -257,11 +255,11 @@ Beenote is free forever with basic features. Paid plans start at $552/year for 1 #### Pros -- Powerful for complex enterprise workflows with multiple stakeholders -- Deep Outlook integration makes adoption easier in Microsoft-heavy organizations -- Meeting series feature provides excellent continuity for recurring meetings -- Advanced meeting facilitation tools (voting, pros/cons) improve decision quality -- Proven track record with 30+ years in the market +- Handles complex enterprise workflows with multiple stakeholders +- Deep Outlook integration speeds adoption in Microsoft-heavy organizations +- Meeting series feature gives continuity for recurring meetings +- Built-in voting and pros/cons tools improve decision quality +- 30+ years in the market #### Cons @@ -278,7 +276,7 @@ MeetingBooster offers custom pricing based on organization size and needs. They ![](/api/assets/blog/articles/meeting-minutes-software/image-7.png) -**[Fathom](https://www.fathom.ai/)** automatically logs everything to Salesforce, HubSpot, and other platforms immediately after each call. While other tools require you to copy-paste summaries into your CRM, Fathom eliminates that step. +**[Fathom](https://www.fathom.ai/)** logs everything to Salesforce, HubSpot, and other platforms right after each call. Other tools make you copy-paste summaries into your CRM; Fathom skips that step. #### Key features @@ -291,10 +289,10 @@ MeetingBooster offers custom pricing based on organization size and needs. They #### Pros -- Completely free for unlimited meetings -- Excellent CRM integration eliminates manual data entry -- Simple, clean interface with minimal setup required -- Fast AI processing—summaries available within minutes +- Free for unlimited meetings +- CRM integration removes manual data entry +- Clean interface with minimal setup +- Summaries available within minutes #### Cons @@ -311,7 +309,7 @@ Free forever for individuals with unlimited meetings and transcription. Team pla ![](/api/assets/blog/articles/meeting-minutes-software/image-8.png) -**[tl;dv](https://tldv.io/)** offers genuinely unlimited recordings on its free plan. Unlike other "free" options with restrictive limits, it's ideal for individuals and small teams on tight budgets. +**[tl;dv](https://tldv.io/)** offers unlimited recordings on its free plan. Unlike other "free" options with restrictive limits, it works for individuals and small teams on tight budgets. #### Key features @@ -324,10 +322,10 @@ Free forever for individuals with unlimited meetings and transcription. Team pla #### Pros -- Truly unlimited recordings on free plan (no monthly hour caps) -- Video clip feature makes it easy to share key moments -- Simple, intuitive interface with minimal setup -- Works well for both Zoom and Google Meet +- Unlimited recordings on free plan (no monthly hour caps) +- Video clips make it easy to share moments +- Intuitive interface with minimal setup +- Works for Zoom and Google Meet #### Cons @@ -342,7 +340,7 @@ Free plan includes unlimited recordings and AI summaries. Pro plan starts at $18 ## Key features that matter in meeting minutes software -When evaluating meeting minutes software, focus on these five categories: +When evaluating meeting minutes software, focus on five things. ### 1. Transcription accuracy @@ -354,19 +352,19 @@ Accuracy matters if you're relying on automated transcription. Look for: - Custom vocabulary training for specialized terms - Multi-language support if needed -Most AI meeting minutes software achieves 90-95% accuracy in ideal conditions, but accuracy drops significantly with background noise, accents, or multiple speakers talking simultaneously. +Most AI meeting minutes software hits 90-95% accuracy in ideal conditions. Accuracy drops with background noise, accents, or multiple speakers talking at once. ### 2. Privacy & security For sensitive conversations, privacy architecture matters more than compliance certifications: -- **Local-first processing** (Char) provides maximum privacy +- **Local-first processing** (Anarlog) provides maximum privacy - **End-to-end encryption** protects data in transit - **Compliance certifications** (HIPAA, SOC 2, GDPR) verify security practices - **Data retention policies** control how long recordings are stored - **Access controls** manage who can view sensitive meetings -If your conversations involve confidential information, prioritize tools with local processing or strong encryption over convenient cloud-based options. +If your conversations involve confidential information, pick tools with local processing or strong encryption over convenient cloud options. ### 3. Integrations @@ -378,7 +376,7 @@ Meeting minutes software should connect with your existing workflow: - **Communication platforms** (Slack, Teams) for sharing summaries - **Document storage** (Google Drive, Dropbox) for archival -The best integrations are bidirectional—not just pushing data out, but pulling context in to make summaries more relevant. +The best integrations are bidirectional. They push data out and pull context in, so summaries stay relevant. ### 4. Collaboration features @@ -390,7 +388,7 @@ Teams need to work together on meeting documentation: - **Approval workflows** for formal minutes requiring sign-off - **Version control** to track changes and revert if needed -Remote teams particularly benefit from strong collaboration features that keep everyone aligned regardless of location. +Remote teams benefit most from collaboration features that keep everyone aligned across time zones. ### 5. Search & retrieval @@ -406,6 +404,6 @@ The difference between a searchable meeting archive and 100 unsearchable documen ## Our top recommendation -If you value both functionality and control, Char is worth a look. You get AI-powered features without giving up ownership of your data, and you can export everything as plain markdown with zero lock-in. +If you want functionality without giving up control, try Anarlog. You get AI features without giving up ownership of your data, and everything exports as plain markdown. -You can try the tool out for free. If it's not for you, export everything and walk away. +Try it for free. If it's not for you, export everything and walk away. diff --git a/apps/web/content/articles/meeting-preparation-checklist.mdx b/apps/web/content/articles/meeting-preparation-checklist.mdx index 6c21b5fb2a..cfff85a0e0 100644 --- a/apps/web/content/articles/meeting-preparation-checklist.mdx +++ b/apps/web/content/articles/meeting-preparation-checklist.mdx @@ -3,7 +3,6 @@ meta_title: "My Go-to Meeting Preparation Checklist" display_title: "Meeting Preparation Checklist: 7 Steps to Run More Productive Meetings" meta_description: "Use this meeting preparation checklist to walk into every call with clarity and confidence. Build a repeatable system to have productive meetings." author: "John Jeong" -coverImage: "/api/assets/blog/meeting-preparation-checklist/cover.png" featured: false category: "Guides" date: "2025-11-03" @@ -21,7 +20,7 @@ Most people show up with a vague memory of "we discussed this before" but can't Digging through old emails, Slack threads, and scattered notes takes hours and you still miss things. -**A better system 💡:** Use an AI notetaker that lets you search across all past conversations instantly. With Char's Search feature, press cmd + k and type any keyword: a project name, a person, a specific topic. You'll see every note containing that term, along with when it was mentioned and who said it. +**A better system 💡:** Use an AI notetaker that lets you search across all past conversations instantly. With Anarlog's Search feature, press cmd + k and type any keyword: a project name, a person, a specific topic. You'll see every note containing that term, along with when it was mentioned and who said it. Preparation Checklist @@ -31,7 +30,7 @@ Walking into a meeting with specific references keeps conversations focused on m ### 2. Stakeholder research & profiling: know who you're talking to -Effective meeting prep recognizes that every person brings different priorities, communication styles, and decision-making authority. +Every person brings different priorities, communication styles, and decision-making authority. Effective prep accounts for that. Before the meeting, research each participant: @@ -40,13 +39,13 @@ Before the meeting, research each participant: - **Decision-making authority** -- Can this person say yes, or do they need to escalate? Who influences their thinking? - **Recent context** -- What wins or setbacks have they experienced lately that might affect their perspective? -This research helps you tailor your communication approach and anticipate conflicts before they surface. When you know that two participants have historically disagreed on resource allocation, or that the legal team has concerns about compliance implications, you can address these tensions in your preparation rather than getting blindsided mid-meeting. +This research helps you tailor your approach and anticipate conflict. If two participants have disagreed on resource allocation before, or the legal team has compliance concerns, you can address those tensions in prep instead of getting blindsided mid-meeting. **Where to gather this intel 💡:** LinkedIn for professional background, your CRM for past interactions, and your meeting history. Preparation Checklist -Check the Contacts View in Char's Finder to see every meeting you've had with each person, what they care about, and where previous conversations left off. Filter contacts by organization to understand team dynamics and spot patterns in what resonates with that company. +Check the Contacts View in Anarlog's Finder to see every meeting you've had with each person, what they care about, and where previous conversations left off. Filter contacts by organization to understand team dynamics and spot patterns in what resonates with that company. ### 3. Sending the briefing package and meeting details @@ -60,11 +59,9 @@ Include: - **Pre-reading materials** -- Background documents, relevant data, context from previous meetings. Keep these concise; nobody reads 40-page reports. - **Preparation expectations** -- Be explicit. "Please review the budget scenarios and come prepared with your preference" tells people what to actually do. - **Decision framework** -- If you're making decisions, explain how. Consensus? Voting? Executive call? When people know the rules upfront, meetings run faster. -- **Recording notice** -- If you're recording (and if you're using Char), mention it in the invite. Transparency builds trust, especially in compliance-sensitive industries. +- **Recording notice** -- If you're recording (including with Anarlog), mention it in the invite. Transparency matters in compliance-sensitive industries. -**Pro tip 💡:** Create a single source of truth: a shared doc, a sales room, a project page, and link to it clearly in the calendar invite. Don't bury this in a massive email thread. - - +**Pro tip 💡:** Create one source of truth (a shared doc, sales room, or project page) and link to it in the calendar invite. Don't bury it in a long email thread. ### 4. Participation strategy: design for diverse voices @@ -76,9 +73,9 @@ The loudest voices dominate meetings by default. Without a deliberate participat **Structured Q&A segments:** Instead of "any questions?" which usually gets silence, try "I'm going to pause here for questions, please drop them in chat or unmute." Or assign specific Q&A windows: "After the budget overview, we'll have 10 minutes for questions about resource allocation." -**Pre-assign speaking roles:** If you need input from specific people, tell them before the meeting. "Dev, I'd like you to speak to the technical feasibility. Priya, can you cover the customer impact?" People contribute better when they know they're expected to speak and have time to prepare. +**Pre-assign speaking roles:** If you need input from specific people, tell them before the meeting. "Dev, I'd like you to speak to technical feasibility. Priya, can you cover customer impact?" People contribute better when they know they're expected to speak and have time to prepare. -This ensures you actually hear the perspectives that matter before making decisions. +Do this and you'll actually hear the perspectives that matter before making decisions. ### 5. Fallback plans: prepare for when discussions stall @@ -100,57 +97,56 @@ When something goes sideways, you're steering the meeting back on track instead ### 6. Meeting recording: capture everything without the overhead -Traditional meeting documentation either misses crucial details or requires so much effort that nobody maintains it. Taking manual notes means you're distracted from the actual conversation. Inviting a bot to your call announces to everyone that their words are being sent to the cloud—not ideal for sensitive discussions. +Traditional meeting documentation either misses details or takes so much effort that nobody keeps it up. Manual notes pull you out of the conversation. Inviting a bot announces to everyone that their words are being sent to the cloud, which doesn't work for sensitive discussions. -Char runs entirely on your device. No bot joins your call. No data leaves your machine. It just captures the conversation, both your microphone and system audio, so you have a complete record without the overhead. +Anarlog runs on your device. No bot joins. No data leaves your machine. It captures both your microphone and system audio, so you get a full record without the overhead. -Char +Anarlog **What to consider when recording 💡:** -- **Consent management:** In many jurisdictions and industries, you need explicit consent to record conversations. Mention recording in your meeting invite. For higher-stakes meetings, consider verbal confirmation at the start: "Just confirming we're recording this for our records, everyone comfortable with that?" -- **For compliance-sensitive industries** (healthcare, legal, finance), zero-lock-in tools like Char give you complete control. When discussing patient information, legal matters, or confidential business details, choosing your own AI stack and storing files as plain markdown helps you meet HIPAA, attorney-client privilege, and other regulatory requirements. -- **Environment matters:** Find a quiet space. Use a decent microphone. Background noise and poor audio quality break transcription accuracy. -- **Offline meetings:** If you're recording an in-person conversation, make sure your meeting assistant works when you need it most. Choose tools that don't rely on internet connectivity for transcription and note-taking. Char runs entirely on your device using local AI models, so it works in basement conference rooms, on planes, or anywhere without WiFi. +- **Consent management:** In many jurisdictions and industries, you need explicit consent to record. Mention recording in your invite. For higher-stakes meetings, get verbal confirmation at the start: "Just confirming we're recording this for our records, everyone comfortable with that?" +- **Compliance-sensitive industries** (healthcare, legal, finance): zero-lock-in tools like Anarlog give you control. When discussing patient information, legal matters, or confidential business details, picking your own AI stack and storing files as plain markdown helps you meet HIPAA, attorney-client privilege, and other regulations. +- **Environment matters:** Find a quiet space. Use a decent microphone. Background noise and poor audio quality wreck transcription accuracy. +- **Offline meetings:** Pick tools that don't rely on the internet for transcription. Anarlog runs on your device using local AI models, so it works in basement conference rooms, on planes, or anywhere without WiFi. ### 7. Meeting templates: stop starting from scratch every time -You run the same types of meetings repeatedly: customer calls, team standups, strategy reviews, performance check-ins. Templates reduce cognitive load by giving you a proven framework. +You run the same meeting types over and over: customer calls, standups, strategy reviews, performance check-ins. Templates reduce cognitive load by giving you a known frame. **How to create meeting templates:** Identify your recurring meeting types. For each, define the standard structure. Customer discovery might be: context questions (10 min), pain points (15 min), solution exploration (15 min), next steps (5 min). Weekly team updates might be: wins, blockers, priorities, action items. -Char lets you create custom templates that automatically structure your AI-generated summaries. Set a default template in Settings, and every meeting gets summarized in that format automatically. Or select a specific template right after finishing a recording for one-off meetings. +Anarlog lets you create custom templates that automatically structure your AI-generated summaries. Set a default template in Settings, and every meeting gets summarized in that format automatically. Or select a specific template right after finishing a recording for one-off meetings. Preparation Checklist To create a custom template, define your sections (the structure you want) and add system instructions (rules for the AI to follow). For example: -- **For user interviews** -- "For every bullet point you write, attach the user's actual quote as a reference next to it." Now every interview summary comes with supporting evidence automatically. -- **For sales calls** -- "Generate the summary in JSON format with fields: painPoints, budget, timeline, decisionMakers." Perfect if you're pushing data to a CRM. +- **For user interviews** -- "For every bullet point you write, attach the user's actual quote as a reference next to it." Every interview summary comes with supporting evidence. +- **For sales calls** -- "Generate the summary in JSON format with fields: painPoints, budget, timeline, decisionMakers." Useful if you're pushing data to a CRM. - **For therapy or coaching sessions** -- "Focus on the client's own words and emotional themes. Minimize interpretation." -The more specific your instructions, the better the AI tailors notes to your needs. +The more specific your instructions, the closer the notes get to what you actually need. --- -These seven steps transform chaotic meeting prep into a structured process. +These seven steps turn chaotic meeting prep into a process you can repeat. -## Use Char for productive meetings +## Use Anarlog for productive meetings -Meeting preparation is exhausting when you're doing it manually: searching through old emails, reconstructing context from memory, frantically taking notes while trying to stay present. +Manual meeting prep is exhausting: searching old emails, reconstructing context from memory, frantically taking notes while trying to stay present. -Char eliminates that overhead. +Anarlog removes that overhead. **Before the meeting:** Use Search (cmd + k) to instantly find relevant past conversations. Check Contacts View to see your complete history with each participant. Ask AI Chat questions like "What did we decide about the pricing model?" to pull specific context without digging through transcripts. -**During the meeting:** Stop trying to be a human tape recorder. Char captures everything automatically—both what's said and what you're thinking. Everything is stored as plain markdown files with zero lock-in. You choose your AI stack, making it the right choice for sensitive conversations in healthcare, legal, and finance. You can stay fully present in the conversation instead of splitting focus between listening and documenting. +**During the meeting:** Stop trying to be a human tape recorder. Anarlog captures everything automatically: what's said and what you're thinking. Everything is stored as plain markdown files with zero lock-in. You pick your AI stack, which makes it workable for sensitive conversations in healthcare, legal, and finance. You stay present instead of splitting focus between listening and documenting. -**After the meeting:** Don't immediately process anything. Take an actual break. When you're ready, your AI-generated summary is waiting with your custom template already applied. Hover over any part to see the exact quote from the transcript. Ask AI Chat "What are my action items?" instead of hunting through notes. Search across all meetings to track themes and patterns. +**After the meeting:** Don't process anything right away. Take a break. When you're ready, the summary is waiting with your custom template applied. Hover over any part to see the exact quote from the transcript. Ask AI Chat "What are my action items?" instead of hunting through notes. Search across all meetings to track themes and patterns. -You spend your mental energy on thinking, not on reconstructing what happened or organizing information. +Spend your mental energy on thinking, not on reconstructing what happened. -[Download Char free](/) and have productive meetings with confidence +[Download Anarlog free](/) and have productive meetings with confidence - diff --git a/apps/web/content/articles/meeting-productivity-tools.mdx b/apps/web/content/articles/meeting-productivity-tools.mdx index 4c2c5863c7..f36bfd6361 100644 --- a/apps/web/content/articles/meeting-productivity-tools.mdx +++ b/apps/web/content/articles/meeting-productivity-tools.mdx @@ -1,7 +1,7 @@ --- meta_title: "The 5 Meeting Productivity Tools Worth Using in 2026" display_title: "Top 5 Software to Make Your Meetings Productive" -meta_description: "A no-fluff guide to the tools that fix different parts of a broken meeting workflow - and where Char fits in." +meta_description: "A no-fluff guide to the tools that fix different parts of a broken meeting workflow - and where Anarlog fits in." author: - "John Jeong" featured: false @@ -9,13 +9,13 @@ category: "Comparisons" date: "2026-02-19" --- -Meetings are a category of problems that no single tool can solve. You need the right time to meet, a way to stay engaged during the meeting, a place to capture what was discussed, somewhere to ideate visually, and a calendar that isn't completely dominated by other people's priorities. +No single tool fixes meetings. You need a way to schedule, stay engaged, capture what was discussed, ideate visually, and a calendar that isn't dominated by other people's priorities. -Most "best tools for meetings" articles hand you a list of 25 apps and call it a day. This one doesn't. We picked five tools and focused on being honest about what each one actually does well and where it falls short. +Most "best tools for meetings" articles hand you 25 apps and call it a day. This one picks five and is honest about what each does well and where it falls short. Here's what we're covering: -- **Char**: for capturing what happens in meetings without giving up control of your data +- **Anarlog**: for capturing what happens in meetings without giving up control of your data - **Calendly**: for scheduling meetings without the back-and-forth - **Grain**: for teams that need to record, share clips, and sync meeting insights to a CRM - **Miro**: for visual thinking and collaboration during and around meetings @@ -25,24 +25,24 @@ Let's get into it. ## 5 best **meeting productivity tools in 2026** -### 1. Char: best AI notepad for meetings +### 1. Anarlog: best AI notepad for meetings ![](/api/assets/blog/articles/meeting-productivity-tools/image-1-3.png) -Most AI meeting tools make the same trade: convenience in exchange for your data living in someone else's database, locked into their format, processed by their AI. Char doesn't make that trade. +Most AI meeting tools make the same trade: convenience for your data living in someone else's database, locked into their format, processed by their AI. Anarlog doesn't make that trade. -[Char](https://char.com) (formerly Hyprnote) is an open-source AI notepad for meetings, built for people who want complete control over their files, their AI stack, and what happens to their data.  +[Anarlog](https://anarlog.so) (formerly Anarlog) is an open-source AI notepad for meetings, built for people who want control over their files, AI stack, and data.  -Every meeting is saved as a plain .md file on your device. You choose which AI processes it. Nothing is locked behind Char's ecosystem. +Every meeting is saved as a plain .md file on your device. You pick which AI processes it. Nothing is locked behind Anarlog's ecosystem. -That design is deliberate. Engineers, developers, and privacy-conscious professionals in fields like law, healthcare, and finance don't want a SaaS tool deciding what happens to their meeting data. +That design is deliberate. Engineers, developers, and privacy-conscious professionals in law, healthcare, and finance don't want a SaaS tool deciding what happens to their meeting data. #### Key Features - **System audio capture** - no bots, no calendar access, works on Zoom, Teams, phone calls, and in-person conversations - **Real-time transcription** - live transcript as the meeting happens, so you can stay present instead of furiously typing - **AI summaries** - combines your notes and transcript into structured summaries with action items -- **Your choice of AI stack** - use Char's managed cloud, bring your own API keys (OpenAI, Anthropic, Deepgram), or run fully local via Ollama or LM Studio +- **Your choice of AI stack** - use a managed cloud option, bring your own API keys (OpenAI, Anthropic, Deepgram), or run fully local via Ollama or LM Studio - **Plain markdown files** - every meeting is a .md file on your computer; open it in Obsidian, VS Code, Notion, anywhere - **Custom templates** - reusable structures for recurring meeting types - **AI chat + semantic search** - query your transcripts after the fact, search across all past meetings @@ -50,11 +50,11 @@ That design is deliberate. Engineers, developers, and privacy-conscious professi #### Pros -- True data ownership - your files, on your device, not in someone's database -- No lock-in of any kind: portable format, swappable AI providers, works with any editor -- Works for any conversation - phone calls, in-person meetings, anything with audio, not just scheduled video calls -- Fully local mode available for teams that can't send data to any external service -- Open source builds trust with security teams; the code is there to inspect +- Real data ownership - your files, on your device, not in someone's database +- No lock-in: portable format, swappable AI providers, works with any editor +- Works for any conversation - phone calls, in-person, anything with audio, not just scheduled video calls +- Fully local mode for teams that can't send data to any external service +- Open source - security teams can inspect the code before approving #### Cons @@ -64,7 +64,7 @@ That design is deliberate. Engineers, developers, and privacy-conscious professi #### Pricing -Free with local transcription or bring-your-own API keys. Pro is $25/month for the managed cloud service. +Free forever, fully local + BYOK, open source. ### 2. Calendly: best for scheduling without the email back-and-forth @@ -72,7 +72,7 @@ Free with local transcription or bring-your-own API keys. Pro is $25/month for t You share a link, the other person picks a time, it lands on both calendars. That's the premise, and [Calendly](https://calendly.com/) executes it cleanly.  -What makes it more than a booking page is how much complexity it absorbs underneath: multiple calendars, team routing, automated follow-ups, while the person booking sees nothing but a simple interface. +It's more than a booking page because of how much complexity it absorbs underneath: multiple calendars, team routing, automated follow-ups. The person booking sees a simple interface. For groups where no one shares a calendar - cross-company sessions, client kickoffs - Doodle is cleaner.  @@ -88,7 +88,7 @@ For groups where no one shares a calendar - cross-company sessions, client kicko - Once it's set up, scheduling just happens - the back-and-forth stops - Automatic timezone detection handles international meetings without anyone thinking about it -- Free plan is genuinely usable for basic individual scheduling +- Free plan is usable for basic individual scheduling #### Cons @@ -115,7 +115,7 @@ Free plan with one event type. Standard is $12/user/month, Teams is $20/user/mon #### Pros -- Video clips are genuinely useful - sharing a precise 90-second moment beats sending a full recording +- Video clips are useful - sharing a 90-second moment beats sending a full recording - CRM sync means the data entry that normally gets skipped actually happens - Free plan is generous: unlimited recordings with one Notetaker seat @@ -135,7 +135,7 @@ Free plan with unlimited recordings (one Notetaker seat). Starter is $19/user/mo If you've ever watched someone share their screen, open a Google Doc, and spend 30 minutes trying to explain something that needed to be drawn, you understand what [Miro](https://miro.com/) solves.  -It's an infinite collaborative canvas, and the most capable digital replacement for a physical whiteboard that exists right now. +It's an infinite collaborative canvas, and the closest digital replacement for a physical whiteboard right now. #### Key Features @@ -147,9 +147,9 @@ It's an infinite collaborative canvas, and the most capable digital replacement #### Pros -- Nothing else comes close for visual collaboration - the infinite canvas genuinely changes how teams think together -- Strong for both live sessions and async work, which most tools aren't -- Easy to get started; most people figure it out without a tutorial +- Nothing else comes close for visual collaboration - the infinite canvas changes how teams think together +- Works for live sessions and async work, which most tools don't +- Easy to start; most people figure it out without a tutorial #### Cons @@ -167,7 +167,7 @@ Free plan with three editable boards and unlimited collaborators. Starter is $8/ You can use every tool on this list correctly and still end up with a week that's wall-to-wall meetings and no time for actual work. That's where [Reclaim](https://reclaim.ai/) comes in.  -It sits on top of your Google Calendar or Outlook as an intelligent layer - you tell it your priorities (focus time, lunch, recurring 1:1s), and it finds the best times, marks them appropriately, and reshuffles lower-priority blocks when something more important needs the slot. Your calendar ends up reflecting what you actually want your week to look like. +It sits on top of Google Calendar or Outlook as a layer. You tell it your priorities (focus time, lunch, recurring 1:1s), and it finds the best times, marks them, and reshuffles lower-priority blocks when something more important needs the slot. Your calendar ends up reflecting what you actually want your week to look like. #### Key Features @@ -179,9 +179,9 @@ It sits on top of your Google Calendar or Outlook as an intelligent layer - you #### Pros -- Once configured, your calendar reflects your priorities rather than just everyone else's requests -- The priority system is smart - higher-priority items automatically overbook lower-priority ones -- Free plan is genuinely useful, not just a trial +- Once configured, your calendar reflects your priorities rather than everyone else's requests +- Priority system is smart - higher-priority items automatically overbook lower-priority ones +- Free plan is useful, not just a trial #### Cons @@ -199,12 +199,12 @@ These five tools solve different parts of the same problem: **Getting on the calendar without the email chain**: Calendly (or Doodle for group polls) -**Capturing what was said and decided**: Char (zero lock-in, no bot, your AI stack) or Grain (team-first, video clips, CRM sync) +**Capturing what was said and decided**: Anarlog (zero lock-in, no bot, your AI stack) or Grain (team-first, video clips, CRM sync) **Thinking visually together**: Miro **Protecting time for actual work**: Reclaim.ai -None of these replace each other. And none of them replace the part where you actually pay attention and do the work. But if you're losing hours every week to scheduling friction, forgotten decisions, or a calendar you feel like you have no control over - starting with even one of these changes the situation meaningfully. +None of these replace each other. And none replace the part where you pay attention and do the work. But if you're losing hours every week to scheduling friction, forgotten decisions, or a calendar you don't control, even one of these changes the picture. -We're biased, but we'd start with Char. Not because it's ours, but because if you're not capturing what's actually happening in your meetings - on your terms - every other optimization sits on a leaky foundation. +We're biased, but we'd start with Anarlog. If you're not capturing what's happening in your meetings on your terms, every other optimization sits on a leaky foundation. diff --git a/apps/web/content/articles/mistral-api-key.mdx b/apps/web/content/articles/mistral-api-key.mdx index 526287e482..6d3e1b3161 100644 --- a/apps/web/content/articles/mistral-api-key.mdx +++ b/apps/web/content/articles/mistral-api-key.mdx @@ -19,7 +19,7 @@ You need: No credit card required for the free Experiment plan. -One thing worth knowing upfront: on the free Experiment plan, your API requests may be used to train Mistral's models. If you're working with sensitive data, you'll want to upgrade to a paid plan, which comes with data isolation. +One thing to know upfront: on the free Experiment plan, your API requests may be used to train Mistral's models. If you're working with sensitive data, upgrade to a paid plan, which comes with data isolation. ## **How to generate your Mistral API key (step by step)** @@ -33,33 +33,31 @@ One thing worth knowing upfront: on the free Experiment plan, your API requests On the free Experiment plan, every model is capped at 500,000 tokens per minute and 1,000,000,000 tokens per month. That's a billion tokens a month, which sounds like a lot until you're running something in a loop. -The per-minute limit is the one you'll actually hit. 500k tokens per minute works out to roughly 375,000 words per minute, which is fast, but easy to saturate if you're processing batches or running concurrent requests. If a request gets rate limited, the API returns a 429 and you'll need to retry with backoff. +The per-minute limit is the one you'll hit. 500k tokens per minute is roughly 375,000 words per minute. Fast, but easy to saturate if you're processing batches or running concurrent requests. If a request gets rate limited, the API returns a 429 and you retry with backoff. -The monthly cap of 1B tokens is genuinely generous for individual use. At typical usage, most developers won't come close. If you're building something at scale or need guaranteed throughput, upgrading to the Scale plan removes these ceilings and adds data isolation. +The monthly cap of 1B tokens is generous for individual use. Most developers won't come close. If you're building at scale or need guaranteed throughput, the Scale plan removes these ceilings and adds data isolation. ## **Mistral API pricing: how much does it cost** Mistral charges per million tokens, counting both input (what you send) and output (what comes back). There's no subscription fee on the API side. You pay for what you use. - -|   |   |   | +|   |   |   | | ----------------- | ---------------- | ---------------- | -| **Model** | **Input** | **Output** | +| **Model** | **Input** | **Output** | | Mistral Small 3.2 | $0.10 / M tokens | $0.30 / M tokens | -| Mistral Large 3 | $0.50 / M tokens | $1.50 / M tokens | - +| Mistral Large 3 | $0.50 / M tokens | $1.50 / M tokens | -To put those numbers in context: a million input tokens is roughly 750,000 words, or about 1,500 pages of text. At Mistral Small's rates, that costs ten cents. For most personal or small-team use, the monthly bill is negligible. +For context: a million input tokens is roughly 750,000 words, or about 1,500 pages. At Mistral Small's rates, that's ten cents. For most personal or small-team use, the monthly bill is negligible. -If you're running batch jobs that don't need an immediate response, Mistral offers a 50% discount on batch API requests. Worth using if latency isn't a concern. +If you're running batch jobs that don't need an immediate response, Mistral offers a 50% discount on batch API requests. Worth using when latency doesn't matter. ## **Which Mistral model should you use** If you're not sure, use mistral-small-latest. -It's fast, cheap, multimodal, multilingual, and Apache 2.0 licensed. The Apache 2.0 license matters if you're building something commercial. No usage restrictions, no license fees. +It's fast, cheap, multimodal, multilingual, and Apache 2.0 licensed. The Apache 2.0 license matters if you're building something commercial: no usage restrictions, no license fees. -Step up to mistral-large-latest if you need heavier reasoning: complex multi-step tasks, nuanced analysis, or anything where output quality is worth paying more for. It's five times the price of Small, so it's worth being deliberate about when you actually need it. +Step up to mistral-large-latest if you need heavier reasoning: complex multi-step tasks, nuanced analysis, or anything where output quality is worth paying more for. It's five times the price of Small, so be deliberate about when you actually need it. ## **How to use your Mistral API key in your project** @@ -73,9 +71,9 @@ Test it with a curl call: ```bash curl https://api.mistral.ai/v1/chat/completions \ - -H "Authorization: Bearer $MISTRAL_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ "model": "mistral-small-latest", "messages": [{"role": "user", "content": "Hello"}] }' + -H "Authorization: Bearer $MISTRAL_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "model": "mistral-small-latest", "messages": [{"role": "user", "content": "Hello"}] }' ``` If you get a JSON response with a choices array back, the key is working. If you get a 401, double-check that the environment variable is set correctly in your current session. @@ -86,22 +84,22 @@ For Python projects, the python-dotenv package is the standard way to load a .en from dotenv import load_dotenv import os -load_dotenv() +load_dotenv api_key = os.getenv("MISTRAL_API_KEY") ``` -## Use your Mistral API key in Char for AI meeting notes that stay on your device +## Use your Mistral API key in Anarlog for AI meeting notes that stay on your device ![](/api/assets/blog/articles/mistral-api-key/image-1.png) -Getting your own API key is a deliberate choice. It means you want control over what AI you're using and what happens to the data you send it. Char works on the same principles.  +Getting your own API key is a deliberate choice. It means you want control over which AI you're using and what happens to the data you send it. Anarlog works on the same principle.  -It’s an open-source AI notepad for meetings that gives you complete control over your AI stack and your data.  +It's an open-source AI notepad for meetings that gives you control over your AI stack and your data.  The workflow is simple: record, transcribe locally, summarize with your own Mistral key. You choose which model runs. You can swap to Anthropic, OpenAI, or a local Ollama model any time without losing your files or your history. -Everything else stays local. The audio file, the transcript, the summary are all saved as plain markdown files on your device, not on Char's servers. Char doesn't have servers storing your conversations. There's nothing to breach, no vendor to trust with your data. +Everything else stays local. The audio file, transcript, and summary are saved as plain markdown files on your device, not on Anarlog's servers. Anarlog doesn't have servers storing your conversations. There's nothing to breach and no vendor to trust with your data. -Plus, all core features, local transcription, and BYOK stay completely free. You’re already paying for the API key, you shouldn’t have to pay twice. But if you want cloud services and don't want to manage keys at all, there is a $8/month plan you can check out.  +All core features, local transcription, and BYOK are free. You're already paying for the API key; you shouldn't have to pay twice. If you want cloud services and don't want to manage keys at all, there's an $8/month plan.  -To connect Mistral, open Char's settings, go to API Keys, paste your key, and that's it. Try it out for free now - [Download Char for macOS](https://char.com/download/). \ No newline at end of file +To connect Mistral, open Anarlog's settings, go to API Keys, and paste your key. That's it. [Download Anarlog for macOS](https://anarlog.so/download/) to try it. \ No newline at end of file diff --git a/apps/web/content/articles/mistral-data-retention-policy.mdx b/apps/web/content/articles/mistral-data-retention-policy.mdx index 325b68ff65..dd8abe8ef4 100644 --- a/apps/web/content/articles/mistral-data-retention-policy.mdx +++ b/apps/web/content/articles/mistral-data-retention-policy.mdx @@ -8,58 +8,58 @@ category: "Guides" date: "2026-03-17" --- -We've been evaluating data retention policies of major AI providers and Mistral is the only provider headquartered in the European Union. That is not a minor detail. It means Mistral is natively subject to GDPR, stores your data in the EU by default, and was built from the ground up under stricter privacy regulation than any of the US-based alternatives. +We've been evaluating data retention policies of major AI providers, and Mistral is the only one headquartered in the European Union. That's not a minor detail. Mistral is natively subject to GDPR, stores your data in the EU by default, and was built under stricter privacy regulation than any US-based alternative. -For teams evaluating AI providers on data governance grounds, that starting point matters. +For teams evaluating AI providers on data governance, that starting point matters. -Here is how the policy works in practice. +Here's how the policy works in practice. ## What Mistral Stores by Default -For API users, Mistral retains your inputs and outputs for 30 rolling days after the request is processed. This window exists for abuse monitoring. After 30 days, the data is deleted. It is not used to train Mistral's models unless you explicitly opt in. +For API users, Mistral retains your inputs and outputs for 30 rolling days after the request is processed. This window exists for abuse monitoring. After 30 days, the data is deleted. It's not used to train Mistral's models unless you explicitly opt in. For free users of Le Chat, Mistral's consumer product, your conversations may be used for model improvement unless you opt out. You can do this by opening the Privacy menu in the Admin Console and disabling the toggle under Anonymous improvement data. Paid plans are handled differently. Users on Le Chat Team, Le Chat Enterprise, or any paid API plan have their data excluded from training by default. No opt-out required. -Account metadata follows a separate schedule. Name and identity data is kept for 5 years after account termination. Email and phone number are retained for 1 year after account deletion. Technical data such as connection logs is kept for 1 rolling year. +Account metadata follows a separate schedule. Name and identity data is kept for 5 years after account termination. Email and phone number are kept for 1 year after account deletion. Technical data like connection logs is kept for 1 rolling year. ## Where Your Data Is Stored -By default, all data is hosted within the European Union. Mistral [explicitly states](https://legal.mistral.ai/terms/privacy-policy) that it prioritizes EU-based infrastructure providers and applies GDPR-standard safeguards when any non-EU provider is involved. +By default, all data is hosted in the European Union. Mistral [explicitly states](https://legal.mistral.ai/terms/privacy-policy) that it prioritizes EU-based infrastructure providers and applies GDPR-standard safeguards when any non-EU provider is involved. -There is a US API endpoint available if you need it. Using it will route your data to US infrastructure. If EU residency matters for your use case, stick with the default endpoint and confirm with your Mistral account contact that your deployment is EU-only. +There's a US API endpoint if you need it, which routes your data to US infrastructure. If EU residency matters for your use case, stick with the default endpoint and confirm with your Mistral contact that your deployment is EU-only. ## Is Zero Data Retention Applicable? -ZDR is available on the API. When activated, Mistral does not retain your inputs or outputs beyond what is needed to return the result. The 30-day abuse monitoring window does not apply. +ZDR is available on the API. When activated, Mistral doesn't retain your inputs or outputs beyond what's needed to return the result. The 30-day abuse monitoring window doesn't apply. -One important limitation: Zero Data Retention is not available on Le Chat. Because the consumer product relies on stored conversation history to function, ZDR cannot be applied there. If ZDR is a requirement, you need to use the API directly. +One limitation: ZDR isn't available on Le Chat. The consumer product relies on stored conversation history to function. If ZDR is a requirement, use the API directly. ## Training Opt-Out in Plain Terms -Free Le Chat users are opted in to training by default and can opt out via Privacy settings. Paid Le Chat and paid API users are opted out by default. ZDR API customers have no training risk because their data is never retained to begin with. +Free Le Chat users are opted in to training by default and can opt out via Privacy settings. Paid Le Chat and paid API users are opted out by default. ZDR API customers have no training risk because their data is never retained. -If you are a paid API customer, you do not need to take any action to keep your data out of training pipelines. It is already excluded. +If you're a paid API customer, you don't need to do anything to keep your data out of training pipelines. It's already excluded. ## GDPR and Compliance -As an EU company, Mistral operates under GDPR natively rather than as a compliance overlay. A Data Processing Addendum is available for all business customers. GDPR rights including access, correction, deletion, and portability can be exercised by contacting Mistral's privacy team directly. +As an EU company, Mistral operates under GDPR natively, not as a compliance overlay. A Data Processing Addendum is available for all business customers. GDPR rights (access, correction, deletion, portability) can be exercised by contacting Mistral's privacy team directly. -Mistral does not currently publish a HIPAA Business Associate Agreement. For US healthcare organizations that need HIPAA coverage, Mistral is not the right choice without confirming a BAA is available for your specific plan. +Mistral doesn't currently publish a HIPAA Business Associate Agreement. For US healthcare organizations that need HIPAA coverage, don't pick Mistral without first confirming a BAA is available for your specific plan. ## How Mistral Compares With Other AI Providers -The structural difference between Mistral and the US-based providers is data residency by default. With [OpenAI](/blog/chatgpt-data-retention-policy/), [Anthropic](/blog/anthropic-data-retention-policy/), or Google, you are opting into EU data residency as an enterprise feature. With Mistral, EU residency is the baseline and US routing is the opt-in. +The structural difference between Mistral and US-based providers is data residency by default. With [OpenAI](/blog/chatgpt-data-retention-policy/), [Anthropic](/blog/anthropic-data-retention-policy/), or Google, EU residency is an enterprise opt-in. With Mistral, EU residency is the baseline and US routing is the opt-in. -For organizations in the EU or those operating under GDPR, that reversal is meaningful. It shifts the burden of compliance from your team to Mistral's default configuration. +For organizations in the EU or operating under GDPR, that reversal matters. It shifts the burden of compliance from your team to Mistral's default config. -## Use Mistral API to Take Meeting Notes Through Char +## Use Mistral API to Take Meeting Notes Through Anarlog -[Char](https://char.com) is an open-source AI notepad for meetings that supports Mistral as one of its AI provider options. When you bring your own Mistral API key, your meeting data is handled under Mistral's API policy: 30-day retention for abuse monitoring, not used for training, stored in the EU by default. +[Anarlog](https://anarlog.so) is an open-source AI notepad for meetings that supports Mistral as one of its AI provider options. When you bring your own Mistral API key, your meeting data is handled under Mistral's API policy: 30-day retention for abuse monitoring, not used for training, stored in the EU by default. -If you activate ZDR on your Mistral API account, that protection applies to requests made through Char as well. Nothing is stored beyond the time needed to process the result. +If you activate ZDR on your Mistral API account, that protection applies to requests made through Anarlog as well. Nothing is stored beyond the time needed to process the result. -And if your data governance requirements change, or a different provider gets approved by your security team, you can switch inside Char without changing how your notes are stored or structured. Your files stay on your device regardless. +If your data governance requirements change, or a different provider gets approved by your security team, you can switch inside Anarlog without changing how your notes are stored or structured. Your files stay on your device regardless. -[Download Char for macOS](https://char.com/download/apple-silicon) and use the AI provider your security team actually trusts. \ No newline at end of file +[Download Anarlog for macOS](https://anarlog.so/download/apple-silicon) and use the AI provider your security team trusts. \ No newline at end of file diff --git a/apps/web/content/articles/notetakers-for-developers.mdx b/apps/web/content/articles/notetakers-for-developers.mdx index 178029da81..ca868d3918 100644 --- a/apps/web/content/articles/notetakers-for-developers.mdx +++ b/apps/web/content/articles/notetakers-for-developers.mdx @@ -8,54 +8,52 @@ category: "Comparisons" date: "2026-04-08" --- -Most "best note-taking apps" articles are written for project managers and productivity influencers. They'll recommend Notion, show you a Kanban board screenshot, and call it a day. This isn't that article. +Most "best note-taking apps" articles are written for project managers and productivity influencers. They recommend Notion, show you a Kanban board screenshot, and call it a day. This isn't that article. -If you install tools through Homebrew, keep your dotfiles in a Git repo, and care whether your notes are stored as plain markdown or a proprietary blob, you have different requirements. You want local files you can grep. You want version control that isn't "page history (30 days)." You want a tool that doesn't require an account just to write a thought down. +If you install tools through Homebrew, keep your dotfiles in a Git repo, and care whether your notes are stored as plain markdown or a proprietary blob, you have different requirements. You want local files you can grep. You want version control that isn't "page history (30 days)". You want a tool that doesn't require an account to write a thought down. -I tested and researched these five tools across developer forums, Hacker News threads, Reddit discussions, and my own workflow. Each one covers a distinct use case. Here's what I found. +I tested these tools across developer forums, Hacker News threads, Reddit discussions, and my own workflow. Each one covers a distinct use case. Here's what I found. ## How These Note-Taking Apps for Developers Compare - -| Tool | Best For | Pricing | +| Tool | Best For | Pricing | | ------------------------ | ------------------------------------------------------ | -------------------------------------- | -| Char | AI meeting notes with local files and your own LLM | Free (BYOK) / Pro $25/mo | -| Obsidian | Building a linked knowledge base over months and years | Free / Sync $5–10/mo | -| VS Code + Markdown + Git | Developers who refuse to install another app | Free | -| Joplin | Encrypted, self-hosted, cross-platform notes | Free / Cloud $5–10/mo | -| Logseq | Outliner-style thinking with block-level linking | Free / Sync $5/mo (beta) | -| Outline | Team wiki and internal documentation | Free (self-hosted) / Cloud from $10/mo | -| Novi Notes | Bonus: AI-native notes via MCP for Claude users | Varies by region | - +| Anarlog | AI meeting notes with local files and your own LLM | Free forever (OSS, local + BYOK). | +| Obsidian | Building a linked knowledge base over months and years | Free / Sync $5–10/mo | +| VS Code + Markdown + Git | Developers who refuse to install another app | Free | +| Joplin | Encrypted, self-hosted, cross-platform notes | Free / Cloud $5–10/mo | +| Logseq | Outliner-style thinking with block-level linking | Free / Sync $5/mo (beta) | +| Outline | Team wiki and internal documentation | Free (self-hosted) / Cloud from $10/mo | +| Novi Notes | Bonus: AI-native notes via MCP for Claude users | Varies by region | ## Best Note-Taking Apps for Developers in 2026 -### 1. Char[a] +### 1. Anarlog[a] -[Char](https://char.com) is an open-source AI meeting note-taker that captures system audio directly from your computer, with no bot joining the call and no calendar permissions required. You jot notes in a built-in notepad while it transcribes in the background, then the AI merges both into a structured summary. Everything is stored on your device, and you can bring your own LLM: Ollama, LM Studio, OpenAI, Anthropic, etc. +[Anarlog](https://anarlog.so) is an open-source AI meeting note-taker that captures system audio from your computer. No bot joins the call. No calendar permissions required. You jot notes in a built-in notepad while it transcribes in the background, and the AI merges both into a structured summary. Everything is stored on your device, and you can bring your own LLM: Ollama, LM Studio, OpenAI, Anthropic, etc. #### Why devs love it -- Install with brew install --cask fastrepl/fastrepl/char. There's a CLI. The repo is public on GitHub. +- Install with brew install --cask fastrepl/fastrepl/anarlog. There's a CLI. The repo is public on GitHub. - Nine speech-to-text providers including two local options (Parakeet V3 and Whisper Small via Cactus) for fully offline transcription. - Notes support code blocks, @mentions with backlinks, and the AI chat can edit your notes directly in place. - Export to Markdown, PDF (with a formatted cover page), JSON, VTT, and Org mode. You can point the storage directory at an Obsidian vault. - Record-only mode lets you capture audio now and transcribe it later. Meeting countdown auto-starts recording for calendar events. -#### What’s the catch +#### What's the catch - macOS only right now. Windows and Linux builds are planned for Q2 2026. -- No mobile app. If you need to record a call from your phone, Char can't help yet. -- No built-in CRM integrations. Meeting notes stay in Char unless you export or sync manually. +- No mobile app. If you need to record a call from your phone, Anarlog can't help yet. +- No built-in CRM integrations. Meeting notes stay in Anarlog unless you export or sync manually. - Daily notes with automatic task carry-forward and calendar linking are in preview but not shipped as the default experience yet. #### Cost -Free with no limits if you bring your own API key or run local models. The Pro plan at $25/month adds managed cloud transcription and summarization. A Lite plan was recently added as a middle tier. For a developer running Ollama locally, the free tier is genuinely unlimited. +Free with no limits if you bring your own API key or run local models. For a developer running Ollama locally, the free tier is genuinely unlimited. ### 2. Obsidian -![obsidian for developers](/api/assets/blog/articles/notetakers-for-developers/image-1.png "char-editor-width=80") +![obsidian for developers](/api/assets/blog/articles/notetakers-for-developers/image-1.png "anarlog-editor-width=80") [Obsidian](https://obsidian.md) is a markdown-based knowledge management app that stores all your notes as .md files in a local folder. You link notes together with [[double brackets]], and the graph view maps how your ideas connect. With 1,500+ community plugins, it bends into whatever shape your workflow needs. @@ -64,14 +62,14 @@ Free with no limits if you bring your own API key or run local models. The Pro p - Your notes are just .md files on disk. Claude Code, VS Code, and grep all work on them directly. No API, no MCP, no export step. - The plugin ecosystem is enormous: Kanban boards, spaced repetition, Mermaid diagrams, Dataview queries, Git integration, and Vim keybindings. - Backlinks and graph view build a map of how your ideas connect over time. You don't organize upfront. You write, link with [[brackets]], and the structure emerges. -- Canvas mode lets you spatially arrange notes and images on an infinite board for brainstorming architecture or planning sprints. -- The app is fast. Startup is near-instant even with thousands of notes, which is more than most Electron apps can say. +- Canvas mode lets you arrange notes and images on an infinite board for brainstorming architecture or planning sprints. +- The app is fast. Startup is near-instant even with thousands of notes, which most Electron apps can't claim. -#### What’s the catch +#### What's the catch - Syncing across devices costs extra. Obsidian Sync starts at $4/month. Many devs use iCloud or Dropbox as a free workaround, but conflict resolution isn't as clean. - No real-time collaboration. You can share a vault via Sync, but it's not designed for two people editing the same note simultaneously. -- The learning curve is real. New users often spend their first week configuring plugins instead of writing notes. The community calls this "plugin paralysis." +- The learning curve is real. New users often spend their first week configuring plugins instead of writing notes. The community calls this "plugin paralysis". #### Cost @@ -79,9 +77,9 @@ The core app is free forever, no account required. Obsidian Sync runs $5–10/mo ### 3. VS Code + Markdown + Git -![how to use VS Code for notetaking](/api/assets/blog/articles/notetakers-for-developers/image-2.png "char-editor-width=80") +![how to use VS Code for notetaking](/api/assets/blog/articles/notetakers-for-developers/image-2.png "anarlog-editor-width=80") -This isn't a product. It's a setup. You create a folder of .md files, open it in VS Code, and version-control it with Git.And if your team needs those markdown files published as shared docs, [GitBook](https://www.gitbook.com) syncs directly with your Git repo and renders them as a polished documentation site. Edit in VS Code, push to GitHub, GitBook handles the rest. +This isn't a product. It's a setup. You create a folder of .md files, open it in VS Code, and version-control it with Git. If your team needs those markdown files published as shared docs, [GitBook](https://www.gitbook.com) syncs directly with your Git repo and renders them as a documentation site. Edit in VS Code, push to GitHub, GitBook handles the rest. #### Why devs love it @@ -90,7 +88,7 @@ This isn't a product. It's a setup. You create a folder of .md files, open it in - Markdown All in One (5M+ installs) adds TOC generation, list continuation, and formatting shortcuts. markdownlint keeps your files consistent. - Side-by-side preview with Cmd+K V. IntelliSense autocompletes file links and headings. Drag-and-drop images just work. -#### What’s the catch +#### What's the catch - No mobile access. Your notes live on your laptop, period. Unless you set up something like GitHub Codespaces, you're stuck. - No backlinks, no graph view, no knowledge graph. Notes don't know about each other unless you manually link them in markdown. @@ -98,11 +96,11 @@ This isn't a product. It's a setup. You create a folder of .md files, open it in #### Cost -Free. VS Code is free. Git is free. A private GitHub repo is free. The only cost is the setup time and the discipline to commit regularly. Several devs automate the commit step with shell scripts or the GitDoc extension, which turns it into a fire-and-forget system. +Free. VS Code is free, Git is free, a private GitHub repo is free. The only cost is setup time and the discipline to commit regularly. Several devs automate commits with shell scripts or the GitDoc extension, which turns it into a fire-and-forget system. ### 4. Joplin -![Joplin for programmers](/api/assets/blog/articles/notetakers-for-developers/image-3.png "char-editor-width=80") +![Joplin for programmers](/api/assets/blog/articles/notetakers-for-developers/image-3.png "anarlog-editor-width=80") [Joplin](https://joplinapp.org) is an open-source note-taking app with end-to-end encryption and sync across every platform: Windows, macOS, Linux, iOS, Android, and even a terminal client. If you're migrating from Evernote and want to actually own your data, this is where most people land. @@ -113,11 +111,11 @@ Free. VS Code is free. Git is free. A private GitHub repo is free. The only cost - The terminal app (joplin) lets you manage notes entirely from the command line. It's the only mainstream note app that ships a TUI. - Web clipper for Chrome and Firefox. Evernote import tool that actually works. Markdown with notebook and tag organization. -#### What’s the catch +#### What's the catch -- The desktop app is Electron and feels like it. It's functional, not fast. Opening a large notebook takes a noticeable beat compared to Obsidian or Apple Notes. +- The desktop app is Electron and feels like it. Functional, not fast. Opening a large notebook takes a beat compared to Obsidian or Apple Notes. - No real-time collaboration. Joplin is a single-player tool. Sharing a note means exporting it. -- The UI is honest but dated. If you care about visual polish, Joplin will feel utilitarian. The community calls it "function over form." +- The UI is honest but dated. If you care about visual polish, Joplin will feel utilitarian. The community calls it "function over form". #### Cost @@ -125,9 +123,9 @@ The app is free and open-source with no feature gates. Joplin Cloud is $5/month ### 5. Logseq -![logseq for notetaking](/api/assets/blog/articles/notetakers-for-developers/image-4.png "char-editor-width=80") +![logseq for notetaking](/api/assets/blog/articles/notetakers-for-developers/image-4.png "anarlog-editor-width=80") -[Logseq](https://logseq.com) is an open-source, block-based outliner where every bullet point is a first-class object that can be linked, queried, and embedded anywhere in your knowledge graph. It stores notes as local Markdown or Org-mode files and uses daily journal pages as the main entry point. With 32,000+ GitHub stars and an AGPL license, it's one of the most actively developed PKM tools in the open-source ecosystem. +[Logseq](https://logseq.com) is an open-source, block-based outliner where every bullet is a first-class object you can link, query, and embed anywhere in your graph. It stores notes as local Markdown or Org-mode files and uses daily journal pages as the entry point. With 32,000+ GitHub stars and an AGPL license, it's one of the most actively developed PKM tools in open source. #### Why devs love it @@ -136,7 +134,7 @@ The app is free and open-source with no feature gates. Joplin Cloud is $5/month - Built-in flashcards with spaced repetition. Add #card to any bullet and Logseq schedules reviews automatically. Useful for learning new frameworks or prepping for interviews. - PDF annotation with Zotero integration. Highlight a passage, and it links directly to your notes. Researchers and devs who read papers will recognize the value immediately. -#### What’s the catch +#### What's the catch - Mobile apps are the weak point. The iOS app has a history of crashes and slow loading. It works for quick capture, but extended editing on a phone is frustrating. - Sync has been Logseq's biggest friction point. The official Logseq Sync is still in beta. Without it, you're wiring up iCloud, Dropbox, or Git, all of which can cause conflicts if you're not careful. @@ -148,7 +146,7 @@ Completely free with no feature limits for local use. Logseq Sync is $5/month (b ### 6. Outline -![outline for dev teams](/api/assets/blog/articles/notetakers-for-developers/image-5.png "char-editor-width=80") +![outline for dev teams](/api/assets/blog/articles/notetakers-for-developers/image-5.png "anarlog-editor-width=80") [Outline](https://getoutline.com) is an open-source team knowledge base with real-time collaborative editing, markdown with slash commands, and blazing-fast search. Think of it as the self-hostable alternative to Notion or Confluence, but focused on doing one thing well: organizing team documentation without the bloat. @@ -159,7 +157,7 @@ Completely free with no feature limits for local use. Logseq Sync is $5/month (b - The editor is fast and modern: slash commands, nested collections, drag-and-drop, code blocks, embeds, and a clean reading experience that doesn't feel like a wiki from 2010. - REST API + webhooks + 20+ integrations including Slack, Zapier, and SSO via Google, Microsoft, or OIDC. If your team already has an identity provider, Outline slots right in. -#### What’s not there yet +#### What's not there yet - No mobile app. Outline works through a responsive web interface, which is functional but not the same as a native app for quick capture on the go. - Self-hosting requires Docker, Postgres, Redis, and an external auth provider (OIDC/OAuth). There's no built-in username/password login. If your team doesn't already run an identity provider, the setup is more involved. @@ -171,7 +169,7 @@ Self-hosted is free (open-source). Outline's cloud service runs $10/month for te ### 7. Bonus: Novi Notes -![](/api/assets/blog/articles/notetakers-for-developers/image-6.png "char-editor-width=80") +![](/api/assets/blog/articles/notetakers-for-developers/image-6.png "anarlog-editor-width=80") [Novi Notes](https://rhcwlq89.github.io/en/novi-note/) is a local-first Mac note app built by a solo developer in Seoul. The hook: it's AI-native via MCP (Model Context Protocol). Install the app, open Claude Desktop, and Claude can read and write your notes directly. No plugins, no API keys, no configuration files. It launched on Product Hunt in March 2026, hit Product of the Day #10, and it's a one-time purchase with no subscription. @@ -181,7 +179,7 @@ It's early. Mac-only, Claude-only for AI features, no cloud sync. But the zero-c There's no single tool that handles meeting transcription, knowledge management, team documentation, and daily journaling equally well. That's why this list covers seven distinct approaches instead of ranking them against each other. -If your main problem is meeting notes and you want to own the transcript, Char captures system audio without a bot, stores everything locally, and lets you run any LLM you want. The tradeoff is macOS-only for now. +If your main problem is meeting notes and you want to own the transcript, Anarlog captures system audio without a bot, stores everything locally, and lets you run any LLM you want. The tradeoff is macOS-only for now. If you're building a long-term knowledge base and want the richest ecosystem, Obsidian has 1,500 plugins and a community that's solved almost every workflow. The tradeoff is that sync costs extra and the setup time is real. @@ -195,4 +193,4 @@ If your team needs shared documentation you actually control, Outline is the sel And if you're deep in the Claude ecosystem and want your notes to be part of that workflow natively, keep an eye on Novi Notes. It's early, but the MCP-first approach is worth watching. -For meeting notes specifically, you can [download Char](https://char.com/download) and try it in your next meeting. +For meeting notes specifically, you can [download Anarlog](https://anarlog.so/download) and try it in your next meeting. diff --git a/apps/web/content/articles/obsidian-meeting-notes.mdx b/apps/web/content/articles/obsidian-meeting-notes.mdx index 03725de6af..e8b4490764 100644 --- a/apps/web/content/articles/obsidian-meeting-notes.mdx +++ b/apps/web/content/articles/obsidian-meeting-notes.mdx @@ -1,6 +1,6 @@ --- meta_title: "How to Use Obsidian for Meeting Notes" -meta_description: "Step-by-step guide to building meeting notes in Obsidian with templates, person notes, backlinks, searchable action items, and local AI transcription via Char." +meta_description: "Step-by-step guide to building meeting notes in Obsidian with templates, person notes, backlinks, searchable action items, and local AI transcription via Anarlog." author: - "John Jeong" featured: true @@ -19,7 +19,7 @@ By the end of this guide you'll have: - A meeting note template that auto-fills the date and drops your cursor where you start typing - A person note for each colleague that automatically shows every meeting they appeared in - Action items you can find across your entire vault in one search -- A way to get full meeting transcripts and AI summaries into your vault without any copy-pasting — and without your data leaving your machine +- A way to get full meeting transcripts and AI summaries into your vault without any copy-pasting, and without your data leaving your machine ## Sources and credit @@ -35,7 +35,7 @@ This guide draws from Obsidian's official documentation and real workflows share ### Step 1: Create the folder structure -![Obsidian vault with Meetings and People folders](/api/assets/blog/articles/obsidian-meeting-notes/image-1-1.png "char-editor-width=80") +![Obsidian vault with Meetings and People folders](/api/assets/blog/articles/obsidian-meeting-notes/image-1-1.png "anarlog-editor-width=80") Open your vault. Create two folders: @@ -50,7 +50,7 @@ If you already have an established vault, just add these two folders alongside w ### Step 2: Create a person note -![Person note in Obsidian with role, company, and tags properties](/api/assets/blog/articles/obsidian-meeting-notes/image-2-1.png "char-editor-width=80") +![Person note in Obsidian with role, company, and tags properties](/api/assets/blog/articles/obsidian-meeting-notes/image-2-1.png "anarlog-editor-width=80") Before you touch templates or meetings, create your first person note. In the `People/` folder, make a new note. Name it after someone you meet with regularly. Something like `Sarah Chen.md`. @@ -61,7 +61,7 @@ Add this to the note: role: Engineering Lead company: Acme tags: - - person + - person --- ## About Engineering lead on the payments team. Reports to VP Eng. @@ -82,7 +82,7 @@ date: "{{date:YYYY-MM-DD}}" type: meeting attendees: tags: - - meeting + - meeting --- # {{title}} **Attendees:** @@ -105,11 +105,11 @@ Here's what a meeting note looks like after a meeting. The key habit: write name date: "2026-04-06" type: meeting attendees: - - "[[Sarah Chen]]" - - "[[James Park]]" + - "[[Sarah Chen]]" + - "[[James Park]]" tags: - - meeting - - payments + - meeting + - payments --- # 2026-04-06 Sprint Planning **Attendees:** [[Sarah Chen]], [[James Park]] @@ -129,15 +129,15 @@ tags: Look at what just happened: -- [[Sarah Chen]] appears in multiple places. Her person note now shows this meeting in backlinks — with the surrounding context of what she said, what she decided, and what she owes. +- [[Sarah Chen]] appears in multiple places. Her person note now shows this meeting in backlinks, with the surrounding context of what she said, what she decided, and what she owes. - [[PCI audit]] links to a topic note. If that note doesn't exist yet, that's fine. When the PCI audit becomes important enough to have its own page, every meeting that mentioned it is already connected. - The action items use - [ ] task syntax. Obsidian's search can find these directly, and you can narrow it to a specific person. ### Step 5: Person notes fill themselves -![Person note with backlinks panel showing linked meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-3-1.png "char-editor-width=80") +![Person note with backlinks panel showing linked meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-3-1.png "anarlog-editor-width=80") -Open `Sarah Chen.md` in `People/`. Click the backlinks icon in the right sidebar. Every meeting where you wrote `[[Sarah Chen]]` shows up with surrounding context. You can see what was discussed, what she decided, what she owes — all without maintaining anything. +Open `Sarah Chen.md` in `People/`. Click the backlinks icon in the right sidebar. Every meeting where you wrote `[[Sarah Chen]]` shows up with surrounding context. You can see what was discussed, what she decided, what she owes, all without maintaining anything. This is the approach from the [Managing notes for meetings thread](https://forum.obsidian.md/t/managing-notes-for-meetings-and-one-on-ones-effectively/32049) that resonated with the most people. No plugins. No queries. Just links and backlinks doing the work. @@ -145,16 +145,16 @@ If you want a structured table view instead of backlinks, use a Base. The [Autom ``` filters: - and: - - file.hasLink(this.file.name) - - 'type = "meeting"' + and: + - file.hasLink(this.file.name) + - 'type = "meeting"' ``` -Embed this Base in Sarah's person note and it renders a table of every meeting note that links to her, filterable and sortable. This uses Obsidian's built-in Bases feature — no community plugins needed. +Embed this Base in Sarah's person note and it renders a table of every meeting note that links to her, filterable and sortable. This uses Obsidian's built-in Bases feature, no community plugins needed. ### Step 6: Create meeting notes fast from your daily note -![Daily note with wikilinks to today's meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-4-1.png "char-editor-width=80") +![Daily note with wikilinks to today's meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-4-1.png "anarlog-editor-width=80") If you use Obsidian's Daily Notes plugin, you probably want to create meeting notes from there. The [Daily Note to Meeting Note thread](https://forum.obsidian.md/t/from-daily-note-create-a-new-note-with-a-template/87160) shows how to set up a one-click button using Meta Bind and Templater that: @@ -173,7 +173,7 @@ This is where the system pays off. Every query below has been tested in a live O Sarah's open action items: -![search results for open tasks in obsidian](/api/assets/blog/articles/obsidian-meeting-notes/image-5-1.png "char-editor-width=80") +![search results for open tasks in obsidian](/api/assets/blog/articles/obsidian-meeting-notes/image-5-1.png "anarlog-editor-width=80") ``` task-todo:"Sarah Chen" @@ -183,7 +183,7 @@ Finds every incomplete task across your vault that mentions Sarah by name. Retur All meetings tagged payments: -![search filtering in obsidian](/api/assets/blog/articles/obsidian-meeting-notes/image-6-1.png "char-editor-width=80") +![search filtering in obsidian](/api/assets/blog/articles/obsidian-meeting-notes/image-6-1.png "anarlog-editor-width=80") ``` path:Meetings [tags:payments] @@ -193,7 +193,7 @@ Finds meeting notes where the tags property includes "payments." Returns only fi Every meeting where PCI audit came up: -![Obsidian search for a topic across all meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-7-1.png "char-editor-width=80") +![Obsidian search for a topic across all meeting notes](/api/assets/blog/articles/obsidian-meeting-notes/image-7-1.png "anarlog-editor-width=80") ``` path:Meetings "PCI audit" @@ -203,17 +203,17 @@ Finds every mention of "PCI audit" across all meeting notes, with surrounding co All March meetings: -![Obsidian search filtering notes by date property](/api/assets/blog/articles/obsidian-meeting-notes/image-8-1.png "char-editor-width=80") +![Obsidian search filtering notes by date property](/api/assets/blog/articles/obsidian-meeting-notes/image-8-1.png "anarlog-editor-width=80") ``` [date:/2026-03/] ``` -Uses regex on the date property to find all notes with a March 2026 date. Works for any month — change the pattern to match what you need. +Uses regex on the date property to find all notes with a March 2026 date. Works for any month: change the pattern to match what you need. Everyone at a specific company: -![Obsidian property search across person notes](/api/assets/blog/articles/obsidian-meeting-notes/image-9-1.png "char-editor-width=80") +![Obsidian property search across person notes](/api/assets/blog/articles/obsidian-meeting-notes/image-9-1.png "anarlog-editor-width=80") ``` [company:Acme] @@ -221,40 +221,40 @@ Everyone at a specific company: Searches the `company` property across person notes. Useful when you're preparing for a client meeting and want to see all your contacts there. -### Step 8: Add Char for real-time transcription and AI notes +### Step 8: Add Anarlog for real-time transcription and AI notes Everything up to this point relies on what you managed to type. In a 30-minute meeting, that's maybe 15% of what was said. The decision that was made at minute 22. The exact phrasing someone used when they pushed back on a deadline. The side comment that turns out to be the most important thing said all week. It's gone unless you happened to write it down. -You need transcription. And if you're the kind of person who chose Obsidian — you chose it because your files live on your machine, in a format you control, with no lock-in — you need transcription that works the same way. +You need transcription. And if you're the kind of person who chose Obsidian (you chose it because your files live on your machine, in a format you control, with no lock-in) you need transcription that works the same way. Most meeting transcription tools don't. They store your data on their servers. They require monthly subscriptions. They join your call as a bot. They lock your transcripts in a proprietary format you can't move. -This is exactly the problem Char solves. +This is exactly the problem Anarlog solves. -Char is an open-source AI notepad for meetings. Here's what it does and why it fits this system: +Anarlog is an open-source AI notepad for meetings. Here's what it does and why it fits this system: -![Char obsidian integration](/api/assets/blog/articles/obsidian-meeting-notes/image-10-1.png "char-editor-width=80") +![Anarlog obsidian integration](/api/assets/blog/articles/obsidian-meeting-notes/image-10-1.png "anarlog-editor-width=80") -You point the output folder at your vault. This is the key. Set Char's output folder to your `Meetings/` folder (or a subfolder like `Meetings/Transcripts/`). When Char finishes processing a meeting, the transcript and summary appear in your vault. Obsidian indexes them instantly. They're searchable, linkable, part of your system. No export. No copy-paste. +You point the output folder at your vault. This is the key. Set Anarlog's output folder to your `Meetings/` folder (or a subfolder like `Meetings/Transcripts/`). When Anarlog finishes processing a meeting, the transcript and summary appear in your vault. Obsidian indexes them instantly. They're searchable, linkable, part of your system. No export. No copy-paste. -It records via system audio. No bot joins your Zoom. No calendar permissions. Char captures what your computer hears, which means it works with Zoom, Teams, Google Meet, phone calls, and in-person conversations with a mic. Nobody in the meeting knows it's running. +It records via system audio. No bot joins your Zoom. No calendar permissions. Anarlog captures what your computer hears, which means it works with Zoom, Teams, Google Meet, phone calls, and in-person conversations with a mic. Nobody in the meeting knows it's running. -It transcribes in real time and generates structured summaries. Char combines your notes with the full transcript and produces a summary using AI. The summary lands as a file on your computer. +It transcribes in real time and generates structured summaries. Anarlog combines your notes with the full transcript and produces a summary using AI. The summary lands as a file on your computer. -You choose the AI. Use Char's managed cloud service if you want simplicity. Bring your own API keys from OpenAI, Deepgram, or Anthropic if your company already has them. Run models locally through Ollama or LM Studio if nothing leaves your machine. Your security team can audit the code. You're not trusting a black box with your meeting recordings. +You choose the AI. Use a managed cloud option if you want simplicity. Bring your own API keys from OpenAI, Deepgram, or Anthropic if your company already has them. Run models locally through Ollama or LM Studio if nothing leaves your machine. Your security team can audit the code. You're not trusting a black box with your meeting recordings. -There's no lock-in. Every file Char produces is a file on your computer. Stop using Char and every transcript is still there, in your vault, working. There's nothing to export because nothing was ever locked up. +There's no lock-in. Every file Anarlog produces is a file on your computer. Stop using Anarlog and every transcript is still there, in your vault, working. There's nothing to export because nothing was ever locked up. -If you use Obsidian, you already made this choice once: files on your machine, open format, your control. Char is the same choice applied to meeting recordings. +If you use Obsidian, you already made this choice once: files on your machine, open format, your control. Anarlog is the same choice applied to meeting recordings. ## Your Obsidian Meeting Notes Workflow Monday morning. You open Obsidian. 1. You check your calendar and see three meetings today. From your daily note, you create a meeting note for the first one: `2026-04-07 Sprint Planning`. -2. The meeting starts. You open Char. It starts recording system audio. You take notes in Obsidian: quick bullets, [[wikilinks]] for people and topics as they come up. -3. The meeting ends. You stop Char. It processes the recording and drops a transcript and structured summary into your vault. You pull the key decisions and action items to the top of your meeting note. +2. The meeting starts. You open Anarlog. It starts recording system audio. You take notes in Obsidian: quick bullets, [[wikilinks]] for people and topics as they come up. +3. The meeting ends. You stop Anarlog. It processes the recording and drops a transcript and structured summary into your vault. You pull the key decisions and action items to the top of your meeting note. 4. Before your 1:1 with Sarah that afternoon, you open [[Sarah Chen]] and glance at backlinks. You can see every meeting she's been in this month, what she committed to, what's still open. You search task-todo:"Sarah Chen" and it shows rwo open items from last week. 5. In the 1:1, you reference the specific thing she said in the sprint planning meeting. You know the exact phrasing because it's in the transcript. You're not guessing. You're not paraphrasing from memory. You're reading her words back from a file in your vault. -That's the system. Templates give you structure. Links connect everything. Backlinks surface history. Search finds anything. Char fills the gap between what you typed and what was actually said. [Download Char now](https://char.com/download/apple-silicon) and try it on your next meeting. +That's the system. Templates give you structure. Links connect everything. Backlinks surface history. Search finds anything. Anarlog fills the gap between what you typed and what was actually said. [Download Anarlog now](https://anarlog.so/download/apple-silicon) and try it on your next meeting. diff --git a/apps/web/content/articles/open-source-meeting-transcription-software.mdx b/apps/web/content/articles/open-source-meeting-transcription-software.mdx index d2a3dde5cd..5d0c4e5c1d 100644 --- a/apps/web/content/articles/open-source-meeting-transcription-software.mdx +++ b/apps/web/content/articles/open-source-meeting-transcription-software.mdx @@ -4,7 +4,6 @@ display_title: "Open Source Alternatives to Otter AI and Fireflies" meta_description: "Review of the best open source meeting transcription tools you can fork, self-host, and customize. Compare features, pricing, and deployment options." author: - "Harshika" -coverImage: "/api/assets/blog/open-source-meeting-transcription-software/cover.png" featured: true category: "Comparisons" date: "2025-10-23" @@ -12,42 +11,40 @@ date: "2025-10-23" Looking for meeting transcription tools that you can fork, fix, and make your own? This article reviews three open-source options that give you complete control over your meeting data. -We'll compare Char, Meetily, and Amical—the only three open-source meeting transcription tools that let you inspect every line of code, modify any component, and own your data forever. +We'll compare Anarlog, Meetily, and Amical—the only three open-source meeting transcription tools that let you inspect every line of code, modify any component, and own your data forever. ## What are the best open-source meeting transcription software? - -| Feature | Char | Meetily | Amical | +| Feature | Anarlog | Meetily | Amical | | ---------------------- | ----------------------------------- | ------------------------- | ---------------------------- | -| **Best For** | Control over data and AI stack | Pure transcription needs | Hands-free typing everywhere | -| **Transcription** | Real-time via Whisper | Real-time via Whisper.cpp | Real-time via Whisper | -| **Platforms** | macOS (Windows/Linux Q2 2026) | Windows, macOS, Linux | macOS (Windows in progress) | -| **License** | MIT | MIT | MIT | -| **Meeting Support** | All platforms, no bots | All platforms, no bots | Live meeting transcription | -| **Local Models** | HyperLLM-V1, Whisper | Whisper.cpp, Ollama | Ollama support | -| **Cloud Options** | OpenAI, Gemini, Claude, custom APIs | Claude, Groq, OpenAI | OpenAI, custom APIs | -| **Individual Use** | Free forever | Free (MIT license) | Free (MIT license) | -| **Organization Plans** | Pro ($8), Enterprise (custom) | Custom (2-100 users) | None currently | -| **GitHub Stars** | 6.4k+ | 7.8k+ | 96 | - +| **Best For** | Control over data and AI stack | Pure transcription needs | Hands-free typing everywhere | +| **Transcription** | Real-time via Whisper | Real-time via Whisper.cpp | Real-time via Whisper | +| **Platforms** | macOS (Windows/Linux Q2 2026) | Windows, macOS, Linux | macOS (Windows in progress) | +| **License** | MIT | MIT | MIT | +| **Meeting Support** | All platforms, no bots | All platforms, no bots | Live meeting transcription | +| **Local Models** | HyperLLM-V1, Whisper | Whisper.cpp, Ollama | Ollama support | +| **Cloud Options** | OpenAI, Gemini, Claude, custom APIs | Claude, Groq, OpenAI | OpenAI, custom APIs | +| **Individual Use** | Free forever | Free (MIT license) | Free (MIT license) | +| **Organization Plans** | Pro ($8), Enterprise (custom) | Custom (2-100 users) | None currently | +| **GitHub Stars** | 6.4k+ | 7.8k+ | 96 | ## Top open-source meeting transcription software: detailed reviews -### 1. Char +### 1. Anarlog -[Char](/) is an open-source AI notepad for meetings that gives you complete control over your data and AI stack. Everything is stored as plain markdown files. +[Anarlog](/) is an open-source AI notepad for meetings that gives you full control over your data and AI stack. Everything is stored as plain markdown files. The interface works like Apple Notes. You open it and start typing. No list of past meetings to navigate, no calendar integration. Just a blank canvas ready to use. -While you're writing, Char listens to both your microphone and system audio. Once the meeting ends, it enhances your manual notes into actionable AI summaries based on your chosen template. +While you're writing, Anarlog listens to both your microphone and system audio. Once the meeting ends, it enhances your manual notes into actionable AI summaries based on your chosen template. **Available as:** Native Mac app (desktop application, Windows and Linux coming in Q2 2026) -![Char](/api/assets/blog/open-source-meeting-transcription-software/hyprnote.webp) +![Anarlog](/api/assets/blog/open-source-meeting-transcription-software/anarlog.webp) #### **Top Features** -- Multiple deployment options: keep everything on your device, deploy on your own servers, or use the managed cloud service +- Several deployment options: keep everything on your device, deploy on your own servers, or use the managed cloud service - Real-time transcription with no bots or calendar permissions required - Works with Zoom, Teams, Google Meet, and in-person meetings without any setup - Your choice of AI: managed cloud, bring your own API keys (OpenAI, Anthropic, Mistral, and others), or run local models via Ollama or LM Studio @@ -73,13 +70,13 @@ While you're writing, Char listens to both your microphone and system audio. Onc #### Pricing -Free forever for local transcription, BYOK, and all core features. Managed cloud service is $25/month for the easiest setup. Enterprise offers on-premises deployment. +Free forever for local transcription, BYOK, and all core features. Enterprise offers on-premises deployment.   ### 2. Meetily -[Meetily](https://meetily.zackriya.com/) is a privacy-first AI meeting assistant that runs entirely on your local machine. Unlike Char, which combines note-taking with transcription, Meetily focuses on recording, transcribing, and summarizing meetings. +[Meetily](https://meetily.zackriya.com/) is a privacy-first AI meeting assistant that runs entirely on your local machine. Unlike Anarlog, which combines note-taking with transcription, Meetily focuses on recording, transcribing, and summarizing meetings. **Available as:** Native apps for Windows, macOS, and Linux @@ -92,7 +89,7 @@ Free forever for local transcription, BYOK, and all core features. Managed cloud - **Dual audio capture** records both microphone and system audio simultaneously with intelligent ducking and clipping prevention. - **GPU acceleration support** automatically detects and uses hardware acceleration: Metal/CoreML on macOS, CUDA for NVIDIA, Vulkan for AMD/Intel. - **Semantic search via ChromaDB** enables searching by concepts and related discussions, not just exact keywords. -- **Universal meeting platform support** works with Zoom, Teams, Google Meet, Webex, and any platform without requiring bots. +- **Wide meeting platform support** works with Zoom, Teams, Google Meet, Webex, and any platform without requiring bots. - **SQLite local storage** keeps all data locally in a lightweight database that's easy to back up and migrate. - **REST API access** exposes endpoints for integrating with other tools and workflows. @@ -147,7 +144,7 @@ Free for individuals under MIT license. Organizational (2-100 users) and enterpr #### Cons - In active development toward first stable release—expect bugs -- Meeting transcription features less mature than Char or Meetily +- Meeting transcription features less mature than Anarlog or Meetily - macOS is the primary focus; Windows support exists but less polished - App not notarized, causing security warnings - Smaller community and contributor base (96 GitHub stars vs 6k-8k for others) @@ -158,25 +155,25 @@ Completely free with no paid tiers. ## Which open-source transcription tool should you choose? -For most developers and privacy-conscious professionals, **Char** stands out. It delivers zero lock-in with complete control over your data and AI stack. Plain markdown files work with any tool, you choose your AI provider, and the manual note integration gives you control over what the AI focuses on. +For most developers and privacy-conscious professionals, **Anarlog** stands out. It delivers zero lock-in with full control over your data and AI stack. Plain markdown files work with any tool, you choose your AI provider, and the manual note integration gives you control over what the AI focuses on. The code is open source under the MIT license, so you can verify exactly how your data is processed. Need enterprise deployment? Self-host it on your own infrastructure. Want to modify the summarization logic? Fork it and make it yours. Setup is straightforward. No complex configuration, no security warnings to bypass, no hidden dependencies. -Ready to try it? [Download Char for macOS](/download) or [check out the source code on GitHub](https://github.com/fastrepl/char). +Ready to try it? [Download Anarlog for macOS](https://anarlog.so) or [check out the source code on GitHub](https://github.com/fastrepl/anarlog). ## Frequently asked questions ### 1. Are open-source transcription tools really free? -Yes, all three are open source. Char and Amical are [free](/blog/free-transcription-software/) for all users. Char is free forever for local transcription and BYOK. The managed cloud service is $25/month, and Enterprise tier has custom pricing. +Yes, all three are open source. Anarlog and Amical are [free](/blog/free-transcription-software/) for all users. Anarlog is free forever for local transcription and BYOK. Meetily is free for individual users under the MIT license. Organizations (2-100 users) and enterprises (100+ users) can request custom plans, but pricing isn't publicly listed. ### 2. Do I need a powerful computer to run local transcription software? -You need a reasonably modern machine. An M-series Mac works best for Char, though Intel Macs function with reduced performance. Meetily and Amical have similar requirements—at least 8GB RAM (16GB recommended) and a decent CPU. GPU acceleration helps but isn't required for basic use. +You need a reasonably modern machine. An M-series Mac works best for Anarlog, though Intel Macs function with reduced performance. Meetily and Amical have similar requirements—at least 8GB RAM (16GB recommended) and a decent CPU. GPU acceleration helps but isn't required for basic use. ### 3. Can I use these tools offline? @@ -188,8 +185,8 @@ With larger Whisper models, accuracy typically reaches 90-95% for clear English ### 5. What about HIPAA, GDPR, and compliance? -Because processing happens locally by default, these tools simplify compliance significantly. Your data doesn't leave your device, so you're not transmitting protected information to third-party processors. This makes HIPAA, GDPR, SOC 2, and other frameworks much easier to satisfy. +Because processing happens locally by default, these tools simplify compliance. Your data doesn't leave your device, so you're not transmitting protected information to third-party processors. This makes HIPAA, GDPR, SOC 2, and other frameworks easier to satisfy. -Organizations needing formal compliance documentation can use Char or Meetily's enterprise plans, which include dedicated support. +Organizations needing formal compliance documentation can use Anarlog or Meetily's enterprise plans, which include dedicated support.   diff --git a/apps/web/content/articles/openrouter-data-retention-policy.mdx b/apps/web/content/articles/openrouter-data-retention-policy.mdx index 3601f4da8d..be8ad613bf 100644 --- a/apps/web/content/articles/openrouter-data-retention-policy.mdx +++ b/apps/web/content/articles/openrouter-data-retention-policy.mdx @@ -24,7 +24,7 @@ Your actual conversation content is not retained by OpenRouter unless you specif OpenRouter offers a 1% discount on usage costs in exchange for enabling prompt logging. If you turn this on, OpenRouter stores your prompts and completions. -There is a significant term attached to this. [OpenRouter's privacy policy](https://openrouter.ai/privacy) states that enabling prompt logging grants OpenRouter an irrevocable right to commercial use of those inputs and outputs. That language is broader than most providers use. It means logged data could be used for purposes beyond just displaying your history in the dashboard. +There is a notable term attached to this. [OpenRouter's privacy policy](https://openrouter.ai/privacy) states that enabling prompt logging grants OpenRouter an irrevocable right to commercial use of those inputs and outputs. That language is broader than most providers use. It means logged data could be used for purposes beyond just displaying your history in the dashboard. If you are evaluating OpenRouter for any use case involving confidential information, sensitive conversations, or regulated data, do not enable prompt logging. The 1% discount is not worth the data rights you are granting. @@ -38,7 +38,7 @@ This gives you granular control if you need it. For most use cases, simply not e This is where OpenRouter's data policy gets more complicated. -When you send a request through OpenRouter, it routes that request to a provider. That provider processes your prompt under their own data retention policy. OpenRouter does not change what the provider stores. If your request goes to OpenAI's API, [OpenAI's 30-day abuse monitoring window](https://char.com/blog/chatgpt-data-retention-policy/) applies. If it goes to Anthropic's API, [Anthropic's 7-day window](https://char.com/blog/anthropic-data-retention-policy/) applies. +When you send a request through OpenRouter, it routes that request to a provider. That provider processes your prompt under their own data retention policy. OpenRouter does not change what the provider stores. If your request goes to OpenAI's API, [OpenAI's 30-day abuse monitoring window](https://anarlog.so/blog/chatgpt-data-retention-policy/) applies. If it goes to Anthropic's API, [Anthropic's 7-day window](https://anarlog.so/blog/anthropic-data-retention-policy/) applies. OpenRouter displays the data retention policy for each provider endpoint in its interface. Before routing sensitive data through a model, check the provider policy shown for that endpoint. @@ -54,7 +54,7 @@ EU routing is not enabled by default and requires an enterprise account configur OpenRouter does not publish a HIPAA Business Associate Agreement. If your use case involves protected health information, OpenRouter is not the right routing layer without a BAA in place. The same applies to other regulated data categories. -For compliance-sensitive deployments, the multi-provider nature of OpenRouter adds complexity. You are not just evaluating OpenRouter's compliance posture but also the compliance posture of every provider your requests might be routed to. Unless you lock routing to a specific provider with known compliance certifications, the compliance surface is difficult to bound. +For compliance-sensitive deployments, the multi-provider nature of OpenRouter adds complexity. You are not just evaluating OpenRouter's compliance posture but also the compliance posture of every provider your requests might be routed to. Unless you lock routing to a specific provider with known compliance certifications, the compliance surface is hard to bound. ## **When OpenRouter Works for Sensitive Work and When It Does Not** @@ -62,14 +62,14 @@ OpenRouter's value is flexibility and cost optimization across providers. For no For sensitive work, the two-layer data question requires more active management. You need to be specific about which provider your requests go to, verify that provider's data policy, confirm that OpenRouter ZDR is enabled, and ensure prompt logging is off. That is manageable, but it requires deliberate configuration rather than relying on defaults. -The defaults at OpenRouter are reasonably privacy-conscious. Prompts are not logged out of the box. But the flexibility that makes OpenRouter useful also makes its data handling harder to audit than a direct provider relationship. +The defaults at OpenRouter are reasonably privacy-conscious. Prompts are not logged out of the box. But the same flexibility that makes OpenRouter useful also makes its data handling harder to audit than a direct provider relationship. -## Use OpenRouter to Take AI Notes Through Char +## Use OpenRouter to Take AI Notes Through Anarlog -Char supports OpenRouter as an API provider option. When you bring your own OpenRouter API key, your meeting data routes through OpenRouter under your account settings. +Anarlog supports OpenRouter as an API provider option. When you bring your own OpenRouter API key, your meeting data routes through OpenRouter under your account settings. -If you configure OpenRouter with ZDR enabled and prompt logging off, those settings apply to requests Char sends through your account. If you have locked routing to a specific provider within OpenRouter, requests from Char will follow that configuration. +If you configure OpenRouter with ZDR enabled and prompt logging off, those settings apply to requests Anarlog sends through your account. If you have locked routing to a specific provider within OpenRouter, requests from Anarlog will follow that configuration. -As with all Char integrations, your meeting notes are stored on your device. The AI provider processes your data to generate a summary, but the output lives locally. Switching from OpenRouter to a direct provider relationship, or to a local model, does not require any changes to how your notes are organized. +As with all Anarlog integrations, your meeting notes are stored on your device. The AI provider processes your data to generate a summary, but the output lives locally. Switching from OpenRouter to a direct provider relationship, or to a local model, does not require any changes to how your notes are organized. -[Download Char for MacOS](https://char.com/download) and use the AI provider your security team actually approves. \ No newline at end of file +[Download Anarlog for MacOS](https://anarlog.so/download) and use the AI provider your security team actually approves. \ No newline at end of file diff --git a/apps/web/content/articles/organize-meeting-notes.mdx b/apps/web/content/articles/organize-meeting-notes.mdx index 9d29a8c42d..e21127d958 100644 --- a/apps/web/content/articles/organize-meeting-notes.mdx +++ b/apps/web/content/articles/organize-meeting-notes.mdx @@ -15,17 +15,17 @@ Most people capture meeting notes fine. Finding them three weeks later is the pr ### **Use Your Meeting Assistant's Built-In Features** -Before building any system on top, most people underuse what their note-taking app already gives them. Conversational search. Summaries that pull out decisions and action items. Templates for recurring meetings. Char, Otter, Granola, Fireflies - all of them have this to some degree. +Before building any system on top, most people underuse what their note-taking app already gives them. Conversational search. Summaries that pull out decisions and action items. Templates for recurring meetings. Anarlog, Otter, Granola, Fireflies - all of them have this to some degree. -Start there. Search is good enough now that a lot of organizational overhead simply isn't necessary anymore. Most things you'd want to find are one query away. +Start there. Search is good enough now that most organizational overhead isn't necessary. Most things you'd want to find are one query away. -### **If You're Using Char, You're Already at an Advantage** +### **If You're Using Anarlog, You're Already at an Advantage** With most meeting note-takers, you're renting space in their database. Switch tools and getting your notes out cleanly is harder than it should be. -Char, on the other hand, saves your notes as plain markdown files on your own device. That one difference opens up a lot. +Anarlog, on the other hand, saves your notes as plain markdown files on your own device. That one difference opens up a lot. -Markdown files are just text. Any tool can read them, which means your meeting history isn't locked in a platform. The files travel with you and work with anything, regardless of what Char does in the future. +Markdown files are just text. Any tool can read them, which means your meeting history isn't locked in a platform. The files travel with you and work with anything, regardless of what Anarlog does in the future. In practice that means you can open your notes folder directly in Obsidian and start linking meetings to projects and people. Drop the same folder into Logseq if you prefer a more task-and-outline-focused workflow. Open it in VS Code and run search across hundreds of files in seconds. Point Claude at the folder to organize it how you'd like. diff --git a/apps/web/content/articles/otter-ai-alternatives.mdx b/apps/web/content/articles/otter-ai-alternatives.mdx index 5420acc0e0..1d3363c744 100644 --- a/apps/web/content/articles/otter-ai-alternatives.mdx +++ b/apps/web/content/articles/otter-ai-alternatives.mdx @@ -2,35 +2,34 @@ meta_title: "14 Best Otter.ai Alternatives & Competitors in 2026" meta_description: "Discover 14 Otter AI alternatives that solve privacy, language, and accuracy issues. Compare features and pricing to find your perfect meeting assistant." author: "John Jeong" -coverImage: "/api/assets/blog/otter-ai-alternatives/cover.png" category: "Comparisons" date: "2025-08-11" --- -While [Otter.ai](/blog/otter-ai-review/) has established itself as a popular meeting transcription tool, teams that prioritize data privacy and security often find it unsuitable for their needs. +[Otter.ai](/blog/otter-ai-review/) is a popular meeting transcription tool, but teams that prioritize data privacy and security often find it unsuitable. -If you need better accuracy, offline capabilities, or want to keep sensitive conversations off cloud servers, several alternatives are worth evaluating. +If you need better accuracy, offline capabilities, or want to keep sensitive conversations off cloud servers, several alternatives are worth a look. -This guide reviews 14 Otter AI alternatives, from zero-lock-in open-source solutions to enterprise platforms with advanced analytics. +This guide reviews 14 Otter AI alternatives, from zero-lock-in open-source tools to enterprise platforms with analytics. ## Quick comparison: top Otter AI alternatives -| Tool | Best For | Starting Price (monthly) | Key Advantage | +| Tool | Best For | Starting Price (monthly) | Key Advantage | | ------------ | ----------------------- | ------------------------------------------------------------------ | ------------------------------------------------- | -| Char | Zero lock-in, complete control | Free forever (local + BYOK). Managed cloud $25/month. | Open source. Plain markdown files. Your choice of AI. | -| Fireflies.ai | Team collaboration | $18/user/month | 69+ languages supported | -| Rev | High accuracy needs | Pay-per-use: $0.25/min | Human + AI transcription | -| Fathom | Sales teams | $19/user/month | CRM integrations | -| Notta | Multilingual teams | $13.49/user/month | 58 languages supported | -| MeetGeek | Analytics focus | $19/user/month | Advanced meeting insights | -| Tactiq | Chrome users | $12/user/month | Browser extension | -| Dialpad | Communication platforms | $27/user/month | Unified communications | -| Avoma | Revenue teams | $29/user/month | Conversation intelligence | -| Grain | Video-first teams | $19/user/month | Video highlights | -| Sembly AI | Workflow Integration | $15/user/month | AI task extraction | -| Descript | Content creators | $24/user/month | Audio/video editing | -| Jamie AI | Bot-free EU teams | €24/user/month | GDPR compliance & offline capability | -| tl;dv | Video-first teams | $29/user/month | Unlimited video recordings & clips | +| Anarlog | Zero lock-in, complete control | Free forever, fully local + BYOK.nth. | Open source. Plain markdown files. Your choice of AI. | +| Fireflies.ai | Team collaboration | $18/user/month | 69+ languages supported | +| Rev | High accuracy needs | Pay-per-use: $0.25/min | Human + AI transcription | +| Fathom | Sales teams | $19/user/month | CRM integrations | +| Notta | Multilingual teams | $13.49/user/month | 58 languages supported | +| MeetGeek | Analytics focus | $19/user/month | Advanced meeting insights | +| Tactiq | Chrome users | $12/user/month | Browser extension | +| Dialpad | Communication platforms | $27/user/month | Unified communications | +| Avoma | Revenue teams | $29/user/month | Conversation intelligence | +| Grain | Video-first teams | $19/user/month | Video highlights | +| Sembly AI | Workflow Integration | $15/user/month | AI task extraction | +| Descript | Content creators | $24/user/month | Audio/video editing | +| Jamie AI | Bot-free EU teams | €24/user/month | GDPR compliance & offline capability | +| tl;dv | Video-first teams | $29/user/month | Unlimited video recordings & clips | ## How we selected these alternatives @@ -40,9 +39,9 @@ The most common pain points we found include: ### 1. Privacy and data control concerns -[Otter.ai's privacy policy](https://otter.ai/privacy-policy) indicates they transfer data across international borders to partners and service providers, subjecting conversations to different privacy laws and regulatory frameworks depending on where those partners operate. +[Otter.ai's privacy policy](https://otter.ai/privacy-policy) says they transfer data across international borders to partners and service providers. That means your conversations fall under different privacy laws depending on where those partners operate. -This creates compliance complexity: organizations must ensure adherence to privacy regulations across multiple countries rather than maintaining local control. +Compliance gets messy fast. You're tracking privacy rules across multiple countries instead of keeping things local. Otter also shares data with third-party vendors and uses customer conversations for AI model training, even when de-identified. @@ -52,7 +51,7 @@ Also read about [Otter AI's recent class-action lawsuit and privacy concerns](/b ### 2. Bot dependence and intrusiveness -Otter.ai requires meeting bots to join Zoom, Teams, or Google Meet calls to record and transcribe. Many users find this intrusive—especially in client-facing meetings where an unfamiliar "bot participant" suddenly appears. +Otter.ai sends a bot into Zoom, Teams, or Google Meet to record and transcribe. Users find this intrusive, especially in client-facing meetings where an unfamiliar "bot participant" shows up uninvited. Bot-based assistants also create compliance hurdles in finance and healthcare, where explicit participant consent is mandatory. @@ -67,46 +66,46 @@ Source: [G2](https://www.g2.com/products/otter-ai/reviews/otter-ai-review-986071 ### 4. Language support gaps -[International teams](https://cybernews.com/ai-tools/otter-ai-review/) report significant limitations with Otter AI's support for only English, Spanish, and French, compared to competitors offering 40+ languages. Global organizations with diverse linguistic needs find this restrictive. +[International teams](https://cybernews.com/ai-tools/otter-ai-review/) hit a wall with Otter AI's three-language support (English, Spanish, French) when competitors offer 40+. Global organizations find this too narrow. ### 5. Internet dependency -Teams working in locations with poor connectivity or needing offline capabilities find Otter AI's requirement for stable internet connection for live transcription limiting. +Otter AI needs a stable internet connection for live transcription. Teams in locations with poor connectivity or anyone needing offline capabilities are out of luck. does otter ai work offline Source: [G2](https://www.g2.com/products/otter-ai/reviews/otter-ai-review-10454595) ### 6. Cost escalation for teams -Advanced features are locked behind higher-tier subscriptions. Organizations with heavy usage requirements report costs escalating quickly. +Advanced features are locked behind higher-tier subscriptions. Heavy users report costs escalating fast. otter ai cost Source: [G2](https://www.g2.com/products/otter-ai/reviews/otter-ai-review-10454595) ### 7. Accuracy and speaker identification issues -Speaker identification accuracy issues become problematic in multi-speaker environments where precise attribution is critical for follow-up actions and accountability. +Speaker identification breaks down in multi-speaker meetings, where accurate attribution drives follow-ups and accountability. otter ai accuracy issues Source: [G2](https://www.g2.com/products/otter-ai/reviews/otter-ai-review-10454595) ## The 14 best Otter.ai alternatives -### 1. Char - Best for Zero Lock-in and Complete Control +### 1. Anarlog - Best for Zero Lock-in and Complete Control -Char +Anarlog #### How it compares to Otter AI: -[Char](/) is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. +[Anarlog](/) is an open-source AI notepad for meetings, built for people who want full control over their data and AI stack. -The interface resembles Apple Notes, and everything is stored as plain markdown files on your device—not in proprietary databases. No bots joining your calls, no vendor lock-in. +The interface resembles Apple Notes. Everything is stored as plain markdown files on your device, not in a proprietary database. No bots joining your calls, no vendor lock-in. -Launch a meeting and the AI assistant transcribes speech in real-time. When the meeting ends, it presents a structured summary with key decisions, action items, and discussion themes automatically extracted. +Start a meeting and the AI transcribes in real-time. When it ends, you get a structured summary with decisions, action items, and discussion themes pulled out automatically. -You choose your AI stack: use the managed cloud service, bring your own API keys (OpenAI, Deepgram, Anthropic), or run local models via Ollama. Otter AI locks you into their cloud with no way to switch. +You pick your AI stack: managed cloud, your own API keys (OpenAI, Deepgram, Anthropic), or local models via Ollama. Otter AI locks you into its cloud with no way to switch. -You trade Otter's real-time collaboration features for zero lock-in, true file ownership, and complete control over your workflow. Your notes work with any tool—Obsidian, Notion, VS Code—because they're just markdown files. +You give up Otter's real-time collaboration features in exchange for zero lock-in and full file ownership. Your notes work with any tool — Obsidian, Notion, VS Code — because they're just markdown files. #### Key advantages over Otter AI: @@ -129,9 +128,9 @@ You trade Otter's real-time collaboration features for zero lock-in, true file o #### How it compares to Otter AI: -[Fireflies](https://fireflies.ai/) addresses Otter.ai's language limitations. Otter AI supports only 3 languages, making it unsuitable for global organizations. Fireflies offers 69+ languages while also providing the advanced conversation intelligence that Otter AI lacks. +[Fireflies](https://fireflies.ai/) covers Otter.ai's language gap. Otter supports 3 languages; Fireflies handles 69+, plus deeper conversation intelligence Otter lacks. -Both tools use meeting bots, which may concern people preferring less intrusive AI assistants. +Both tools use meeting bots, which may put off people who want a less intrusive AI assistant. #### Key advantages over Otter AI: @@ -153,9 +152,9 @@ Both tools use meeting bots, which may concern people preferring less intrusive #### How it compares to Otter AI: -[Rev](https://www.rev.com/) solves Otter AI's accuracy limitations. While Otter relies solely on AI transcription with 85% accuracy, Rev offers both AI (96%+) and human transcription (99% accuracy) options. +[Rev](https://www.rev.com/) closes Otter's accuracy gap. Otter sticks to AI transcription at 85% accuracy. Rev offers AI (96%+) or human transcription (99%). -This matters for legal, healthcare, and journalism professionals who cannot tolerate transcription errors from Otter's AI-only approach. +This matters for legal, healthcare, and journalism work where errors aren't acceptable. #### Key advantages over Otter AI: @@ -177,13 +176,13 @@ This matters for legal, healthcare, and journalism professionals who cannot tole #### How it compares to Otter AI: -[Tactiq](https://tactiq.io/) works directly in your browser rather than as a separate app. +[Tactiq](https://tactiq.io/) runs directly in your browser instead of as a separate app. -This browser-first design eliminates the need to download additional software like Otter AI requires, but creates dependency on Chrome that limits flexibility. +That removes the need to install software, but ties you to Chrome. -The privacy philosophy differs significantly—Tactiq transcribes without storing audio files, addressing privacy concerns some users have with Otter's comprehensive recording approach. +Tactiq also transcribes without storing audio files, which sidesteps privacy concerns Otter's full-recording model creates. -This privacy-focused design means sacrificing Otter's ability to playback meetings or share recordings with absent team members. +The trade-off: no playback, no sharing recordings with team members who missed the meeting. #### Key advantages over Otter AI: @@ -206,9 +205,9 @@ This privacy-focused design means sacrificing Otter's ability to playback meetin #### How it compares to Otter AI: -[Fathom](https://www.fathom.ai/) directly challenges Otter AI's freemium limitations. While Otter AI restricts free users to 300 minutes monthly and basic features, Fathom provides unlimited recording, transcription, and AI summaries at no cost. +[Fathom](https://www.fathom.ai/) takes a swing at Otter's freemium limits. Otter caps free users at 300 minutes a month with basic features. Fathom gives you unlimited recording, transcription, and AI summaries for free. -In its "Teams Edition," Fathom offers conversation intelligence features typically found in expensive tools like Gong. Otter AI remains focused on basic meeting documentation. +The "Teams Edition" includes conversation intelligence features usually found in pricier tools like Gong. Otter stays focused on basic meeting documentation. #### Key advantages over Otter AI: @@ -230,11 +229,11 @@ In its "Teams Edition," Fathom offers conversation intelligence features typical #### How it compares to Otter AI: -[Notta](https://www.notta.ai/) solves Otter's language support limitation. +[Notta](https://www.notta.ai/) covers Otter's language gap. -Otter's three-language restriction makes it unsuitable for international organizations, while Notta supports 58 languages with real-time translation capabilities. +Otter's three-language limit rules it out for international teams. Notta supports 58 languages and adds real-time translation. -The trade-off involves ecosystem maturity—Otter AI offers more established integrations and collaborative features, while Notta focuses on superior multilingual accuracy. +The trade-off is ecosystem maturity. Otter has more established integrations and collaborative features. Notta is built for multilingual accuracy. #### Key advantages over Otter AI: @@ -256,11 +255,11 @@ The trade-off involves ecosystem maturity—Otter AI offers more established int #### How it compares to Otter AI: -[MeetGeek](https://meetgeek.ai/) goes beyond Otter's basic transcription and summary by automatically understanding meeting context. +[MeetGeek](https://meetgeek.ai/) goes past Otter's basic transcription by reading meeting context automatically. -Otter AI treats every meeting the same way. MeetGeek intelligently detects whether you're in a sales call, HR interview, or team standup, then applies appropriate templates and workflows. +Otter treats every meeting the same. MeetGeek detects whether you're in a sales call, HR interview, or team standup, then applies the right template and workflow. -This contextual intelligence means less manual post-meeting work compared to Otter's generic output, though it requires more initial setup and learning to fully utilize advanced automation features. +Less manual post-meeting work, but more setup and a steeper learning curve for the automation features. #### Key advantages over Otter AI: @@ -282,11 +281,11 @@ This contextual intelligence means less manual post-meeting work compared to Ott #### How it compares to Otter AI: -[Dialpad](https://www.dialpad.com/) positions transcription as part of a complete business communications platform. Otter AI focuses solely on meeting documentation. +[Dialpad](https://www.dialpad.com/) bundles transcription into a full business communications platform. Otter sticks to meeting documentation. -Dialpad users get unified voice, video, messaging, and contact center capabilities alongside transcription, but pay significantly more than Otter's meeting-focused pricing. +Dialpad gives you unified voice, video, messaging, and contact center alongside transcription, at a much higher price than Otter. -For organizations already using multiple communication tools, Dialpad consolidates functionality that Otter AI cannot provide, though it may be overkill for teams only needing meeting transcription. +If you're already running multiple communication tools, Dialpad consolidates them. If you just need meeting transcription, it's overkill. #### Key advantages over Otter AI: @@ -308,9 +307,9 @@ For organizations already using multiple communication tools, Dialpad consolidat #### How it compares to Otter AI: -[Avoma](https://www.avoma.com/) transforms meeting transcripts into revenue intelligence. Otter.ai stops at basic documentation. +[Avoma](https://www.avoma.com/) turns meeting transcripts into revenue intelligence. Otter.ai stops at basic documentation. -Otter AI provides generic summaries. Avoma automatically identifies objections, tracks deal progression, and generates sales coaching insights. This makes Avoma invaluable for revenue teams frustrated with Otter's inability to connect meeting content to business outcomes, though the specialized focus and higher pricing make it less suitable for general meeting documentation needs. +Otter gives you generic summaries. Avoma flags objections, tracks deal progression, and generates sales coaching insights. Useful for revenue teams who need meeting content tied to business outcomes. The specialized focus and higher pricing make it a bad fit for general meeting documentation. #### Key advantages over Otter AI: @@ -332,11 +331,11 @@ Otter AI provides generic summaries. Avoma automatically identifies objections, #### How it compares to Otter AI: -[Grain](https://grain.com/) prioritizes video content creation over text transcription, making it ideal for teams who need to share meeting clips rather than read summaries. +[Grain](https://grain.com/) puts video clips ahead of text transcription. Good for teams that share clips instead of reading summaries. -Otter AI focuses on searchable text and collaborative editing. Grain excels at creating shareable video highlights with timestamps for visual learners and content creators. +Otter focuses on searchable text and collaborative editing. Grain is built for shareable video highlights with timestamps. -This video-first approach trades some of Otter's text analysis capabilities for superior visual meeting documentation and clip sharing. +You trade some text analysis depth for better video documentation and clip sharing. #### Key advantages over Otter AI: @@ -358,9 +357,9 @@ This video-first approach trades some of Otter's text analysis capabilities for #### How it compares to Otter AI: -Otter AI focuses primarily on transcription and basic note-taking. [Sembly AI](https://www.sembly.ai/) positions itself as a comprehensive meeting intelligence platform. +Otter sticks to transcription and basic note-taking. [Sembly AI](https://www.sembly.ai/) is a meeting intelligence platform. -Sembly analyzes deeper, creating structured insights, action items, and complex artifacts like project plans and requirements documents. Otter AI excels at accurate transcription but lacks Sembly's advanced document generation. +Sembly digs deeper: structured insights, action items, and complex artifacts like project plans and requirements docs. Otter is better at clean transcription but doesn't generate documents like Sembly does. #### Key advantages over Otter AI: @@ -385,9 +384,9 @@ Sembly analyzes deeper, creating structured insights, action items, and complex #### How it compares to Otter AI: -[Descript](https://www.descript.com/) combines transcription with comprehensive audio and video editing tools. Otter focuses purely on meeting documentation. Descript works better for podcast creators, video producers, and content teams who need to edit their recordings, not just transcribe them. +[Descript](https://www.descript.com/) pairs transcription with full audio and video editing. Otter sticks to meeting documentation. Descript fits podcast creators, video producers, and content teams who need to edit recordings, not just transcribe them. -This expanded functionality comes with complexity and higher pricing that exceeds what most teams need for simple meeting notes—an area where Otter AI's focused approach provides better value. +That extra functionality brings complexity and higher pricing than most teams need for simple meeting notes. Otter's focused approach is the better deal there. #### Key advantages over Otter AI: @@ -409,13 +408,13 @@ This expanded functionality comes with complexity and higher pricing that exceed #### How it compares to Otter AI: -[Jamie AI](https://www.meetjamie.ai/) addresses privacy and compliance concerns that European organizations have with US-based tools like Otter AI. +[Jamie AI](https://www.meetjamie.ai/) handles the privacy and compliance concerns European organizations raise with US-based tools like Otter. -Otter processes data in the cloud and uses meeting bots that can feel intrusive. Jamie operates without bots and stores all data within EU servers in Frankfurt with strict GDPR compliance. +Otter processes data in the cloud and uses meeting bots that can feel intrusive. Jamie skips the bots and stores all data on EU servers in Frankfurt under strict GDPR compliance. -Jamie works both online and offline, making it ideal for in-person meetings or areas with poor connectivity. Otter AI requires stable internet. +Jamie works online or offline, which fits in-person meetings and areas with poor connectivity. Otter needs stable internet. -The key trade-off is real-time collaboration—Otter AI offers live editing and team features, while Jamie focuses on post-meeting processing and individual productivity. +The trade-off is real-time collaboration. Otter has live editing and team features. Jamie is built for post-meeting processing and individual productivity. #### Key advantages over Otter AI: @@ -437,11 +436,11 @@ The key trade-off is real-time collaboration—Otter AI offers live editing and #### How it compares to Otter AI: -Otter only offers video recording on Enterprise plans. [tl;dv](https://tldv.io/) provides unlimited video recordings, transcriptions, and automated notes for free. +Otter only offers video recording on Enterprise plans. [tl;dv](https://tldv.io/) gives you unlimited video recordings, transcriptions, and automated notes for free. -tl;dv excels at creating video clips directly from transcripts and timestamped highlights. Otter AI requires users to upgrade to expensive enterprise tiers for basic video functionality. +tl;dv is built for video clips pulled directly from transcripts and timestamped highlights. Otter forces you onto an expensive enterprise tier for basic video. -Otter offers better transcript editing capabilities and mobile functionality that tl;dv currently lacks. +Otter still wins on transcript editing and mobile functionality, where tl;dv is weaker. #### Key advantages over Otter AI: @@ -458,16 +457,16 @@ Otter offers better transcript editing capabilities and mobile functionality tha - Less transcript editing capability vs. full transcript modification - Desktop-focused approach vs. cross-platform accessibility -## Why Char stands out +## Why Anarlog stands out -While each alternative has strengths, **Char** offers a unique value proposition: **zero lock-in and complete control**. +Every alternative here has strengths. **Anarlog** is the only one built around **zero lock-in and full control**. ✅ **Zero lock-in** - Plain markdown files, open source, portable format that works with any tool -✅ **Complete control** - Choose your AI stack: managed cloud, bring your own keys, or run fully local +✅ **Full control** - Choose your AI stack: managed cloud, bring your own keys, or run fully local ✅ **True ownership** - Your files live on your device, not in someone's database -✅ **Simple but powerful** - Complete control without complexity +✅ **Simple but powerful** - Full control without complexity ✅ **Cost-effective** - No recurring fees for basic functionality -Whether you're an engineer who wants files over apps, a privacy-conscious professional in regulated industries, or someone whose company has banned cloud tools like Otter—Char gives you ownership at the foundational level. +If you're an engineer who prefers files over apps, a privacy-conscious professional in a regulated industry, or someone whose company has banned cloud tools like Otter, Anarlog gives you ownership at the foundation. -**Ready to take control?** [Download Char](/download) and experience meeting notes with zero lock-in. +**Ready to take control?** [Download Anarlog](https://anarlog.so) and try meeting notes with zero lock-in. diff --git a/apps/web/content/articles/otter-ai-review.mdx b/apps/web/content/articles/otter-ai-review.mdx index 99d845fd37..cfc6dce684 100644 --- a/apps/web/content/articles/otter-ai-review.mdx +++ b/apps/web/content/articles/otter-ai-review.mdx @@ -2,16 +2,15 @@ meta_title: "Otter AI Review: Is It Worth It in 2026?" meta_description: "Complete Otter AI review covering features, pricing, security concerns, and user feedback. Compare alternatives and find the best AI notetaker for your needs." author: "Harshika" -coverImage: "/api/assets/blog/otter-ai-review/cover.png" featured: false category: "Comparisons" ready_for_review: true date: "2026-02-11" --- -After using Otter AI across 200+ meetings, including sensitive client calls and chaotic team standups, I've learned what the marketing materials won't tell you. +After using Otter AI across 200+ meetings, including sensitive client calls and chaotic team standups, I've seen what the marketing won't tell you. -Otter AI offers a free tier with significant limitations. Safety is complicated—there's a 2025 lawsuit and privacy concerns worth understanding. This review covers pricing (including hidden costs), features that actually work, transcription accuracy, privacy issues, and alternatives I've tested. +The free tier is heavily capped. Safety is complicated — there's a 2025 lawsuit and real privacy concerns worth understanding. This review covers pricing (including hidden costs), features that actually work, transcription accuracy, privacy, and alternatives I've tested. ## How does Otter AI work? @@ -20,9 +19,9 @@ Otter AI offers a free tier with significant limitations. Safety is complicated 3. It records everything and sends audio to Otter AI's cloud servers 4. You get a transcript, summary, and action items 2 hours later -I find bots intrusive. In practice, people become more guarded, speak less candidly, and conversation flows suffer. +Bots are intrusive. People get guarded, speak less candidly, and conversation suffers. -Beyond the social awkwardness is technical dependency. Otter AI's bot requires stable internet connectivity and proper permissions. I've seen it fail to join meetings due to calendar sync issues, get kicked out by overzealous hosts, or simply not work with certain enterprise meeting configurations. When the bot doesn't join, you have no transcript and no backup recording happening locally. This is why I prefer bot-free meeting assistants. +Beyond the social cost is the technical dependency. Otter's bot needs stable internet and the right permissions. I've watched it fail to join over calendar sync issues, get kicked out by hosts, or refuse to work with certain enterprise meeting configurations. When the bot doesn't join, you have no transcript and no local backup. That's why I prefer bot-free meeting assistants. ## What platforms does Otter AI support? @@ -34,52 +33,50 @@ Otter AI works with: - Mobile apps for iOS/Android (requires internet) - Browser extensions -Otter AI requires constant internet connectivity. During a venue walkthrough with spotty WiFi, it captured 12 minutes of a 45-minute conversation. There's no offline mode. +Otter needs constant internet. During a venue walkthrough with spotty WiFi, it captured 12 minutes out of 45. No offline mode. ## What languages does Otter support? -Otter AI transcribes only 3 languages: English, Spanish, and French. +Otter transcribes only 3 languages: English, Spanish, French. -For international teams, this becomes a dealbreaker. A weekly check-in with German partners means no Otter AI. A client call with Japanese stakeholders gets no transcription. +For international teams, that's a dealbreaker. A weekly check-in with German partners? No Otter. A client call with Japanese stakeholders? No transcription. ## Otter meeting assistant features & how I would grade them - -|   |   |   | +|   |   |   | | ------------------------- | --------- | -------------------------------------------- | -| **Feature** | **Grade** | **Key Takeaway** | -| Real-Time Transcription | B- | Works but inaccurate with accents/noise | -| Meeting Summary | B+ | Useful but generic for technical content | -| Slide/Screen Capture | A- | Brilliant feature, just watch your screen | -| Otter Chat / AI Assistant | C+ | Handles basics, fails at complexity | -| Speaker Identification | D | Consistently inaccurate, needs heavy editing | -| Channels & Workspaces | B | Functional organization, nothing special | -| Third-Party Integrations | B- | Surface-level, not deep automation | -| Calendar Integration | A | Flawless auto-join functionality | -| Mobile App | B- | Functional but requires internet | -| Search & Playback | B+ | Good core search, missing advanced filters | -| File Upload | C+ | Artificial limits to push upgrades | -| Export Options | B | Covers basics, missing customization | -| Vocabulary Customization | B | Helps with jargon, should be smarter | -| Collaboration | C+ | Basic comments, poor integration | -| Admin Controls | B | Standard features, nothing innovative | - +| **Feature** | **Grade** | **Key Takeaway** | +| Real-Time Transcription | B- | Works but inaccurate with accents/noise | +| Meeting Summary | B+ | Useful but generic for technical content | +| Slide/Screen Capture | A- | Brilliant feature, just watch your screen | +| Otter Chat / AI Assistant | C+ | Handles basics, fails at complexity | +| Speaker Identification | D | Consistently inaccurate, needs heavy editing | +| Channels & Workspaces | B | Functional organization, nothing special | +| Third-Party Integrations | B- | Surface-level, not deep automation | +| Calendar Integration | A | Flawless auto-join functionality | +| Mobile App | B- | Functional but requires internet | +| Search & Playback | B+ | Good core search, missing advanced filters | +| File Upload | C+ | Artificial limits to push upgrades | +| Export Options | B | Covers basics, missing customization | +| Vocabulary Customization | B | Helps with jargon, should be smarter | +| Collaboration | C+ | Basic comments, poor integration | +| Admin Controls | B | Standard features, nothing innovative | ### 1. Real-time transcription (Grade: B-) -Live transcription appears within 2-3 seconds of speech, which helps when following fast-paced discussions. Accuracy drops to 60-70% with background noise. Technical terms are consistently wrong ("Kubernetes" becomes "communitas"). It fails completely with heavy accents. +Live transcription appears within 2-3 seconds of speech, which helps in fast-paced discussions. Accuracy drops to 60-70% with background noise. Technical terms come out wrong consistently ("Kubernetes" becomes "communitas"). Heavy accents break it entirely. ### 2. Meeting summary (Grade: B+) -Otter.ai generates automated summaries broken into chapters with timestamps. Email summaries arrive within 2 hours (usually 45 minutes in my experience). The chapter breakdown makes long meetings navigable. Action item extraction catches about 70-80% of commitments. +Otter generates automated summaries broken into chapters with timestamps. Email summaries land within 2 hours, usually 45 minutes in my experience. Chapter breakdowns make long meetings navigable. Action item extraction catches about 70-80% of commitments. -The frustration: summaries for technical discussions turn generic. "The team discussed system architecture" instead of actual details. Nuanced decisions in complex strategy calls get missed, and there's no way to customize summary format or depth. +The frustration: technical discussions get generic summaries. "The team discussed system architecture" instead of actual details. Strategy calls lose nuance, and there's no way to customize summary format or depth. ### 3. Slide and screen capture (Grade: A-) -The automatic screenshot feature worked surprisingly well. During product demos, it captured 90% of slides without manual input. Visual context embedded in transcripts saved me hours when reviewing what was actually shown versus what was said. +The automatic screenshot feature worked better than I expected. During product demos, it captured 90% of slides without manual input. Visual context inside the transcript saved me hours when reviewing what was actually shown versus what was said. -One caution: it captures everything on screen, including private Slack messages or emails if you forget to pause screen sharing. I almost leaked internal metrics to a client once. +One warning: it captures everything on screen, including private Slack messages or emails if you forget to pause screen sharing. I almost leaked internal metrics to a client once. ### 4. Otter Chat / AI assistant (Grade: C+) @@ -87,11 +84,11 @@ The AI assistant handles basic questions like "What were the main action items?" ### 5. Speaker identification (Grade: D) -Speaker identification is Otter.ai's weakest feature. In a 10-person team meeting, it misidentified speakers 30% of the time and confused two people with similar voices throughout, requiring 15 minutes of manual correction afterward. Even in simple two-person calls, it occasionally attributed my words to the other person. For meeting minutes you'll actually share, plan on extensive editing. +Speaker identification is Otter's weakest feature. In a 10-person team meeting, it misidentified speakers 30% of the time and confused two people with similar voices, requiring 15 minutes of manual cleanup. Even in two-person calls, it sometimes attributed my words to the other person. For meeting minutes you'll actually share, plan on heavy editing. ### 6. Otter channels & workspaces (Grade: B) -Team collaboration features worked adequately for organizing meetings by project. I created separate channels for client work (private), internal strategy (public to team), and all-hands meetings (public). The permissions system is straightforward, though not as flexible as some Otter.ai alternatives. +Team collaboration is adequate for organizing meetings by project. I set up separate channels for client work (private), internal strategy (public to team), and all-hands (public). Permissions are straightforward, though less flexible than some Otter alternatives. ### 7. Third-party integrations (Grade: B-) @@ -101,21 +98,21 @@ Alternatives like Fireflies offer deeper CRM integration and workflow automation ### 8. Calendar integration (Grade: A) -Otter.ai nails calendar integration. It detected 98% of scheduled meetings, joined automatically without manual intervention, and updated meeting details if the calendar changed. Truly set-it-and-forget-it functionality. +Otter nails calendar integration. It detected 98% of scheduled meetings, joined automatically, and updated meeting details when the calendar changed. Set-it-and-forget-it. -One minor issue: it occasionally joined recurring meetings I'd removed from the series. +One minor issue: it sometimes joined recurring meetings I'd removed from the series. ### 9. Mobile app recording (Grade: B-) -Recording in-person meetings from phone works, and transcripts sync across devices. It requires an internet connection (can record offline but won't transcribe until online), causes significant battery drain during long recordings, and the UI feels cramped for editing transcripts. You'll want to review and edit on a desktop. +Recording in-person meetings from phone works, and transcripts sync across devices. It needs internet (can record offline but won't transcribe until online), drains battery hard on long recordings, and the UI is cramped for editing transcripts. Review and edit on desktop. ### 10. Search & playback (Grade: B+) -Keyword search across hundreds of meetings is fast. Clicking any word in the transcript jumps audio to that moment. Variable playback speed (1.5x-2x) saved tons of review time. Advanced search operators are limited. You can't search "phrase in quotes AND keyword," and there's no way to filter by meeting duration, participant count, or date range easily. +Keyword search across hundreds of meetings is fast. Clicking any word in the transcript jumps audio to that moment. Variable playback speed (1.5x-2x) saved tons of review time. Advanced search operators are limited. You can't search "phrase in quotes AND keyword", and you can't easily filter by meeting duration, participant count, or date range. ### 11. File upload & transcription (Grade: C+) -Upload pre-recorded audio or video files for transcription. The problem: the free plan gets 3 lifetime uploads (then done forever), the Pro plan gets 10 uploads per month, and unlimited is locked to the Business tier. Having "lifetime limits" on a free tier is absurd. Ten per month on a $17/month plan feels stingy. This should be unlimited on Pro. +Upload pre-recorded audio or video files for transcription. The problem: free plan gets 3 lifetime uploads (forever), Pro gets 10 per month, and unlimited is locked to Business. "Lifetime limits" on a free tier is absurd. Ten per month on a $17/month plan is stingy. Should be unlimited on Pro. ### 12. Export options (Grade: B) @@ -125,31 +122,31 @@ Missing: direct export to Google Docs, customization options (remove speakers, k ### 13. Vocabulary customization (Grade: B) -Add custom words for better transcription accuracy with company names, product names, and technical terms. After adding "Kubernetes", "PostgreSQL", and "Supabase", accuracy improved 10-15% for jargon-heavy meetings. +Add custom words for better transcription on company names, product names, and technical terms. After adding "Kubernetes", "PostgreSQL", and "Supabase", accuracy improved 10-15% for jargon-heavy meetings. -However, I had to manually add every term (no bulk upload), and it sometimes ignored custom vocabulary anyway. +I had to add every term manually (no bulk upload), and it sometimes ignored custom vocabulary anyway. ### 14. Live collaboration & comments (Grade: C+) -Team members can comment on transcripts at specific timestamps and @mention others for follow-up. It works for basic collaboration, but comments don't integrate with task management tools, there's no way to mark comments as resolved, and notifications become overwhelming with active teams. +Team members can comment on transcripts at specific timestamps and @mention others. Basic collaboration works, but comments don't integrate with task management tools, there's no way to resolve them, and notifications get overwhelming on active teams. ### 15. Admin controls & analytics (Grade: B) (Business/Enterprise only) -See who's using minutes to prevent overages, manage user permissions, and view usage reports by department. This catches power users and helps deactivate former employees' access. +See who's using minutes to prevent overages, manage permissions, and view usage reports by department. Catches power users and helps deactivate former employees. -Analytics are superficial though (just usage, not meeting quality metrics), and you can't set per-user or per-team minute limits. +Analytics stay shallow (usage only, no meeting quality metrics), and you can't set per-user or per-team minute limits. ## Is Otter AI safe? -Otter AI's security became a major concern following a federal class-action lawsuit filed in August 2025 and multiple user incidents. The case, filed in the U.S. District Court for the Northern District of California, alleges that Otter "deceptively and surreptitiously" records private conversations without proper consent. +Otter's security became a serious concern after a federal class-action lawsuit filed in August 2025 and multiple user incidents. The case, filed in the U.S. District Court for the Northern District of California, alleges Otter "deceptively and surreptitiously" records private conversations without proper consent. ### Legal and privacy concerns ![](/api/assets/blog/articles/otter-ai-review/image-1.png) -The lawsuit centers on allegations that Otter's bot automatically joins meetings without obtaining consent from participants. If a meeting host has integrated their calendar with Otter, the system may join meetings without alerting attendees that conversations are being recorded and shared with Otter for AI training. +The lawsuit centers on Otter's bot automatically joining meetings without participant consent. If a meeting host has integrated their calendar with Otter, the system may join meetings without alerting attendees that conversations are being recorded and shared with Otter for AI training. -Real-world incidents have highlighted these privacy issues: +Real incidents have surfaced these privacy issues: - Corporate data leaks where sensitive VC meetings were inadvertently shared with external parties - Job interview recordings captured without candidate knowledge @@ -157,21 +154,21 @@ Real-world incidents have highlighted these privacy issues: ### Data collection and usage -Otter's privacy policy reveals extensive data collection practices beyond simple transcription. The company admits to using customer data for AI training: +Otter's privacy policy goes well beyond simple transcription. The company admits to using customer data for AI training: *"We train our proprietary artificial intelligence technology on de-identified audio recordings. We also train our technology on transcriptions to provide more accurate services, which may contain Personal Information." - Otter AI* -The service shares personal information with multiple third-party processors, including cloud service providers, data labeling services, and AI backend providers. This creates potential exposure points that many users may not realize they're accepting. +The service shares personal information with third-party processors, including cloud service providers, data labeling services, and AI backend providers. That's exposure most users don't realize they're accepting. ### Enterprise security considerations ![](/api/assets/blog/articles/otter-ai-review/image-2.png) -Organizations in regulated industries face particular risks. Individual employee adoption of Otter can create company-wide data exposure. The service collects extensive metadata including usage patterns, device information, and location data, which may conflict with enterprise security policies. +Regulated industries face particular risk. One employee adopting Otter can create company-wide data exposure. The service collects metadata including usage patterns, device information, and location data, which often conflicts with enterprise security policies. -Some IT security professionals have compared Otter's behavior to malware-like characteristics. Otter automatically sends workspace invitations to colleagues on users' behalf and shares meeting transcripts with external participants without explicit authorization. This can spread throughout organizations before users realize what's happening. +Some IT security professionals have likened Otter's behavior to malware. Otter automatically sends workspace invitations to colleagues on users' behalf and shares meeting transcripts with external participants without explicit authorization. It spreads through organizations before users realize what's happening. -For a detailed analysis of these security concerns and documented incidents, see [Is Otter AI Safe: What You Need to Know](https://char.com/blog/is-otter-ai-safe/). +For a detailed analysis of these security concerns and documented incidents, see [Is Otter AI Safe: What You Need to Know](https://anarlog.so/blog/is-otter-ai-safe/). ## How much does Otter AI cost @@ -189,9 +186,9 @@ Otter AI offers several pricing tiers: - AI Chat (20 queries/month) - Integration with Zoom, Teams, Meet, Slack -After three weeks of daily meetings, I hit the 300-minute limit. I had to choose which meetings to transcribe for the rest of the month. The 30-minute per-conversation cap is particularly painful. Long strategy sessions required restarting the recording, breaking the transcript into chunks. +After three weeks of daily meetings, I hit the 300-minute limit. I had to pick which meetings to transcribe for the rest of the month. The 30-minute per-conversation cap is the worst part. Long strategy sessions forced me to restart recording, breaking transcripts into chunks. -Otter AI's free tier works only if you average 1-2 meetings per week. For daily meeting participants, you'll need to upgrade within a month. +Otter's free tier only works if you average 1-2 meetings per week. Daily meeting participants will need to upgrade within a month. ### Otter AI Pro plan @@ -239,11 +236,11 @@ For a 10-person team: ### How costs add up -The pricing structure creates multiple pressure points. Heavy users quickly exhaust even the Business tier's 6,000-minute limit (100 hours/month). Teams face escalating per-user costs. +The pricing structure has multiple pressure points. Heavy users blow past even the Business tier's 6,000-minute limit (100 hours/month). Per-user costs escalate fast. -The annual discount of 51% pressures users into longer commitments before they fully understand the service's privacy implications or workflow integration challenges. +The 51% annual discount pushes users into long commitments before they understand the privacy implications or workflow integration challenges. -Enterprise pricing remains opaque with custom quotes. Organizations needing HIPAA compliance or advanced security features face potentially much higher rates than the published tiers suggest. +Enterprise pricing stays opaque with custom quotes. Organizations needing HIPAA compliance or advanced security may face much higher rates than the published tiers suggest. ## What users think about Otter AI? @@ -284,33 +281,31 @@ Based on reviews from G2, TrustPilot, Product Hunt, and Reddit: Here's how Otter AI stacks up against competitors: - -|   |   |   |   | +|   |   |   |   | | ---------------- | --------------------------------- | ------------------------------------------------ | --------------------------------------- | -| **Tool** | **Best For** | **Starting Price (monthly)** | **Key Advantage** | -| [Char](Char.com) | Zero lock-in, complete control | Free forever (local + BYOK). Cloud $25/month | Open source. Plain markdown. Your choice of AI. | -| Fireflies.ai | Team collaboration | Free plan available. Paid from $10/user/month | 69+ languages supported | -| Rev | High accuracy needs | Pay-per-use: $0.25/min AI, $1.99/min human | Human + AI transcription | -| Fathom | Sales teams | Free plan available. Teams from $18/user/month | CRM integrations | -| Notta | Multilingual teams | Free plan available. Paid from $13.49/user/month | 58 languages supported | -| MeetGeek | Analytics focus | Free plan available. Paid from $15.99/user/month | Advanced meeting insights | -| Tactiq | Chrome users | Free plan available. Paid from $20/user/month | Browser extension | -| Dialpad | Communication platforms | From $15/user/month | Unified communications | -| Avoma | Revenue teams | From $29/user/month | Conversation intelligence | -| Grain | Video-first teams | Free plan available. Paid from $19/user/month | Video highlights | -| Sembly AI | Task management | Free plan available. Paid from $17/user/month | AI task extraction | -| Descript | Content creators | Free plan available. Paid from $16/user/month | Audio/video editing | -| Jamie AI | Bot-free EU teams | Free plan available. Paid from €25/user/month | GDPR compliance & offline capability | -| tl;dv | Video-first teams | Free plan available. Paid from $29/user/month | Unlimited video recordings & clips | - - -See [detailed reviews of all Otter AI competitors](https://char.com/blog/otter-ai-alternatives/). +| **Tool** | **Best For** | **Starting Price (monthly)** | **Key Advantage** | +| [Anarlog](https://anarlog.so) | Zero lock-in, complete control | Free forever, fully local + BYOK. | Open source. Plain markdown. Your choice of AI. | +| Fireflies.ai | Team collaboration | Free plan available. Paid from $10/user/month | 69+ languages supported | +| Rev | High accuracy needs | Pay-per-use: $0.25/min AI, $1.99/min human | Human + AI transcription | +| Fathom | Sales teams | Free plan available. Teams from $18/user/month | CRM integrations | +| Notta | Multilingual teams | Free plan available. Paid from $13.49/user/month | 58 languages supported | +| MeetGeek | Analytics focus | Free plan available. Paid from $15.99/user/month | Advanced meeting insights | +| Tactiq | Chrome users | Free plan available. Paid from $20/user/month | Browser extension | +| Dialpad | Communication platforms | From $15/user/month | Unified communications | +| Avoma | Revenue teams | From $29/user/month | Conversation intelligence | +| Grain | Video-first teams | Free plan available. Paid from $19/user/month | Video highlights | +| Sembly AI | Task management | Free plan available. Paid from $17/user/month | AI task extraction | +| Descript | Content creators | Free plan available. Paid from $16/user/month | Audio/video editing | +| Jamie AI | Bot-free EU teams | Free plan available. Paid from €25/user/month | GDPR compliance & offline capability | +| tl;dv | Video-first teams | Free plan available. Paid from $29/user/month | Unlimited video recordings & clips | + +See [detailed reviews of all Otter AI competitors](https://anarlog.so/blog/otter-ai-alternatives/). ### What is the best Otter alternative? -Char is the best Otter AI alternative if you want zero lock-in and complete control. It's an open-source AI notepad that stores everything as plain markdown files and lets you choose your AI stack—managed cloud, bring your own keys, or run local models. Zero vendor lock-in, zero compromises. +Anarlog is the best Otter AI alternative if you want zero lock-in and full control. It's an open-source AI notepad that stores everything as plain markdown files and lets you pick your AI: bring your own keys or run local models. No vendor lock-in. -You get all the core AI note-taking features—transcription, summaries, search—without sacrificing control. +You get the core AI note-taking features — transcription, summaries, search — without giving up control. ## Frequently asked questions @@ -320,20 +315,20 @@ Otter.ai offers a free plan with 300 monthly minutes (5 hours) and a 30-minute l ### 2. Does Otter.ai sell my data? -Otter.ai doesn't explicitly "sell" data, but their privacy policy states they use customer conversations to train AI models and share data with third-party processors. For complete control over your data, consider open-source alternatives like Char. +Otter doesn't explicitly "sell" data, but their privacy policy states they use customer conversations to train AI models and share data with third-party processors. For full control, consider open-source alternatives like Anarlog. ### 3. What are Otter.ai free plan features? -The free plan includes 300 monthly minutes, 30-minute conversation limit, 3 lifetime file imports, live transcription, basic speaker identification, and AI Chat with 20 queries/month. You keep only your 25 most recent conversations—older ones disappear. For comparison, alternatives like Char and Fathom offer unlimited free transcription. +The free plan includes 300 monthly minutes, 30-minute conversation limit, 3 lifetime file imports, live transcription, basic speaker identification, and AI Chat with 20 queries/month. You keep only your 25 most recent conversations—older ones disappear. For comparison, alternatives like Anarlog and Fathom offer unlimited free transcription. ### 4. Can Otter.ai transcribe in languages other than English? -Otter.ai supports only 3 languages: English, Spanish, and French. This is a major limitation compared to competitors. +Otter supports only 3 languages: English, Spanish, French. That's a major gap compared to competitors. ### 5. What are the best alternatives to Otter.ai? -Char (best for zero lock-in with complete control over data and AI), Fireflies (69+ languages for global teams), Fathom (unlimited free tier), Rev (99% human accuracy), and Jamie AI (offline + GDPR compliance). +Anarlog (best for zero lock-in with complete control over data and AI), Fireflies (69+ languages for global teams), Fathom (unlimited free tier), Rev (99% human accuracy), and Jamie AI (offline + GDPR compliance). ### 6. Is Otter.ai worth it for teams? -Otter.ai works for basic English-language meeting documentation if you're comfortable with cloud processing. For a 10-person team, costs run $200-400/month. However, it offers only 3 languages, has significant privacy concerns, requires constant internet, and transcription accuracy needs editing. +Otter works for basic English-language meeting documentation if you're comfortable with cloud processing. For a 10-person team, costs run $200-400/month. The downsides: only 3 languages, real privacy concerns, constant internet required, and transcription accuracy that needs editing. diff --git a/apps/web/content/articles/plaud-ai-alternatives.mdx b/apps/web/content/articles/plaud-ai-alternatives.mdx index 87a5b028aa..a33023510c 100644 --- a/apps/web/content/articles/plaud-ai-alternatives.mdx +++ b/apps/web/content/articles/plaud-ai-alternatives.mdx @@ -4,17 +4,16 @@ display_title: "6 Plaud AI Alternatives Worth Considering in 2026" meta_description: "Looking beyond Plaud? Compare 6 AI voice recorder alternatives. Hardware, software & specialized AI recorders reviewed." author: - "Harshika" -coverImage: "/api/assets/blog/plaud-ai-alternatives/cover.png" featured: false category: "Comparisons" date: "2025-10-27" --- -Plaud's credit card-sized recorder looked promising until you saw the subscription costs pile up. Or maybe you're tired of managing yet another piece of hardware. Perhaps you just want your meeting data to stay on your device instead of floating around in someone else's cloud. +Plaud's credit card-sized recorder looks promising until you add up the subscription costs. Or you're tired of managing another piece of hardware. Or you want your meeting data on your device instead of in someone else's cloud. -Whatever brought you here, you're looking for something different. This guide covers six alternatives that take different approaches to voice recording and AI transcription. +Whatever brought you here, you want something different. This guide covers six alternatives that take different approaches to voice recording and AI transcription. -We'll focus on what actually matters: how they work, what they cost, and whether they'll solve your specific problem better than Plaud. +We'll focus on what matters: how they work, what they cost, and whether they'll solve your specific problem better than Plaud. ## Quick Plaud AI review @@ -24,47 +23,45 @@ The lineup includes Plaud Note (a credit card-sized recorder at 0.12" thin), Pla All devices use dual-mode recording, switching between phone call capture via vibration conduction and ambient recording for in-person conversations. -The catch? After your 300 free minutes per month, you're paying $99.99/year for 1,200 minutes or $239.99/year for unlimited. Plus, there's the upfront hardware cost ($159-$179) and the fact that you need cloud processing for the AI features that make it useful. +The catch? After your 300 free minutes per month, you're paying $99.99/year for 1,200 minutes or $239.99/year for unlimited. Add the upfront hardware cost ($159-$179) and cloud processing for the AI features that make it useful. -It works, but it's not for everyone. Let's look at what else is out there. +It works, but it's not for everyone. Here's what else is out there. ## Comparison table: Plaud vs alternatives Here's how six AI transcription and note-taking tools compare with Plaud: - -| Tool | Best For | Type | Key Advantage | Main Limitation | Starting Price | +| Tool | Best For | Type | Key Advantage | Main Limitation | Starting Price | | -------------------------- | ---------------------------- | ------------------------------ | --------------------------- | --------------------------- | --------------------------- | -| Plaud | Portable recording anywhere | Hardware recorder + app | Portable & discreet | Cloud dependency for AI | $159 hardware + $99.99/year | -| Char | Privacy-focused Mac users | Desktop software (macOS) | Zero cloud dependency | macOS only | Free | -| iFLYTEK Smart Recorder Pro | Professional field recording | Standalone hardware device | Standalone with touchscreen | Bulky form factor | $329.99 one-time | -| Mobvoi TicNote | Casual wearable recording | Wearable hardware + app | Wearable convenience | Limited battery life | $169 + $8.99/month | -| Notta Memo | Budget-conscious users | Pocket hardware + app | Affordable entry point | Requires phone for features | $69 + $13.49/month | -| Deepscribe | Healthcare providers only | Healthcare AI scribe (iOS app) | Clinical documentation | Healthcare-only | $400-500/month per provider | -| Avoma | Sales & revenue teams | Cloud-based software | Sales intelligence | Bot visibility | $29/user/month | - +| Plaud | Portable recording anywhere | Hardware recorder + app | Portable & discreet | Cloud dependency for AI | $159 hardware + $99.99/year | +| Anarlog | Privacy-focused Mac users | Desktop software (macOS) | Zero cloud dependency | macOS only | Free | +| iFLYTEK Smart Recorder Pro | Professional field recording | Standalone hardware device | Standalone with touchscreen | Bulky form factor | $329.99 one-time | +| Mobvoi TicNote | Casual wearable recording | Wearable hardware + app | Wearable convenience | Limited battery life | $169 + $8.99/month | +| Notta Memo | Budget-conscious users | Pocket hardware + app | Affordable entry point | Requires phone for features | $69 + $13.49/month | +| Deepscribe | Healthcare providers only | Healthcare AI scribe (iOS app) | Clinical documentation | Healthcare-only | $400-500/month per provider | +| Avoma | Sales & revenue teams | Cloud-based software | Sales intelligence | Bot visibility | $29/user/month | Continue reading for detailed reviews of each of these tools. ## Plaud AI alternatives: detailed reviews -### 1. Char +### 1. Anarlog -[Char](/) is an open-source AI notepad for meetings that gives you complete control over your data, AI stack, and workflow. Everything is stored as plain Markdown files on your device and not in proprietary databases or cloud servers. +[Anarlog](/) is an open-source AI notepad for meetings that gives you full control over your data, AI stack, and workflow. Everything is stored as plain Markdown files on your device, not in a proprietary database or cloud server. -You can inspect the code, customize functionality, and choose which AI processes your data. Zero lock-in, zero compromises. +Inspect the code, customize functionality, and pick which AI processes your data. Zero lock-in. -![Char](/api/assets/blog/plaud-ai-alternatives/hyprnote.webp) +![Anarlog](/api/assets/blog/plaud-ai-alternatives/anarlog.webp) #### How it works -Char sits quietly on your computer and captures audio directly from your system without bots joining your calls. While you're in a meeting, it generates a live transcript as you take your own notes alongside it. +Anarlog sits on your computer and captures audio directly from your system without bots joining your calls. During a meeting, it generates a live transcript while you take your own notes alongside it. -When the meeting ends, it combines your notes with the transcript and uses AI to produce a structured summary. +When the meeting ends, it combines your notes with the transcript and uses AI to produce a structured summary. -You choose which AI processes that data — our managed service, your own API keys (OpenAI, Anthropic, etc.), or a fully local model running on your machine via Ollama or LM Studio. +You pick which AI processes that data — our managed service, your own API keys (OpenAI, Anthropic, etc.), or a fully local model running on your machine via Ollama or LM Studio. -Everything gets saved as plain markdown files on your device. Nothing goes to Char's servers. You can open those files in Obsidian, VS Code, Notion, or any text editor; they're just files you own. +Everything saves as plain markdown files on your device. Nothing goes to Anarlog's servers. Open those files in Obsidian, VS Code, Notion, or any text editor — they're just files you own. #### Key features @@ -95,11 +92,9 @@ Everything gets saved as plain markdown files on your device. Nothing goes to Ch #### Pricing -Char is free forever for local transcription, BYOK, and all core features. No artificial caps, no trial that expires. - -The managed cloud service is $25/month for those who want the easiest setup without managing API keys. Most people never need to pay—the free plan handles meeting notes completely. +Anarlog is free forever for local transcription, BYOK, and all core features. No artificial caps, no trial that expires. Most people never need to pay — the free plan covers meeting notes completely. -Enterprise pricing applies when you need on-prem deployment, SSO, consent management, or admin controls for compliance-sensitive environments. [Contact sales](/founders) for custom quotes. +Enterprise pricing applies when you need on-prem deployment, SSO, consent management, or admin controls for compliance-sensitive environments. [Contact sales](https://char.com) for custom quotes. ### 2. iFLYTEK Smart Recorder Pro @@ -109,13 +104,13 @@ Enterprise pricing applies when you need on-prem deployment, SSO, consent manage #### How it works -iFLYTEK takes a self-contained approach. The device packs eight microphones (two directional, six omnidirectional) that capture audio from up to 15 meters away, along with an octa-core processor that handles real-time transcription directly on the device. No phone or internet required during recording. +iFLYTEK takes a self-contained approach. The device packs eight microphones (two directional, six omnidirectional) that capture audio from up to 15 meters away, plus an octa-core processor that runs real-time transcription on the device. No phone or internet required during recording. -The 3.5-inch touchscreen lets you see transcriptions appear as you record, switch between four recording modes (intelligent, conference, interview, speech), and mark important moments with bookmarks. +The 3.5-inch touchscreen shows transcriptions as you record, lets you switch between four recording modes (intelligent, conference, interview, speech), and mark important moments with bookmarks. -Unlike Plaud's sync-and-process model, iFLYTEK transcribes as you record using on-device AI. The transcription happens locally for privacy, though you can connect to WiFi or insert a 4G SIM card for cloud syncing. +Unlike Plaud's sync-and-process model, iFLYTEK transcribes as you record using on-device AI. Transcription happens locally for privacy, though you can connect to WiFi or insert a 4G SIM card for cloud syncing. -Files export to PDF, Word, or TXT, and you can transfer them via USB or access through their web portal at cloud.iflytekrecord.com. +Files export to PDF, Word, or TXT. Transfer via USB or access through their web portal at cloud.iflytekrecord.com. #### Key features @@ -147,11 +142,11 @@ Files export to PDF, Word, or TXT, and you can transfer them via USB or access t #### Pricing -iFLYTEK's pricing reflects its position as a professional-grade tool rather than a consumer gadget. The Smart Recorder Pro currently lists at $329.99 (reduced from $369.99), while the standard Smart Recorder runs $199.99. +iFLYTEK is priced as a professional tool, not a consumer gadget. The Smart Recorder Pro lists at $329.99 (reduced from $369.99). The standard Smart Recorder runs $199.99. -There's no monthly subscription or transcription minute limits. You buy the hardware once, get 5GB of cloud storage valid for three years, and transcribe as much as you want offline. +No monthly subscription or transcription minute limits. Buy the hardware once, get 5GB of cloud storage valid for three years, and transcribe as much as you want offline. -After three years, the cloud storage expires, but they haven't announced what happens next—presumably a renewal fee or you just keep using local storage. +After three years, cloud storage expires. They haven't announced what happens next, so expect a renewal fee or fall back to local storage. ### 3. Mobvoi TicNote @@ -161,13 +156,13 @@ After three years, the cloud storage expires, but they haven't announced what ha #### How it works -TicNote takes Plaud's magnetic attachment approach but adds an AI agent layer that changes things. +TicNote borrows Plaud's magnetic attachment approach and stacks an AI agent layer on top. -The hardware captures audio through three MEMS microphones with dual modes (phone calls via vibration conduction, ambient recording for meetings), then uploads to the cloud where Shadow, powered by GPT-4o, GPT-4o-mini, and DeepSeek-R1, handles transcription in 120+ languages. +The hardware captures audio through three MEMS microphones with dual modes (phone calls via vibration conduction, ambient recording for meetings), then uploads to the cloud where Shadow — powered by GPT-4o, GPT-4o-mini, and DeepSeek-R1 — handles transcription in 120+ languages. -The key difference is Shadow's project-centric approach. Instead of treating each recording separately, you organize files into projects. Shadow builds contextual knowledge across everything in a project, so when you ask it questions or request research, it pulls from your entire conversation history within that project. +The difference is Shadow's project-centric approach. Instead of treating each recording separately, you organize files into projects. Shadow builds contextual knowledge across everything in a project, so when you ask questions or request research, it pulls from your entire conversation history within that project. -The more you use it, the smarter Shadow gets at connecting dots and surfacing insights you didn't explicitly ask for. +The more you use it, the better Shadow gets at connecting dots and surfacing insights you didn't ask for. #### Key features @@ -201,15 +196,15 @@ The more you use it, the smarter Shadow gets at connecting dots and surfacing in #### Pricing -TicNote costs $159.99 for hardware—matching Plaud's entry point. +TicNote costs $159.99 for hardware, matching Plaud's entry point. The free tier gives 300 transcription minutes monthly (same as Plaud) but includes AI agent features Plaud doesn't offer. -Pro runs $79/year for 2,000 minutes versus Plaud's $99/year for 1,200 minutes, making it better value if you need the extra capacity. +Pro runs $79/year for 2,000 minutes versus Plaud's $99/year for 1,200 minutes — better value if you need the extra capacity. Business tier ($239/year) delivers 6,000 minutes with unlimited audio imports and AI Speech Enhancement. -The real differentiator is Shadow's contextual intelligence. If you organize work into projects and want an AI that connects dots across conversations, TicNote's pricing makes sense. If you just need straightforward transcription, you're paying extra for features you won't use. +The real differentiator is Shadow's contextual intelligence. If you organize work into projects and want an AI that connects dots across conversations, TicNote's pricing makes sense. If you just need straightforward transcription, you're paying for features you won't use.   @@ -221,11 +216,11 @@ Notta Memo is a Japanese AI company's entry into hardware. An ultra-lightweight #### How it works -The device weighs just 28 grams with four MEMS microphones plus one bone conduction mic for phone calls. Toggle between live mode (ambient recording) and call mode (phone recording via vibration detection), then recordings automatically sync via Bluetooth or WiFi to Notta's cloud platform. +The device weighs 28 grams with four MEMS microphones plus one bone conduction mic for phone calls. Toggle between live mode (ambient recording) and call mode (phone recording via vibration detection). Recordings sync via Bluetooth or WiFi to Notta's cloud platform automatically. -The differentiator is the mature software ecosystem behind it. Your recordings flow across web interface, iOS app, Android app, Chrome extension, and the device itself with AI context maintained throughout. +The differentiator is the mature software ecosystem behind it. Recordings flow across web interface, iOS app, Android app, Chrome extension, and the device itself with AI context maintained throughout. -Start recording on hardware, edit on mobile, analyze on web, share via browser extension. The platform handles transcription in 58 languages, then generates summaries, mind maps, and integrates with 30+ templates. +Start recording on hardware, edit on mobile, analyze on web, share via browser extension. The platform handles transcription in 58 languages, generates summaries and mind maps, and integrates with 30+ templates. #### Key features @@ -260,11 +255,11 @@ Start recording on hardware, edit on mobile, analyze on web, share via browser e #### Pricing -Notta Memo costs $149 for the hardware. This gets you the device plus a "Starter Plan" with 300 minutes monthly transcription, same as Plaud's free tier. +Notta Memo costs $149 for the hardware. That gets you the device plus a "Starter Plan" with 300 minutes monthly transcription, same as Plaud's free tier. -Notta's software pricing sits between basic and premium. Pro plan runs $13.49/month, giving unlimited transcription minutes with a 200 uploaded file monthly cap. Business plan starts at $27.99/user/month with unlimited transcription and uploaded files. +Notta's software pricing sits between basic and premium. Pro runs $13.49/month for unlimited transcription minutes, capped at 200 uploaded files monthly. Business starts at $27.99/user/month with unlimited transcription and uploaded files. -Compare this to Plaud's $99.99/year Pro (1,200 minutes/month) or $239.99/year Unlimited. If you need truly unlimited transcription, Notta's annual Pro delivers better value than Plaud's $99 plan that still caps you at 1,200 minutes monthly. But if 1,200 minutes covers your needs, Plaud's Pro is slightly cheaper. +Compare with Plaud's $99.99/year Pro (1,200 minutes/month) or $239.99/year Unlimited. If you need truly unlimited transcription, Notta's annual Pro beats Plaud's $99 plan, which still caps at 1,200 minutes monthly. If 1,200 minutes covers your needs, Plaud's Pro is slightly cheaper. ### 5. Deepscribe @@ -274,9 +269,9 @@ Deepscribe is an enterprise-grade AI medical scribe built specifically for healt #### How it works -Physicians use an iOS app during patient visits, and the AI—trained on data from over 2 million patient encounters—listens to natural doctor-patient conversations, extracts medically relevant information, and automatically populates discrete EHR fields with complete, billable documentation. +Physicians use an iOS app during patient visits. The AI — trained on data from over 2 million patient encounters — listens to natural doctor-patient conversations, extracts medically relevant information, and populates discrete EHR fields with complete, billable documentation. -It integrates deeply with major EHR systems (Epic, athenaOne, eClinicalWorks, DrChrono) and includes AI pre-charting that pulls forward patient history, labs, imaging, medications, and diagnoses for context. The AI learns individual physician styles and continuously adapts to their documentation preferences. +It integrates with major EHR systems (Epic, athenaOne, eClinicalWorks, DrChrono) and includes AI pre-charting that pulls forward patient history, labs, imaging, medications, and diagnoses for context. The AI learns individual physician styles and adapts to their documentation preferences over time. #### Key features @@ -310,9 +305,9 @@ It integrates deeply with major EHR systems (Epic, athenaOne, eClinicalWorks, Dr #### Pricing -Deepscribe doesn't list prices publicly—you have to request a demo for custom quotes. Based on third-party sources and industry reports, pricing runs approximately $400-500 per provider per month for EHR-integrated plans, with some estimates suggesting $8,000+ annually per clinician. Non-integrated plans may start around $250-400/month. +Deepscribe doesn't list prices publicly. You request a demo for custom quotes. Based on third-party sources and industry reports, pricing runs about $400-500 per provider per month for EHR-integrated plans, with some estimates suggesting $8,000+ annually per clinician. Non-integrated plans may start around $250-400/month. -Compare this to Plaud's $159 hardware + $99-239/year software, and you're looking at 20-40x higher annual cost for Deepscribe. That comparison misses the point entirely: Deepscribe generates billable clinical documentation that meets regulatory standards and integrates with practice management systems. Plaud gives you meeting transcripts. These solve completely different problems for completely different users. +Compared with Plaud's $159 hardware + $99-239/year software, that's 20-40x higher annual cost. The comparison misses the point: Deepscribe generates billable clinical documentation that meets regulatory standards and integrates with practice management systems. Plaud gives you meeting transcripts. Different problems, different users. ### 6. Avoma @@ -322,13 +317,13 @@ Avoma is a cloud-based AI meeting assistant built specifically for revenue teams #### How it works -Avoma operates as a bot-based meeting assistant that joins your Zoom, Teams, or Google Meet calls to record and transcribe conversations in real-time across 75+ languages. +Avoma is a bot-based meeting assistant that joins your Zoom, Teams, or Google Meet calls to record and transcribe conversations in real-time across 75+ languages. -During meetings, you can live-bookmark key moments by clicking categories like "Pain Points" or "Next Steps", and Avoma automatically organizes these under time-stamped sections. +During meetings, you can live-bookmark moments by clicking categories like "Pain Points" or "Next Steps". Avoma organizes these under time-stamped sections automatically. -After the call, AI generates human-like notes using custom templates (MEDDIC, SPICED, SPIN, etc.) and automatically extracts topics like Business Need, Objections, and Pricing Discussions into smart chapters. +After the call, AI generates human-like notes using custom templates (MEDDIC, SPICED, SPIN, etc.) and extracts topics like Business Need, Objections, and Pricing Discussions into smart chapters. -The platform then pushes this data directly to your CRM, updating custom fields, logging activities, and syncing action items without manual data entry. +The platform pushes this data directly to your CRM, updating custom fields, logging activities, and syncing action items without manual data entry. #### Key features @@ -360,17 +355,17 @@ The platform then pushes this data directly to your CRM, updating custom fields, #### Pricing -Avoma's pricing looks straightforward until you realize the good features cost extra. The base Organization plan starts at $29/user/month (billed annually) and includes unlimited transcription, AI notes, CRM automation, and scheduling. This handles core meeting needs without artificial limits. +Avoma's pricing looks straightforward until you realize the good features cost extra. The base Organization plan starts at $29/user/month (billed annually) with unlimited transcription, AI notes, CRM automation, and scheduling. That covers core meeting needs without artificial limits. -But to unlock conversation intelligence features like call scoring, coaching insights, and keyword tracking, add another $29/user/month. Want revenue intelligence with deal risk alerts and pipeline tracking? That's another $29/user/month. Need advanced lead routing? Add $19/user/month for the Scheduler add-on. +To unlock conversation intelligence (call scoring, coaching insights, keyword tracking), add another $29/user/month. Revenue intelligence with deal risk alerts and pipeline tracking? Another $29/user/month. Advanced lead routing? Add $19/user/month for the Scheduler add-on. -For teams that actually need the full revenue intelligence stack, Avoma delivers legitimate ROI by replacing multiple tools. But if you're just looking for meeting transcription, this is expensive overkill. +If your team needs the full revenue intelligence stack, Avoma delivers ROI by replacing multiple tools. If you just want meeting transcription, it's expensive overkill. ## Which Plaud alternative should you choose? The right tool depends on what matters most to you: -**Choose Char** if you're on a Mac and privacy is non-negotiable. It's the only option here that keeps everything local—no cloud, no subscriptions, no data leaving your device. This works for healthcare professionals, lawyers, consultants, or anyone in compliance-sensitive roles who needs unlimited meeting notes without worrying about data storage. +**Choose Anarlog** if you're on a Mac and privacy is non-negotiable. It's the only option here that keeps everything local—no cloud, no subscriptions, no data leaving your device. This works for healthcare professionals, lawyers, consultants, or anyone in compliance-sensitive roles who needs unlimited meeting notes without worrying about data storage. **Choose iFLYTEK Smart Recorder Pro** if you need professional-grade field recording with a standalone device. The built-in touchscreen and 15-meter microphone range make it ideal for journalists, researchers, or anyone recording lectures and conferences where you can't rely on your phone or laptop. @@ -382,10 +377,10 @@ The right tool depends on what matters most to you: **Choose Avoma** if you're on a sales team and need more than transcription. The CRM automation, deal intelligence, and coaching features justify the cost if you're already using multiple tools for revenue operations. But if you just want meeting notes, this is overkill. -### Try Char for free +### Try Anarlog for free -Ready to take meeting notes without the cloud dependency or subscription treadmill? Char gives you unlimited transcription and AI summaries completely free—no credit card, no trials that expire, no catch. +Ready to take meeting notes without the cloud dependency or subscription treadmill? Anarlog gives you unlimited transcription and AI summaries completely free—no credit card, no trials that expire, no catch. -**[Download Char for macOS](/download)** and start recording your first meeting in under 2 minutes. Your data stays yours, on your device, forever. +**[Download Anarlog for macOS](https://anarlog.so)** and start recording your first meeting in under 2 minutes. Your data stays yours, on your device, forever.   diff --git a/apps/web/content/articles/publishing-stack.mdx b/apps/web/content/articles/publishing-stack.mdx index 54983af161..994828cee5 100644 --- a/apps/web/content/articles/publishing-stack.mdx +++ b/apps/web/content/articles/publishing-stack.mdx @@ -1,7 +1,7 @@ --- -meta_title: "Char's Publishing Stack: MDX, GitHub, and the Future of Docs" -display_title: "How We Built Char's Publishing Stack" -meta_description: "A deep dive into how Char built a future-proof publishing system using MDX, GitHub workflows, custom components, and TanStack Start—and why this approach outlives most CMS platforms." +meta_title: "Anarlog's Publishing Stack: MDX, GitHub, and the Future of Docs" +display_title: "How We Built Anarlog's Publishing Stack" +meta_description: "A deep dive into how Anarlog built a future-proof publishing system using MDX, GitHub workflows, custom components, and TanStack Start—and why this approach outlives most CMS platforms." author: "John Jeong" category: "Engineering" date: "2025-12-03" @@ -11,7 +11,7 @@ Most teams pick a CMS. We ended up building a publishing system. Every CMS we evaluated pushed us away from the way we actually work. We're a deeply technical team. We write in our IDEs. We think in Git. We move fast. We rewrite things without ceremony. We treat documentation and blog posts as part of the product, not accessories to it. -When we looked at the options — WYSIWYG editors, headless CMS platforms, Git-based CMS layers, hosted docs systems — every option either slowed us down or boxed us in. So we did the simplest thing: we built our publishing system the same way we build Char. +When we looked at the options — WYSIWYG editors, headless CMS platforms, Git-based CMS layers, hosted docs systems — every option either slowed us down or boxed us in. So we did the simplest thing: we built our publishing system the same way we build Anarlog. This post breaks down exactly how it works, why it works, and why I expect it to outlive most CMS platforms people are betting on today. @@ -49,13 +49,13 @@ Our stack uses GitHub drag-and-drop for inline screenshots, Supabase buckets for ## 5. Custom MDX components -Content becomes code when you use MDX, and every document becomes a canvas. We built a small but powerful set of components: `
`, `