diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 58c761c..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,8 +0,0 @@ -github: - - jb-thery - -# Optional funding platforms can be added later. -# open_collective: -# - ragmir -# custom: -# - https://polar.sh/ diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 81cee5c..19e2e1e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -41,7 +41,6 @@ body: - Retrieval (search, ask, research, evaluate) - MCP server / agent integration - CLI - - Desktop app - Skills (audio, report, legal) - Documentation - Other diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fe74b91..eb34c0d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,4 +16,4 @@ ## Security - [ ] No secrets, private documents, `.env` files, or generated `.ragmir/` state are included. -- [ ] Public documentation does not expose private project, customer, pricing, or validation details. +- [ ] Public documentation does not expose private project, customer, or validation details. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 50a70e1..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - day: monday - time: "08:00" - timezone: Asia/Bangkok - labels: - - dependencies - open-pull-requests-limit: 2 - commit-message: - prefix: chore - include: scope - groups: - npm-safe-updates: - patterns: - - "*" - update-types: - - minor - - patch - ignore: - - dependency-name: "*" - update-types: - - version-update:semver-major - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "08:30" - timezone: Asia/Bangkok - labels: - - dependencies - open-pull-requests-limit: 1 - commit-message: - prefix: chore - include: scope - groups: - github-actions-safe-updates: - patterns: - - "*" - update-types: - - minor - - patch - ignore: - - dependency-name: "*" - update-types: - - version-update:semver-major diff --git a/.github/workflows/deploy-landing.yml b/.github/workflows/deploy-landing.yml deleted file mode 100644 index c24b6b7..0000000 --- a/.github/workflows/deploy-landing.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Deploy landing - -on: - push: - branches: [main, develop] - paths: - - "packages/ragmir-landing/**" - - ".github/workflows/deploy-landing.yml" - workflow_dispatch: - inputs: - version: - description: "Semantic version to inject (e.g. 2.3.0)" - required: false - -permissions: - contents: read - -concurrency: - group: deploy-landing-${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy: - name: Build and deploy landing - runs-on: ubuntu-latest - environment: - name: ${{ github.ref == 'refs/heads/main' && 'landing-production' || 'landing-staging' }} - url: ${{ github.ref == 'refs/heads/main' && 'https://ragmir.com' || 'https://staging.ragmir.jcode.works' }} - defaults: - run: - working-directory: packages/ragmir-landing - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Install mise - run: | - curl https://mise.run | sh - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" - - - name: Install pinned Node via mise - run: mise install - working-directory: . - - - name: Set up pnpm - run: | - corepack enable - corepack prepare pnpm@11.9.0 --activate - - - name: Install dependencies - run: pnpm install --frozen-lockfile - working-directory: . - - - name: Resolve version - id: version - working-directory: . - run: | - VERSION="${{ github.event.inputs.version || '' }}" - if [ -z "$VERSION" ]; then - VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "") - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - - name: Build (production) - if: github.ref == 'refs/heads/main' - run: pnpm build - env: - PUBLIC_RAGMIR_LANDING_URL: https://ragmir.com - PUBLIC_RAGMIR_VERSION: ${{ steps.version.outputs.version }} - - - name: Deploy (production) - if: github.ref == 'refs/heads/main' - run: pnpm exec wrangler deploy - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - - name: Submit to IndexNow - if: github.ref == 'refs/heads/main' - run: pnpm exec node scripts/submit-indexnow.mjs - env: - INDEXNOW_API_KEY: ${{ secrets.INDEXNOW_API_KEY }} - INDEXNOW_KEY_NAME: ${{ vars.INDEXNOW_KEY_NAME }} - SITEMAP_LOCAL_PATH: dist/sitemap-0.xml - continue-on-error: true - - - name: Build (staging, not indexed) - if: github.ref == 'refs/heads/develop' - run: pnpm build - env: - PUBLIC_RAGMIR_LANDING_URL: https://staging.ragmir.jcode.works - PUBLIC_RAGMIR_VERSION: ${{ steps.version.outputs.version }} - - - name: Deploy (staging) - if: github.ref == 'refs/heads/develop' - run: pnpm exec wrangler deploy --env staging - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.github/workflows/native-app-build.yml b/.github/workflows/native-app-build.yml deleted file mode 100644 index 4982eb1..0000000 --- a/.github/workflows/native-app-build.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: Native App Build - -on: - workflow_dispatch: - inputs: - target: - description: "Native target to build" - required: true - default: "linux" - type: choice - options: - - linux - - macos - - windows - - desktop-all - -permissions: - contents: read - -concurrency: - group: native-app-build-${{ github.ref }}-${{ inputs.target }} - cancel-in-progress: false - -jobs: - linux: - name: Build Linux AppImage and deb - if: inputs.target == 'linux' || inputs.target == 'desktop-all' - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install Linux Tauri dependencies - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - - - name: Install mise - run: | - curl https://mise.run | sh - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" - - - name: Install pinned Node and Rust via mise - run: mise install - - - name: Set up pnpm - run: | - corepack enable - corepack prepare pnpm@11.9.0 --activate - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run release preflight - run: pnpm --filter @jcode.labs/ragmir-app release:preflight -- --target linux --soft - - - name: Build native bundle - run: pnpm --filter @jcode.labs/ragmir-app tauri:build:linux - - - name: Generate checksums - run: pnpm --filter @jcode.labs/ragmir-app release:checksums - - - name: Generate release manifest - run: pnpm --filter @jcode.labs/ragmir-app release:manifest -- --target linux - - - name: Upload native bundle - uses: actions/upload-artifact@v4 - with: - name: ragmir-app-linux-${{ github.sha }} - path: packages/ragmir-app/src-tauri/target/release/bundle/ - if-no-files-found: error - - macos: - name: Build macOS app and dmg - if: inputs.target == 'macos' || inputs.target == 'desktop-all' - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install mise - run: | - curl https://mise.run | sh - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" - - - name: Install pinned Node and Rust via mise - run: mise install - - - name: Set up pnpm - run: | - corepack enable - corepack prepare pnpm@11.9.0 --activate - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run release preflight - run: pnpm --filter @jcode.labs/ragmir-app release:preflight -- --target macos --soft - - - name: Build native bundle - run: pnpm --filter @jcode.labs/ragmir-app tauri:build:macos - - - name: Generate checksums - run: pnpm --filter @jcode.labs/ragmir-app release:checksums - - - name: Generate release manifest - run: pnpm --filter @jcode.labs/ragmir-app release:manifest -- --target macos - - - name: Upload native bundle - uses: actions/upload-artifact@v4 - with: - name: ragmir-app-macos-${{ github.sha }} - path: packages/ragmir-app/src-tauri/target/release/bundle/ - if-no-files-found: error - - windows: - name: Build Windows NSIS and MSI - if: inputs.target == 'windows' || inputs.target == 'desktop-all' - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install mise - shell: bash - run: | - curl https://mise.run | sh - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" - - - name: Install pinned Node and Rust via mise - shell: bash - run: mise install - - - name: Set up pnpm - shell: bash - run: | - corepack enable - corepack prepare pnpm@11.9.0 --activate - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run release preflight - run: pnpm --filter @jcode.labs/ragmir-app release:preflight -- --target windows --soft - - - name: Build native bundle - run: pnpm --filter @jcode.labs/ragmir-app tauri:build:windows - - - name: Generate checksums - run: pnpm --filter @jcode.labs/ragmir-app release:checksums - - - name: Generate release manifest - run: pnpm --filter @jcode.labs/ragmir-app release:manifest -- --target windows - - - name: Upload native bundle - uses: actions/upload-artifact@v4 - with: - name: ragmir-app-windows-${{ github.sha }} - path: packages/ragmir-app/src-tauri/target/release/bundle/ - if-no-files-found: error diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 30a7101..e47d53e 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -68,24 +68,3 @@ jobs: run: | VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "") echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - deploy-landing: - name: Rebuild and deploy landing with new version - runs-on: ubuntu-latest - needs: release - if: needs.release.outputs.version != '' - permissions: - actions: write - contents: read - steps: - - name: Trigger landing deploy with version - uses: actions/github-script@v7 - with: - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'deploy-landing.yml', - ref: 'main', - inputs: { version: '${{ needs.release.outputs.version }}' } - }) diff --git a/.gitignore b/.gitignore index 63a17a0..9db4737 100644 --- a/.gitignore +++ b/.gitignore @@ -16,12 +16,8 @@ release-artifacts/ packages/ragmir-core/dist/ packages/ragmir-tts/dist/ packages/ragmir-chat/dist/ -packages/ragmir-app/dist/ -packages/ragmir-app/src-tauri/target/ -packages/ragmir-app/src-tauri/gen/ packages/ragmir-landing/.astro/ packages/ragmir-landing/dist/ -packages/ragmir-license-webhook/dist/ # Tracked synthetic examples. Keep generated example runtime state ignored. !packages/ragmir-core/examples/ @@ -31,7 +27,30 @@ packages/ragmir-license-webhook/dist/ !packages/ragmir-core/examples/**/.ragmir/sources.txt packages/ragmir-core/examples/**/.ragmir/storage/ packages/ragmir-core/examples/**/.ragmir/access.log +packages/ragmir-core/examples/**/.ragmir/.ragmir-access-log.salt packages/ragmir-core/examples/**/.ragmir/models/ packages/ragmir-core/examples/**/.ragmir/reports/ packages/ragmir-core/examples/**/.ragmir/audio/ .gitnexus + +# Ragmir +.claude/skills/ragmir +.claude/skills/ragmir-audio-summary +.claude/skills/ragmir-markdown-report +.claude/skills/ragmir-legal-dossier +.agents/skills/ragmir +.agents/skills/ragmir-audio-summary +.agents/skills/ragmir-markdown-report +.agents/skills/ragmir-legal-dossier +.kimi/skills/ragmir +.kimi/skills/ragmir-audio-summary +.kimi/skills/ragmir-markdown-report +.kimi/skills/ragmir-legal-dossier +.opencode/skills/ragmir +.opencode/skills/ragmir-audio-summary +.opencode/skills/ragmir-markdown-report +.opencode/skills/ragmir-legal-dossier +.cline/skills/ragmir +.cline/skills/ragmir-audio-summary +.cline/skills/ragmir-markdown-report +.cline/skills/ragmir-legal-dossier diff --git a/.gitnexusignore b/.gitnexusignore index 2c7c627..1f5b1b0 100644 --- a/.gitnexusignore +++ b/.gitnexusignore @@ -17,7 +17,6 @@ packages/*/dist/ packages/*/.astro/ packages/*/.output/ packages/*/target/ -packages/*/src-tauri/target/ coverage/ # Local release/download artifacts diff --git a/AGENTS.md b/AGENTS.md index 016dcb5..b7d0fbc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,397 +1,64 @@ # Ragmir -## Working Rules +## Working rules -- Speak with the user in French. -- Write code, identifiers, commit messages, filenames, and technical comments in English. -- Keep this repository free of private user documents, scans, tax identifiers, API keys, - environment files, or generated vector stores. -- Keep public branding centered on `Ragmir`. Use JCode Labs and Jean-Baptiste Thery for - package scope, repository ownership, and copyright, not as the product name. -- Use `Ragmir Core` only for the technical core package `@jcode.labs/ragmir` and developer-facing - package metadata. User-facing product copy remains `Ragmir`; companion packages are Ragmir add-ons. -- The package is open source under the MIT License unless the user explicitly changes it. -- This package must stay reusable across repositories. Resolve project data from the - caller's working directory or explicit config, not from the package installation path. -- The public CLI name is `rgr`. New docs, generated agent configs, landing command examples, and - setup guidance should use `rgr ...` commands. `ragmir` and `kb` remain deprecated compatibility - bins only and must warn users to migrate to `rgr`. User-facing product copy remains `Ragmir`. -- `rgr init` and `rgr install-skill` must keep generated local Ragmir state ignored in target - repositories. By default, add one `.ragmir/` entry to the target repository `.gitignore`. -- Keep confidentiality features low-friction: local-hash retrieval by default, optional - Transformers.js embeddings with remote model loading disabled by default, redaction before - indexing, metadata-only access logs, bounded MCP retrieval, configurable text-extension ingestion, - and `security-audit` should work from default config. -- Keep public positioning focused on sovereign local RAG for confidential datasets and AI agents. - Avoid claiming universal binary-file support; unsupported proprietary formats need extraction or - dedicated parsers. -- Keep FR/EU sovereignty, GDPR, AI Act, and legal-vertical claims bounded by - `docs/fr-eu-sovereign-positioning.md`. Do not claim blanket compliance, legal advice, or regulated - sovereignty certification without a separate review. -- Keep first-run UX centered on `rgr setup` for full onboarding and `rgr doctor --fix` for safe - repairs. `rgr init`, `rgr install-skill`, and `rgr ingest` remain available as explicit - lower-level commands. -- Keep monorepo source onboarding simple: the `sources` array in `.ragmir/config.json` accepts paths, - glob patterns, and `!` exclusions, and `rgr sources add/list` read and write that array. -- Keep product documentation canonical in the root `README.md`. Package README files under - `packages/*/README.md` are intentionally minimal npm entrypoints and must link clearly to the - GitHub root README because npm displays package README files separately. -- Keep long operational references in `docs/` when the root README can link to them cleanly. The - root README stays the canonical product entrypoint, not a dumping ground for every command table. -- Keep real-corpus dogfooding, business validation, pricing tests, customer ledgers, interview notes, - generated JSON, reports, screenshots, paths, and client details outside Git. Commit only public-safe - aggregate findings or synthetic reproductions. -- For private retrieval dogfooding, use `rgr evaluate --fail-under ` as the local recall - gate, and keep the real corpus, golden query files, and generated evaluation JSON outside Git. -- Use `rgr usage-report --days ` for metadata-only dogfooding summaries; do not read or commit - raw access logs when an aggregate report is enough. -- Keep user-facing titles and marketing surfaces branded as `Ragmir`. Use `Ragmir Core` only for the - technical core package and developer-facing metadata. -- Keep public repository surfaces safe to publish: no active checkout URLs, fake download/update URLs - under real Ragmir domains, private documents, generated `.pid` files, committed secrets, internal - GTM/pricing ledgers, or wording that presents tracked MIT source as proprietary or closed source. - `pnpm public:smoke` enforces the cheap checks. -- The public-surface secret scanner (`scripts/public-surface-smoke.mjs`) runs over every tracked - file, tests included. Never write literal secret-shaped strings in source — PEM `PRIVATE KEY` - headers, `ghp_`/`github_pat_`/`sk_live_`/`sk_test_` tokens, or real checkout URLs. When a test - needs one to exercise redaction or skipping, build it at runtime from parts (e.g. interpolate the - `PRIVATE KEY` label from a variable) so no scannable literal is committed. -- Root `llms.txt` (the [llms.txt](https://llmstxt.org/) convention) and `context7.json` are the - LLM/Context7-facing doc index for this repository. Update `llms.txt` when adding or removing a - top-level `docs/*.md` file worth surfacing to agents, and keep `context7.json`'s - `excludeFolders`/`excludeFiles` in sync with new generated-output or private-data directories. - Registering the repo on context7.com (so it resolves through `resolve-library-id`) is a manual - step on their site; these files only prepare the repo for that step. -- `packages/ragmir-ui` is the shared UI/style foundation adapted from the WorkoutGen landing/UI - approach. It provides the common Tailwind theme and React primitives for both the landing and the - Tauri app; do not import WorkoutGen product copy, assets, analytics, or secrets. -- Prefer the shared shadcn-style primitives from `packages/ragmir-ui` for landing and app surfaces. - Tune reusable component variants or theme tokens before adding per-use raw color, typography, or - shape overrides; primary buttons should stay rounded pill buttons. -- Keep shadcn CLI configuration explicit in `packages/ragmir-ui/components.json` and - `packages/ragmir-landing/components.json`. Use `pnpm dlx shadcn@latest info -c - packages/ragmir-ui` for shared primitives and `pnpm dlx shadcn@latest info -c - packages/ragmir-landing` for the Astro landing surface; do not duplicate landing-local UI - components when an export from `@jcode.labs/ragmir-ui` fits. -- `packages/ragmir-landing` is the Astro static landing package. It must stay telemetry-free by - default; do not add PostHog. Run Astro through - `packages/ragmir-landing/scripts/astro-no-telemetry.mjs` so local dev, check, preview, and build - commands set `ASTRO_TELEMETRY_DISABLED=1`. If analytics are needed later, prefer Cloudflare Web - Analytics. -- Keep Ragmir landing local ports separate from WorkoutGen's Astro landing defaults: `astro dev` - uses port `4322`, and `astro preview` uses port `4323` through `packages/ragmir-landing/astro.config.mjs`. -- The landing hero displays `@jcode.labs/ragmir` npm downloads/month under the primary CTAs. Fetch it - at Astro build time through `packages/ragmir-landing/src/services/npm-downloads.ts`; keep the - `RAGMIR_NPM_DOWNLOADS` environment override for offline or deterministic builds. -- Keep the landing section order library-first: hero, install/library, feature map, agent - integrations, use cases, privacy, honest scope, desktop teaser, FAQ, closing CTA. This sells the - open-source package, optional add-ons, and MCP integration before broader scenarios. -- In landing target-client copy, keep categories distinct: Claude, Codex, Kimi, and GLM-style tools - are cloud agents; OpenCode is a local runner; Ragmir Chat is the optional built-in local synthesis - path; Ollama is only an optional external model runtime. Do not present MCP or Cline as - target-client categories in that section. -- The landing deploy target is Cloudflare Workers Static Assets through - `packages/ragmir-landing/wrangler.jsonc` and the canonical domain `ragmir.com`. Keep - Cloudflare account IDs, tokens, and analytics secrets out of the repository; use local dry-runs - before any protected-branch deployment. -- Ragmir landing should keep the broad WorkoutGen landing signals when content changes: Astro i18n - routes, dark-first theme, self-hosted Inter, the shared `RagmirBackground` port of WorkoutGen's - animated particle/canvas background, rounded pill nav, and language switching. Do not flatten it - into a generic single-language static page or replace the background with an unrelated imitation. -- `packages/ragmir-app` is the cross-platform Tauri desktop/mobile shell. Root `pnpm build` validates - the frontend bundle only; native `tauri build`, `tauri ios *`, and `tauri android *` commands stay - explicit and are not part of npm release validation. -- Desktop app config editing should use structured shadcn-style controls from - `@jcode.labs/ragmir-ui`. JSON remains the `.ragmir/config.json` file format, not the primary app - editing experience. -- For Tauri app file and folder pickers triggered from the UI, prefer the official - `@tauri-apps/plugin-dialog` JavaScript `open(...)` API from the click handler. Do not add Rust - commands that call `blocking_*` dialog APIs for frontend-triggered picker flows; they can deadlock - the Tauri event loop and leave app loading state stuck. If a dedicated Rust picker command becomes - necessary, use the non-blocking `pick_*` callback APIs and validate the real `pnpm dev:app` flow by - clicking the control, opening the native picker, cancelling it, and confirming the loading state - clears. -- Distribute the Ragmir app through direct downloads and sideloadable installers, not App Store or - Play Store flows. Desktop installers and Android APK-style distribution are first-class; iOS stays - deferred until a compliant non-store channel is chosen. -- Keep Android release packaging on the APK/direct-sideload path with - `pnpm --filter @jcode.labs/ragmir-app tauri:android:build`; do not add an iOS release build script - until a compliant non-store distribution path exists. -- Keep direct-download packaging and updater rules in `docs/app-distribution.md`; do not wire the - Tauri updater with placeholder keys or endpoints. -- Keep `packages/ragmir-app` `release:updater-guard` passing. It must fail on partial or placeholder - Tauri updater configuration and stay part of release preflight for direct-download packaging. -- Native desktop CI artifacts may be built only through the manual `Native App Build` workflow. It - uploads artifacts for inspection but must not create GitHub releases, deploy, publish, or bypass - signing/checksum requirements. -- Before native app packaging, run `pnpm --filter @jcode.labs/ragmir-app release:preflight -- --target - ` on the matching release machine. The preflight may check that - secret-bearing environment variables are present, but it must never print their values. -- Keep `packages/ragmir-app` `release:preflight:smoke` passing. It verifies supported native release - targets, keeps iOS out of release packaging, and confirms secret-bearing preflight environment - values are not printed. -- Generate native artifact checksums with `pnpm --filter @jcode.labs/ragmir-app release:checksums` - after Tauri packaging and before publishing direct-download files. The manual Native App Build - workflow uploads the generated `SHA256SUMS` with the bundle artifacts. -- Generate `ragmir-app-release.json` with - `pnpm --filter @jcode.labs/ragmir-app release:manifest -- --target ` - after checksums. The manifest is for static direct-download metadata and must not contain fake - checkout URLs or unsigned-artifact claims. -- App license validation is local and per-major. Keep private signing keys out of the repository; - only inject the public JWK at build time through `VITE_RAGMIR_LICENSE_PUBLIC_KEY_JWK`, and use - `packages/ragmir-app` `license:keypair` / `license:issue` scripts for local license operations. -- Lemon Squeezy integration stays offline until a real webhook service is intentionally deployed: - convert exported order/subscription JSON with `license:from-lemonsqueezy`, keep the unpublished - webhook handler in `packages/ragmir-license-webhook`, and never commit API keys or webhook secrets. -- `packages/ragmir-app/src/lib/project-registry.ts` owns the app-side local project registry. Store - selected project roots there and derive `.ragmir/raw` plus `.ragmir/storage`; keep ingest/query/index - truth in Ragmir Core through the sidecar/CLI surface. -- The app's watched-folder feature is an opt-in polling layer over `rgr ingest`; do not add - background daemons unless the plan explicitly changes. The first Google Drive connector is an - opt-in local-sync folder flow using Google Drive for desktop files already present on disk; do not - add OAuth, Drive API calls, or cloud credentials by default. -- Keep optional audio summaries separate from core ingestion/query behavior. The - `ragmir-audio-summary` skill must prefer `rgr audio` / `@jcode.labs/ragmir-tts`, default to the - Transformers.js WAV path for offline/confidential rendering, use the Edge MP3 path for global - Voice Forge quality only when online TTS is explicitly acceptable, and keep generated audio under - ignored local Ragmir state. -- Keep offline TTS preload explicit: use non-sensitive text for the first remote-model render that - warms `.ragmir/models/tts`, pass `--allow-remote-models` only for that preload, then use `--offline` - for confidential narration. Remote TTS model loading must stay disabled by default. The - operational guide lives in `docs/offline-tts-preload.md`. -- Keep optional local chat separate from core retrieval. `@jcode.labs/ragmir-chat` owns local - Transformers.js text generation for `rgr chat`; `rgr chat setup` is the explicit one-command - preload path for `.ragmir/models/chat`, and normal answers must keep remote model loading disabled - unless the user opts into `--allow-remote-models`. -- In `packages/ragmir-app`, keep model preloads explicit in the Tauri sidecar: setup/prepare flows - may pass `--allow-remote-models` only for one-time embedding/chat/TTS preload commands, while - normal chat answers and audio rendering must use offline/local modes after preload. -- In `packages/ragmir-app`, chat UX must stay non-blocking: append the user message and a pending - assistant bubble before invoking native chat, show visible thinking/streaming/error states, render - assistant Markdown, keep recent thread context local and explicit, and run long native CLI work - through async Tauri commands or `spawn_blocking` so the webview does not freeze. -- In `packages/ragmir-app`, distinguish Ragmir Chat from external coding agents. Local chat is the - only fully private in-app conversation path. Codex, Claude, Kimi, OpenCode, Cline, or generic MCP - modes should be presented as a handoff that generates/copies helper config and prompts for the - external agent; do not build a fake duplicate chat UI for agents that already have their own chat. - Always warn that prompts, retrieved snippets, and pasted confidential content may leave the - machine through cloud agents even though the Ragmir index and MCP server stay local. -- In `packages/ragmir-app`, chat TTS belongs on assistant messages, not as a vague global export: - render the selected answer with `rgr audio --offline`, show per-message rendering/playing/error - states, read generated files through Tauri's scoped asset protocol, and keep TTS preload as an - explicit non-sensitive setup action. -- In `packages/ragmir-app`, prepare direct-folder workspaces in bootstrap order: first write or - repair `.ragmir/config.json` with `sources: ["."]` and the safe `local-hash` provider, then run - `rgr models pull --enable`, then rebuild with Transformers. Do not set - `embeddingProvider: "transformers"` before the model preload exists, or `doctor --fix` can ingest - against a missing local model. -- Keep report generation separate from core retrieval. The `ragmir-markdown-report` skill writes cited - Markdown reports under ignored `.ragmir/reports/` by default and must distinguish evidence, - inference, uncertainty, missing documents, and professional-review items. -- Keep the public source boundary in `docs/source-boundary.md`: every tracked package is MIT source. - Commercial value can gate official signed builds, support, updates, and hosted license delivery, - but tracked app or webhook code must not be described as proprietary. -- Keep commercial distribution rules in `docs/commercial-distribution.md` and hosted checkout/webhook - rules in `docs/payment-webhook-architecture.md`. Do not introduce App Store, Play Store, hosted - document storage, committed payment/license secrets, public pricing tests, or customer validation - ledgers. -- Ingestion must be explicit about files it did not index. Preserve `rgr audit --unsupported`, - unsupported-extension summaries, secret-like file skipping, max file size limits, and checksum-based - stale detection. -- Source discovery should include useful dotfiles (for example `.gitignore`, `.gitlab-ci.yml`, and - `.vscode/settings.json`) while still ignoring generated/runtime directories and skipping - secret-like files explicitly. -- OCR and older binary extraction are opt-in only. Keep PDF OCR behind `pdfOcrCommand` / - `RAGMIR_PDF_OCR_COMMAND`, image OCR behind `imageOcrCommand` / `RAGMIR_IMAGE_OCR_COMMAND`, and - legacy `.doc` extraction behind `legacyWordCommand` / `RAGMIR_LEGACY_WORD_COMMAND`; execute - commands without a shell, require stdout text, and do not add heavy OCR/conversion dependencies or - claim universal scan/image/binary support. -- Keep the repository as a simple pnpm workspace monorepo. Add Turbo only if multiple packages or - apps start needing task caching/orchestration beyond `pnpm --filter`. -- The Node.js and Rust versions are each pinned once, in `mise.toml` (via - [mise](https://mise.jdx.dev/)); Rust is only used by `packages/ragmir-app`'s Tauri shell. Bump - versions there only, not as a hardcoded `node-version` in individual workflow steps. Run `pnpm - bootstrap` (`mise install && pnpm install`) for one-command onboarding. CI (`ci.yml`, - `native-app-build.yml`) installs mise with the official `curl https://mise.run | sh` script in a - plain `run:` step, not the `jdx/mise-action` marketplace action — this repo's Actions permissions - are restricted to `actions/*`, `github/codeql-action/*`, and verified creators, and - `jdx/mise-action` does not qualify. `npm-publish.yml` keeps `actions/setup-node` instead, because - that step also wires the npm registry `.npmrc` for publishing; keep its `node-version` in sync - with `mise.toml` by hand. pnpm stays pinned via Corepack through `packageManager` in - `package.json`, not duplicated in `mise.toml`. Keep mise scoped to toolchain-version pinning — - it is not a package manager or task runner here, so don't mirror `package.json` scripts as mise - tasks and don't wrap launch scripts (`dev:app`, `dev:landing`, `example`) in `mise exec`; that - would create a second source of truth and break the "plain pnpm works without mise" onboarding - path. For local dev, contributors activate mise in their shell (`mise activate`) so the pinned - Node/Rust land on `PATH` via shims and every `pnpm` script uses the CI toolchain automatically. -- Keep Ragmir core free of Ollama and LLM generation. `embeddingProvider: "local-hash"` supports - ingestion, search, MCP, and cited retrieval without a model server, but it must not be described as - equivalent to semantic retrieval. `embeddingProvider: "transformers"` is the optional semantic - embedding path; `@jcode.labs/ragmir-chat` is the optional local generation add-on. -- Keep `packages/ragmir-core/examples/sovereign-rag-demo` synthetic and safe to commit. It exists for - package/user testing only; never place real confidential documents there. -- `packages/ragmir-core/examples/library-api-demo` is the local library-API smoke (`pnpm example`). It - `import`s `@jcode.labs/ragmir` via Node self-referencing so it always exercises the local - `packages/ragmir-core/dist` build, never the npm-published package, and it reuses the - `sovereign-rag-demo` synthetic corpus rather than adding a second one. `dist/` is a gitignored build - output: build it first with `pnpm build` (or run `pnpm example`, which builds first), then run - `node packages/ragmir-core/dist/cli.js`. Never use `npx rgr`, which would resolve the released npm - version. -- Use Context7 before changing dependencies or public APIs that rely on external libraries. -- Run `pnpm validate` before opening a release pull request or publishing. It covers - Biome, dependency security audit, TypeScript, Vitest, build output, production CLI/MCP smoke - tests, npm package metadata, semantic-release wiring, and release artifacts. -- Do not publish from a local machine or direct push to `main`. npm releases must go through - the protected `Release npm` GitHub Actions workflow on `main`; semantic-release derives the - version from Conventional Commits, prepares package tarballs, publishes `@jcode.labs/ragmir-tts` - and `@jcode.labs/ragmir-chat` first, then publishes `@jcode.labs/ragmir`. -- Use Git Flow locally: `main` is production, `develop` is integration, feature work starts from - `develop` under `feature/*`. Do not deploy or publish from feature branches. +- Speak with users in French. Keep code, identifiers, comments, file names, and commit messages in English. +- Keep the repository free of private documents, generated `.ragmir/` state, credentials, and environment files. +- Ragmir is a fully open-source MIT project. Do not add activation, account, hosted-storage, native-shell, or cloud-vendor integrations. +- The repository is a pnpm workspace. Use the pinned Node version from `mise.toml`; Rust is not part of this project. +- Start feature work from `develop` on `feature/*`, use pull requests into `develop`, and never publish or deploy without explicit confirmation. -## AI Coding Agent Guardrails +## Product boundary -These rules are binding for every AI coding agent working in this repository (Claude Code, Codex, and -any other), because several agents may run against this repo in parallel. +- `packages/ragmir-core` provides `@jcode.labs/ragmir`: the `rgr` CLI, TypeScript library, MCP server, and portable skills. +- `packages/ragmir-chat` is the optional local GGUF synthesis add-on used by `rgr chat`. +- `packages/ragmir-tts` is the optional local/offline audio add-on used by `rgr audio`. +- `packages/ragmir-landing` is a self-contained, telemetry-free Astro site. Keep it static, open-source focused, and free of vendor deployment configuration. +- Ragmir Core stays retrieval-first: `local-hash` supports offline retrieval, `transformers` is the explicit semantic option, and local chat remains a separate add-on. +- Long-running Node.js processes use one `RagmirClient` per project root and close it during shutdown. Keep the top-level API for one-shot scripts. +- Ragmir does not provide an HTTP server or fixed port. A network-facing host owns transport security, authentication, authorization, and rate limits. +- Ingestion is serialized per local index inside one Node.js process; do not claim a distributed writer lock. +- Public copy must lead with model-agnostic Core and the choice between the user's preferred AI or automation and a fully local consumer. Qwen and Gemma are optional Chat profiles, never Core or MCP requirements. -- **Never create, rename, delete, switch, or reset Git branches on your own.** Ask the user for - explicit confirmation first, and state the exact branch name and base you intend to use. A - high-level task is not blanket permission to spawn branches — confirm the branch itself. -- **Always follow the repository Git Flow.** `main` is production and `develop` is integration; both - are protected and only change through a pull request with green required checks (Quality gate, - Commitlint, Analyze TypeScript). Start work from `develop` under `feature/*` (fixes `fix/*`, chores - `chore/*`), open a PR into `develop`, and promote `develop` to `main` with a release PR. Never - commit or push directly to `main` or `develop`, and never force-push either branch. -- **Do not open or merge pull requests, or trigger a release / npm publish, without explicit - confirmation.** The protected `Release npm` workflow and its `npm-publish` environment approval are - the only publish path. -- **Reuse the branch or PR the user already approved instead of creating new ones.** Do not - proliferate short-lived branches; when a temporary branch is genuinely required (for example a - protected-branch back-merge), name it clearly and delete it once merged. -- **Respect other agents' work.** Before editing, run `git status` and check for other running agents - or processes; never stage, commit, or discard uncommitted changes you did not make. -- **Capture durable agent lessons in the repo.** When a bug, review, or user correction reveals a - repeatable AI-agent trap, update the nearest shared agent instructions in the same task: - `AGENTS.md` for cross-agent project rules, and `CLAUDE.md` only for Claude Code-specific behavior. - Keep the entry concise, factual, and tied to a reproducible workflow; do not rely on tool-local - memory as the only place for team-shared rules. +## Privacy and ingestion -## Coding Conventions +- Resolve project data from the caller’s working directory or explicit configuration, never from the package installation path. +- Keep `.ragmir/` ignored. Use local-hash retrieval by default, redact before indexing, keep access logs metadata-only, and bound MCP retrieval. +- External extraction remains opt-in. OCR only runs for blank PDF pages through a configured local command, never a shell or cloud service. +- Do not claim universal binary support, blanket compliance, legal advice, or certification. -General principles (KISS, DRY, YAGNI, SOLID) as applied in this codebase. Match the surrounding style. +## Documentation -- One responsibility per module. The ingest pipeline is split on purpose: `files` discovers, - `parsing` extracts, `redaction` strips, `chunking` splits, `embeddings` vectorizes, `store` - persists, `query` retrieves. Add logic to the module that owns the concern, or a new small module. -- No duplicated logic. Reuse existing helpers (`loadConfig`, `embedText`/`embedTexts`, - `openRowsTable`, `redactText`, `supportedExtensions`, `recordAccess`); extract instead of copying. - `embedText` delegating to `embedTexts` is the reference pattern. -- No dead or obsolete code. Delete replaced code, unused exports, and commented-out blocks in the - same change. `dist/` is gitignored build output: regenerate it locally with `pnpm build` before - running CLI/MCP smoke or the library-API demo, but do not commit it. -- No magic strings or numbers. Name meaningful literals as constants, and put shared paths, provider - defaults, and ignore constants in `packages/ragmir-core/src/defaults.ts` rather than copying them across - modules. -- Validate at the boundary, narrow inside. Use Zod at external edges (config in `config.ts`, MCP - inputs in `mcp.ts`) and CLI parsers (`parsePositiveInt`); trust the types past that point. -- Type-guard instead of casting. Prefer runtime guards over `as`/`!` (`hasToList`, `isNumberArray`, - `isNumberMatrix`); LanceDB row casts at the `store`/`query` driver boundary are the only exception. -- Named exports only; keep the public surface explicit in `index.ts`. Functions stay small and pure; - private helpers sit below the exported function in the same file. -- Comments explain why, not what; the codebase is near comment-free. Only the CLI (`cli.ts`) writes - to stdout/stderr — library, MCP, and pipeline code return data, never log. -- YAGNI: no options, providers, or abstractions ahead of a real need. +- The root `README.md` is the short canonical entrypoint: what Ragmir is, installation, first use, library API, MCP, and links to focused docs. +- Keep `docs/` concise and task-oriented: CLI, API, configuration, agent integration, troubleshooting, local chat, and local TTS. Remove future plans and obsolete surfaces rather than documenting them. +- Package READMEs are brief npm entrypoints that link to the root README. +- Keep `llms.txt` and `context7.json` aligned with public documentation and generated-output exclusions. +- When code changes public behavior, commands, configuration, supported formats, architecture, or product claims, update the relevant docs and landing in the same change. For internal-only changes, verify both surfaces and leave them unchanged when no update is needed. -## Architecture +## Validation -- `packages/ragmir-core` is Ragmir Core, published as `@jcode.labs/ragmir`. -- `packages/ragmir-core/src/cli.ts` exposes the `rgr` CLI and keeps `ragmir`/`kb` as deprecated - compatibility bins. -- The `rgr` CLI supports global `--project-root ` for sidecar/app usage. Prefer it when a - process cannot or should not change cwd for each selected knowledge base. -- `packages/ragmir-core/src/doctor.ts` owns the user-facing readiness diagnosis behind - `rgr doctor`. -- `packages/ragmir-core/src/config.ts` resolves `.ragmir/config.json` from the target repository. -- `packages/ragmir-core/src/defaults.ts` owns shared default paths, provider defaults, and generated-state ignore - constants. Keep config/init/security/gitignore aligned through this module instead of copying - literals. -- `packages/ragmir-core/src/sources.ts` owns the `sources` array management API used by - `rgr sources add/list` (reads/writes `.ragmir/config.json`); file discovery itself remains in - `files.ts`. -- `packages/ragmir-core/src/skill.ts` owns agent skill installation and the per-agent - `agentHelpers`/MCP config generation (`AgentHelperFile`) behind `rgr setup` and - `rgr install-skill`/`install-agent`. Add a new agent target through `SUPPORTED_AGENT_TARGETS` - and its helper builder here, not by hand-listing agents in `cli.ts`. -- `packages/ragmir-core/src/ingest.ts` parses supported files, chunks text, embeds chunks, and rebuilds the - local LanceDB table. Normal ingest is incremental and reuses rows whose checksum/provider/model - still match; `--rebuild` forces a full re-index. -- `packages/ragmir-core/src/parsing.ts` uses proven parsers for high-risk Office formats: - Mammoth for `.docx` and read-excel-file for `.xlsx`. Keep the lightweight XML ZIP parser for - `.pptx`, OpenDocument, and EPUB unless tests show fidelity gaps. Legacy `.xls` workbooks are not - supported by default; convert them to `.xlsx`, CSV, PDF, HTML, or text before ingesting. -- `packages/ragmir-core/src/query.ts` performs hybrid retrieval (vector candidates plus bounded lexical - BM25 scoring) and returns cited retrieval context; LLM synthesis belongs outside Ragmir core. -- `packages/ragmir-core/src/research.ts` runs the audit-backed multi-query research pass behind - `rgr research`, combining `query.ts` search results with `ingest.ts` audit coverage. -- `packages/ragmir-core/src/evaluate.ts` scores retrieval recall against a golden query file behind - `rgr evaluate`, for the local recall gate described above. -- `packages/ragmir-core/src/mcp.ts` exposes Ragmir as an MCP stdio server for agents. -- `packages/ragmir-tts` is the standalone TTS package used by `rgr audio`; it uses `edge-tts` for - high-quality MP3 when available and Transformers.js for offline WAV rendering. -- `packages/ragmir-chat` is the optional local chat package used by `rgr chat`; it uses - Transformers.js text generation over cited Ragmir retrieval context and must not make Ragmir Core - depend on Ollama or any hosted model API. -- `packages/ragmir-ui` owns shared React UI primitives and Tailwind theme tokens used by Ragmir - product surfaces. -- `packages/ragmir-landing` owns the static Astro landing page. -- `packages/ragmir-app` owns the Tauri app shell for desktop and mobile. -- `packages/ragmir-license-webhook` owns the unpublished MIT-licensed Cloudflare Worker handler for - Lemon Squeezy webhook signature verification, KV-backed idempotency records, and local `RAGMIR1` - license issuance. It must stay undeployed until real provider variants, secrets, - storage/idempotency, and a release surface exist. Its `wrangler.jsonc` must keep placeholder KV - namespace IDs until real Cloudflare resources are provisioned; use `cf:dry-run` only before - protected deployment. -- The app integrates Ragmir Core through the existing `rgr` CLI/MCP surface. Keep the sidecar - decision and command allowlist in `docs/app-sidecar-architecture.md`; the current native bridge is - the bounded `run_ragmir_command` Tauri command, and `externalBin` stays deferred until real platform - sidecar binaries exist. -- `packages/ragmir-core/src/gitignore.ts` owns target-repository `.gitignore` entries for local generated Ragmir - state. -- `packages/ragmir-core/src/security.ts`, `packages/ragmir-core/src/redaction.ts`, and - `packages/ragmir-core/src/access-log.ts` own the - privacy and confidentiality hardening layer. -- `packages/ragmir-core/skills/ragmir/SKILL.md` is the bundled portable agent skill. -- `packages/ragmir-core/skills/ragmir-audio-summary/SKILL.md` is the optional bundled audio-summary skill. -- `packages/ragmir-core/skills/ragmir-markdown-report/SKILL.md` is the optional bundled Markdown-report - skill. -- `rgr setup` must keep generating agent-specific MCP helpers for easy local use by default: - `.ragmir/claude-mcp-server.json` for `claude mcp add-json`, `.ragmir/codex-mcp.toml` for Codex - config layers, `.ragmir/kimi-mcp.json` for Kimi, `.ragmir/opencode.jsonc` for OpenCode, and - `.ragmir/cline-mcp.json` for Cline. Keep `--agents` available on setup/install-skill so a target - repository can generate only the helpers it uses and remove stale unselected helpers. -- `rgr setup --semantic` is the first-run opt-in path for higher-quality semantic retrieval. It - may download the configured Transformers.js embedding model once, then must leave - `transformersAllowRemoteModels` false for normal confidential indexing. -- Keep `--mcp-name`, `--mcp-command`, and repeatable `--mcp-arg` available on setup/install-skill - so repositories can generate MCP helper files for a stable server name or local wrapper script - without post-processing `.ragmir/`. -- Keep prompt routing local, deterministic, and opt-in. `rgr route-prompt` and MCP - `ragmir_route_prompt` may help agent hooks decide when to call Ragmir, but they must not store raw - prompts, call an LLM, or perform retrieval themselves. -- `rgr install-agent` owns native skill discovery for the main supported coding agents. Keep - `--agents claude|codex|kimi|opencode|cline` targeted so a user can install only the agent they use, - with project scope by default and user scope available through `--scope user`. -- Keep `.ragmir/skills/` as the canonical skill source in target repositories. Native agent folders - created by `rgr install-agent` should link to that source by default; use copy mode only as a - compatibility fallback for runtimes or filesystems that cannot follow symlinks. -- `packages/ragmir-core/examples/sovereign-rag-demo` is the tracked synthetic test workspace for manual - and package validation. -- `.ragmir/`, `.claude/`, `.codex/`, and `.agents/` are local user data or generated agent state in - target repositories and must not be committed. +- Run the smallest relevant check while editing and `pnpm validate` before a release pull request. +- Reconcile `git status` with the intended scope before staging. Never stage secrets or generated local state. +- Use Conventional Commits. Commit and push only when the user authorizes them. + +## Code conventions + +- Keep responsibilities small and reuse the existing Core modules for discovery, parsing, redaction, chunking, embeddings, storage, and retrieval. +- Validate external inputs at boundaries with Zod or CLI parsers. Prefer type guards over casts and named exports over defaults. +- Only the CLI writes to stdout or stderr. Pipeline and library modules return values. + +## GitNexus + +The repository is indexed as `jcode-ragmir`. + +- Before editing a function, class, or method, run upstream impact analysis and report HIGH or CRITICAL risk before continuing. +- Run `gitnexus_detect_changes()` before committing and refresh the index once after the final commit. +- Use `gitnexus_rename` for symbol renames. If the index is stale, run `npx gitnexus analyze` before relying on it. # GitNexus — Code Intelligence -This project is indexed by GitNexus as **jcode-ragmir** (2311 symbols, 4841 relationships, 190 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **jcode-ragmir** (2005 symbols, 4448 relationships, 165 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d2761..3e755a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,70 +1,32 @@ # Changelog -## Unreleased +Ragmir publishes its canonical version history through +[GitHub Releases](https://github.com/jcode-works/jcode-ragmir/releases). Each release is generated +by semantic-release from the Conventional Commits merged into `main`, after the repository +validation workflow passes. -- Rename the public CLI commands to `rgr` and `rgr-tts`. -- Keep `ragmir`, `kb`, and `ragmir-tts` as deprecated compatibility bins that warn users to migrate - to `rgr` / `rgr-tts`. -- Add deterministic prompt routing through `rgr route-prompt` and MCP `ragmir_route_prompt`. -- Move the production landing target to `ragmir.com` and keep staging on `staging.ragmir.jcode.works`. +This file is intentionally a stable entrypoint instead of a second, manually maintained copy of +the version history. -## 0.4.2 - 2026-06-29 +## Release history -- Add `rgr doctor` to diagnose initialization, index freshness, security posture, and next steps. -- Make `rgr audio` and `rgr-tts` default to the offline/confidential Transformers.js WAV path; - Edge MP3 now requires an explicit `--engine edge` command. -- Stop indexing the generated `private/README.md` helper file created by `rgr init`. -- Improve onboarding output from `rgr init` and `rgr install-skill`. +- [Latest release](https://github.com/jcode-works/jcode-ragmir/releases/latest) +- [All releases and version comparisons](https://github.com/jcode-works/jcode-ragmir/releases) +- [Current TypeScript API reference](./docs/api-reference.md) -## 0.4.1 - 2026-06-29 +Each GitHub release contains generated feature, fix, and compatibility notes plus the verification +artifacts produced by the release workflow. -- Add an Edge-compatible Ragmir TTS engine so `rgr audio` can match the global Voice Forge quality - path with `edge-tts`, `fr-FR-DeniseNeural`, and MP3 output. -- Keep Transformers.js WAV rendering as the explicit offline/confidential path. -- Remove duplicated governance documents from package directories; root project docs are the single - source of truth. +## API compatibility -## 0.4.0 - 2026-06-28 +Ragmir follows Semantic Versioning: -- Reposition Ragmir as sovereign local RAG for confidential datasets and AI agents. -- Expand default ingestion to common text, Office/OpenDocument, data, config, log, and source-code - file types. -- Add `includeExtensions` / `RAGMIR_INCLUDE_EXTENSIONS` for custom UTF-8 text file extensions. -- Add the optional `ragmir-audio-summary` bundled skill for confidential audio summaries. -- Install both the main Ragmir skill and optional audio-summary skill with `rgr install-skill`. -- Improve agent guidance for deep multi-query retrieval before synthesis. -- Make Ragmir core retrieval-only: `rgr ask` now returns cited context for external agents or LLMs - instead of generating answers internally. -- Add optional Transformers.js semantic embeddings through `embeddingProvider: "transformers"`. -- Remove Ollama providers and keep `embeddingProvider: "local-hash"` as the no-model default. -- Move the repository to a simple pnpm workspace monorepo without adding Turbo. -- Move the core `@jcode.labs/ragmir` package into `packages/ragmir-core`. -- Add `@jcode.labs/ragmir-tts` for plug-and-play JS/ONNX WAV rendering without Python or ffmpeg. -- Add `rgr audio` and update the audio-summary skill to use Ragmir TTS before advanced fallback - engines. +- `fix` commits produce patch releases. +- `feat` commits produce minor releases. +- commits with a `BREAKING CHANGE` footer produce major releases and surface the compatibility + change in the generated release notes. +- documentation commits produce patch releases so the npm documentation stays current. +- landing-only commits do not publish the library packages. -## 0.3.0 - 2026-06-28 - -- Add confidentiality hardening defaults: built-in redaction before indexing, metadata-only access - logs, and bounded MCP retrieval. -- Add `rgr security-audit` for zero-telemetry, provider, redaction, gitignore, storage, and - MCP posture checks. -- Add `rgr destroy-index --yes` to remove generated vector indexes. -- Add release verification artifacts: npm tarball, SHA256 checksums, SBOM, and manifest. -- Document air-gapped operation, threat model, MCP hardening, and secure deletion limits. - -## 0.2.1 - 2026-06-28 - -- Add GitHub Sponsors funding metadata and document suggested sponsor tiers. -- Add maintainer positioning for Jean-Baptiste Thery and JCode Labs in the README. -- Make `rgr init` and `rgr install-skill` automatically keep `.ragmir/` ignored by Git. - -## 0.2.0 - 2026-06-28 - -- Rename public product branding to Ragmir while keeping the JCode Labs npm scope. -- Add the bundled portable `ragmir` agent skill. -- Add the MCP stdio server with `ragmir_status`, `ragmir_search`, `ragmir_ask`, and - `ragmir_audit`. -- Add production smoke coverage for the built CLI and MCP server. -- Add Biome, commitlint, publint, CodeQL, Dependabot grouping, protected npm publishing, - and open-source contribution/security documentation. +Check the release notes for the version you plan to install, then compare the +[API reference](./docs/api-reference.md) when upgrading across a major version. diff --git a/CLAUDE.md b/CLAUDE.md index aa7e5ff..df101b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,178 +1,45 @@ -# CLAUDE.md +# Ragmir development notes -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Commands -`AGENTS.md` is the authoritative source for shared rules — working rules, coding conventions, and -high-level architecture. Read it first. This file adds only the Claude Code operational details and -non-obvious traps that matter when editing here, without duplicating `AGENTS.md`. +```bash +pnpm bootstrap +pnpm validate +pnpm dev:landing +pnpm example +``` -## Branches, PRs, and releases — confirm first +Use `pnpm --filter @jcode.labs/ragmir - - diff --git a/packages/ragmir-app/package.json b/packages/ragmir-app/package.json deleted file mode 100644 index ac99fae..0000000 --- a/packages/ragmir-app/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@jcode.labs/ragmir-app", - "version": "0.4.10", - "private": true, - "description": "Cross-platform Ragmir desktop and mobile shell built with Tauri.", - "type": "module", - "license": "MIT", - "scripts": { - "build": "tsc -p tsconfig.json --noEmit && vite build", - "check": "tsc -p tsconfig.json --noEmit", - "dev": "tauri dev", - "dev:web": "vite", - "license:from-lemonsqueezy": "node scripts/license-from-lemonsqueezy.mjs", - "license:lemonsqueezy:smoke": "node scripts/license-from-lemonsqueezy-smoke.mjs", - "license:issue": "node scripts/license-issue.mjs", - "license:keypair": "node scripts/license-keypair.mjs", - "preview": "vite preview", - "release:bundle:verify": "node scripts/native-bundle-verify.mjs", - "release:bundle:verify:smoke": "node scripts/native-bundle-verify-smoke.mjs", - "release:checksums": "node scripts/native-checksums.mjs", - "release:checksums:smoke": "node scripts/native-checksums-smoke.mjs", - "release:manifest": "node scripts/native-release-manifest.mjs", - "release:manifest:smoke": "node scripts/native-release-manifest-smoke.mjs", - "release:preflight": "node scripts/release-preflight.mjs", - "release:preflight:smoke": "node scripts/release-preflight-smoke.mjs", - "release:updater-guard": "node scripts/updater-guard.mjs", - "release:updater-guard:smoke": "node scripts/updater-guard-smoke.mjs", - "tauri": "tauri", - "tauri:android:build": "tauri android build --apk", - "tauri:android:dev": "tauri android dev", - "tauri:android:init": "tauri android init", - "tauri:build": "tauri build", - "tauri:build:linux": "tauri build --ci --bundles deb,appimage", - "tauri:build:macos": "tauri build --ci --bundles app,dmg", - "tauri:build:windows": "tauri build --ci --bundles nsis,msi", - "tauri:dev": "tauri dev", - "tauri:ios:dev": "tauri ios dev", - "tauri:ios:init": "tauri ios init" - }, - "dependencies": { - "@jcode.labs/ragmir-ui": "workspace:*", - "@tauri-apps/api": "^2.10.0", - "@tauri-apps/plugin-dialog": "^2.7.1", - "lucide-react": "^1.21.0", - "react": "19.2.7", - "react-dom": "19.2.7", - "react-markdown": "^10.1.0" - }, - "devDependencies": { - "@tauri-apps/cli": "^2.10.0", - "@tailwindcss/vite": "^4.3.1", - "@types/react": "19.2.17", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "^5.2.0", - "tailwindcss": "^4.3.1", - "typescript": "^5.9.3", - "vite": "^8.0.13" - } -} diff --git a/packages/ragmir-app/public/favicon.svg b/packages/ragmir-app/public/favicon.svg deleted file mode 100644 index a53d010..0000000 --- a/packages/ragmir-app/public/favicon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/ragmir-app/public/fonts/inter-latin-500-normal.woff2 b/packages/ragmir-app/public/fonts/inter-latin-500-normal.woff2 deleted file mode 100644 index 54f0a59..0000000 Binary files a/packages/ragmir-app/public/fonts/inter-latin-500-normal.woff2 and /dev/null differ diff --git a/packages/ragmir-app/public/fonts/inter-latin-600-normal.woff2 b/packages/ragmir-app/public/fonts/inter-latin-600-normal.woff2 deleted file mode 100644 index d189794..0000000 Binary files a/packages/ragmir-app/public/fonts/inter-latin-600-normal.woff2 and /dev/null differ diff --git a/packages/ragmir-app/public/fonts/inter-latin-700-normal.woff2 b/packages/ragmir-app/public/fonts/inter-latin-700-normal.woff2 deleted file mode 100644 index a68fb10..0000000 Binary files a/packages/ragmir-app/public/fonts/inter-latin-700-normal.woff2 and /dev/null differ diff --git a/packages/ragmir-app/public/fonts/inter-latin-900-normal.woff2 b/packages/ragmir-app/public/fonts/inter-latin-900-normal.woff2 deleted file mode 100644 index 4db8333..0000000 Binary files a/packages/ragmir-app/public/fonts/inter-latin-900-normal.woff2 and /dev/null differ diff --git a/packages/ragmir-app/scripts/license-core.mjs b/packages/ragmir-app/scripts/license-core.mjs deleted file mode 100644 index 20cb409..0000000 --- a/packages/ragmir-app/scripts/license-core.mjs +++ /dev/null @@ -1,108 +0,0 @@ -import { randomUUID, webcrypto } from "node:crypto" -import { readFile } from "node:fs/promises" - -export const PRODUCT_ID = "ragmir-desktop" -export const LICENSE_FORMAT = "RAGMIR1" -export const LICENSE_TIERS = new Set(["solo", "team", "company"]) - -export async function readPrivateKey(values) { - if (values["private-key"]) { - return JSON.parse(await readFile(values["private-key"], "utf8")) - } - if (process.env.RAGMIR_LICENSE_PRIVATE_KEY_JWK) { - return JSON.parse(process.env.RAGMIR_LICENSE_PRIVATE_KEY_JWK) - } - throw new Error("Provide --private-key or RAGMIR_LICENSE_PRIVATE_KEY_JWK.") -} - -export async function signLicensePayload(payload, privateKeyJwk) { - const encodedPayload = base64Url(JSON.stringify(payload)) - const key = await webcrypto.subtle.importKey( - "jwk", - privateKeyJwk, - { name: "ECDSA", namedCurve: "P-256" }, - false, - ["sign"], - ) - const signature = await webcrypto.subtle.sign( - { name: "ECDSA", hash: "SHA-256" }, - key, - new TextEncoder().encode(encodedPayload), - ) - - return `${LICENSE_FORMAT}.${encodedPayload}.${base64Url(signature)}` -} - -export function licensePayload(values) { - const holder = required(values.holder, "--holder is required.") - return { - product: PRODUCT_ID, - licenseId: values["license-id"] ?? randomUUID(), - holder, - tier: licenseTier(values.tier), - majorVersion: positiveInteger(values["major-version"] ?? "0", "major-version"), - issuedAt: isoDate(values["issued-at"] ?? new Date().toISOString(), "issued-at"), - updatesUntil: isoDate(values["updates-until"] ?? yearsFromNow(2), "updates-until"), - ...(values["expires-at"] ? { expiresAt: isoDate(values["expires-at"], "expires-at") } : {}), - } -} - -export function licenseTier(value) { - const tier = value ?? "solo" - if (LICENSE_TIERS.has(tier)) { - return tier - } - throw new Error("tier must be solo, team, or company.") -} - -export function positiveInteger(value, label) { - const parsed = Number.parseInt(value, 10) - if (!Number.isInteger(parsed) || parsed < 0) { - throw new Error(`${label} must be a positive integer.`) - } - return parsed -} - -export function isoDate(value, label) { - const date = new Date(value) - if (!Number.isFinite(date.getTime())) { - throw new Error(`${label} must be a valid date.`) - } - return date.toISOString() -} - -export function yearsFromNow(years, from = new Date()) { - const date = new Date(from) - date.setFullYear(date.getFullYear() + years) - return date.toISOString() -} - -export function required(value, message) { - if (!value) { - throw new Error(message) - } - return value -} - -export function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = "true" - continue - } - parsed[key] = next - index += 1 - } - return parsed -} - -function base64Url(value) { - const bytes = - typeof value === "string" ? Buffer.from(value, "utf8") : Buffer.from(new Uint8Array(value)) - return bytes.toString("base64url") -} diff --git a/packages/ragmir-app/scripts/license-from-lemonsqueezy-smoke.mjs b/packages/ragmir-app/scripts/license-from-lemonsqueezy-smoke.mjs deleted file mode 100644 index 74e5eea..0000000 --- a/packages/ragmir-app/scripts/license-from-lemonsqueezy-smoke.mjs +++ /dev/null @@ -1,173 +0,0 @@ -import { spawnSync } from "node:child_process" -import { webcrypto } from "node:crypto" -import { mkdtemp, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const CONVERTER_SCRIPT = join(SCRIPT_DIR, "license-from-lemonsqueezy.mjs") -const tempDir = await mkdtemp(join(tmpdir(), "ragmir-lemonsqueezy-smoke-")) - -try { - const { privateKeyJwk, publicKeyJwk } = await generateKeyPair() - const privateKeyPath = join(tempDir, "private.jwk") - await writeFile(privateKeyPath, `${JSON.stringify(privateKeyJwk)}\n`, { mode: 0o600 }) - - const orderOutput = await convertEvent({ - privateKeyPath, - eventName: "order-created.json", - event: syntheticOrderEvent(), - }) - await assertLicenseOutput(orderOutput, publicKeyJwk, { - eventName: "order_created", - sourceType: "orders", - tier: "solo", - licenseId: "lemonsqueezy:order:order-synthetic-001", - expiresAt: null, - }) - - const subscriptionOutput = await convertEvent({ - privateKeyPath, - eventName: "subscription-created.json", - event: syntheticSubscriptionEvent(), - }) - await assertLicenseOutput(subscriptionOutput, publicKeyJwk, { - eventName: "subscription_created", - sourceType: "subscriptions", - tier: "team", - licenseId: "lemonsqueezy:subscription:sub-synthetic-001", - expiresAt: "2026-08-01T00:00:00.000Z", - }) - - console.log("Lemon Squeezy license smoke passed.") -} finally { - await rm(tempDir, { recursive: true, force: true }) -} - -async function generateKeyPair() { - const keyPair = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [ - "sign", - "verify", - ]) - return { - privateKeyJwk: await webcrypto.subtle.exportKey("jwk", keyPair.privateKey), - publicKeyJwk: await webcrypto.subtle.exportKey("jwk", keyPair.publicKey), - } -} - -async function convertEvent({ privateKeyPath, eventName, event }) { - const eventPath = join(tempDir, eventName) - await writeFile(eventPath, `${JSON.stringify(event, null, 2)}\n`, { mode: 0o600 }) - - const result = spawnSync( - process.execPath, - [ - CONVERTER_SCRIPT, - "--event", - eventPath, - "--private-key", - privateKeyPath, - "--major-version", - "0", - "--json", - ], - { encoding: "utf8", shell: false }, - ) - - if (result.status !== 0) { - throw new Error(result.stderr || result.stdout || "license conversion failed") - } - - return JSON.parse(result.stdout) -} - -async function assertLicenseOutput(output, publicKeyJwk, expected) { - assertEqual(output.eventName, expected.eventName, "eventName") - assertEqual(output.sourceType, expected.sourceType, "sourceType") - assertEqual(output.tier, expected.tier, "tier") - assertEqual(output.licenseId, expected.licenseId, "licenseId") - assertEqual(output.expiresAt ?? null, expected.expiresAt, "expiresAt") - - const payload = await verifyLicenseKey(output.licenseKey, publicKeyJwk) - assertEqual(payload.product, "ragmir-desktop", "payload.product") - assertEqual(payload.holder, "Synthetic Buyer", "payload.holder") - assertEqual(payload.tier, expected.tier, "payload.tier") - assertEqual(payload.licenseId, expected.licenseId, "payload.licenseId") - assertEqual(payload.expiresAt ?? null, expected.expiresAt, "payload.expiresAt") -} - -async function verifyLicenseKey(licenseKey, publicKeyJwk) { - const parts = String(licenseKey).split(".") - if (parts.length !== 3 || parts[0] !== "RAGMIR1") { - throw new Error("license key must use RAGMIR1.payload.signature format") - } - - const [, encodedPayload, encodedSignature] = parts - const key = await webcrypto.subtle.importKey( - "jwk", - publicKeyJwk, - { name: "ECDSA", namedCurve: "P-256" }, - false, - ["verify"], - ) - const ok = await webcrypto.subtle.verify( - { name: "ECDSA", hash: "SHA-256" }, - key, - Buffer.from(encodedSignature, "base64url"), - new TextEncoder().encode(encodedPayload), - ) - - if (!ok) { - throw new Error("license signature verification failed") - } - - return JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8")) -} - -function syntheticOrderEvent() { - return { - meta: { event_name: "order_created" }, - data: { - type: "orders", - id: "order-synthetic-id", - attributes: { - identifier: "order-synthetic-001", - status: "paid", - created_at: "2026-06-01T00:00:00.000Z", - user_name: "Synthetic Buyer", - user_email: "buyer@example.test", - product_name: "Ragmir Desktop", - variant_name: "Ragmir Desktop Solo", - }, - }, - } -} - -function syntheticSubscriptionEvent() { - return { - meta: { event_name: "subscription_created" }, - data: { - type: "subscriptions", - id: "sub-synthetic-001", - attributes: { - status: "active", - created_at: "2026-06-01T00:00:00.000Z", - renews_at: "2026-08-01T00:00:00.000Z", - user_name: "Synthetic Buyer", - user_email: "buyer@example.test", - product_name: "Ragmir Desktop", - variant_name: "Ragmir Desktop Team", - custom_data: { - ragmir_tier: "team", - }, - }, - }, - } -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label} mismatch: expected ${expected}, got ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/license-from-lemonsqueezy.mjs b/packages/ragmir-app/scripts/license-from-lemonsqueezy.mjs deleted file mode 100644 index 4ff3ffb..0000000 --- a/packages/ragmir-app/scripts/license-from-lemonsqueezy.mjs +++ /dev/null @@ -1,195 +0,0 @@ -import { readFile } from "node:fs/promises" -import { - isoDate, - licensePayload, - licenseTier, - parseArgs, - positiveInteger, - readPrivateKey, - required, - signLicensePayload, - yearsFromNow, -} from "./license-core.mjs" - -const SUBSCRIPTION_ACTIVE_STATUSES = new Set(["active", "on_trial", "past_due"]) -const DEFAULT_UPDATE_YEARS = 2 - -const args = parseArgs(process.argv.slice(2)) -const event = await readEvent(args.event) -const draft = licenseDraftFromLemonSqueezy(event, args) -const privateKeyJwk = await readPrivateKey(args) -const key = await signLicensePayload(draft.payload, privateKeyJwk) - -if (args.json) { - console.log( - JSON.stringify( - { - eventName: draft.eventName, - sourceType: draft.sourceType, - holder: draft.payload.holder, - tier: draft.payload.tier, - licenseId: draft.payload.licenseId, - updatesUntil: draft.payload.updatesUntil, - expiresAt: draft.payload.expiresAt ?? null, - licenseKey: key, - }, - null, - 2, - ), - ) -} else { - console.log(key) -} - -async function readEvent(target) { - const raw = - !target || target === "-" - ? await new Promise((resolve, reject) => { - let data = "" - process.stdin.setEncoding("utf8") - process.stdin.on("data", (chunk) => { - data += chunk - }) - process.stdin.on("end", () => resolve(data)) - process.stdin.on("error", reject) - }) - : await readFile(target, "utf8") - return JSON.parse(raw) -} - -function licenseDraftFromLemonSqueezy(event, values) { - const data = record(event.data ?? event) - const attributes = record(data.attributes ?? event.attributes ?? event) - const sourceType = stringValue(data.type ?? event.type ?? attributes.type ?? "unknown") - const eventName = stringValue(event.meta?.event_name ?? event.event_name ?? values["event-name"]) - - if (sourceType === "subscriptions" || eventName.startsWith("subscription_")) { - return subscriptionLicenseDraft(attributes, data.id, eventName, sourceType, values) - } - - return orderLicenseDraft(attributes, data.id, eventName, sourceType, values) -} - -function orderLicenseDraft(attributes, id, eventName, sourceType, values) { - const status = stringValue(attributes.status) - if (status && status !== "paid" && !values["allow-unpaid"]) { - throw new Error(`Refusing to issue a license for unpaid order status: ${status}.`) - } - - const issuedAt = isoDate( - stringValue(attributes.created_at) || new Date().toISOString(), - "created_at", - ) - const updateYears = positiveInteger( - values["updates-years"] ?? String(DEFAULT_UPDATE_YEARS), - "updates-years", - ) - const holder = holderName(attributes) - const licenseValues = { - holder, - tier: values.tier ?? inferTier(attributes), - "major-version": values["major-version"] ?? "0", - "license-id": - values["license-id"] ?? lemonLicenseId("order", firstNonEmpty(attributes.identifier, id)), - "issued-at": issuedAt, - "updates-until": values["updates-until"] ?? yearsFromNow(updateYears, issuedAt), - } - - return { - eventName, - sourceType, - payload: licensePayload(licenseValues), - } -} - -function subscriptionLicenseDraft(attributes, id, eventName, sourceType, values) { - const status = stringValue(attributes.status) - if (!SUBSCRIPTION_ACTIVE_STATUSES.has(status) && !values["allow-inactive"]) { - throw new Error(`Refusing to issue a license for subscription status: ${status || "unknown"}.`) - } - - const issuedAt = isoDate( - stringValue(attributes.created_at) || new Date().toISOString(), - "created_at", - ) - const expiresAt = firstNonEmpty(values["expires-at"], attributes.renews_at, attributes.ends_at) - const holder = holderName(attributes) - const licenseValues = { - holder, - tier: values.tier ?? inferTier(attributes), - "major-version": values["major-version"] ?? "0", - "license-id": - values["license-id"] ?? - lemonLicenseId("subscription", firstNonEmpty(id, attributes.order_id)), - "issued-at": issuedAt, - "updates-until": - values["updates-until"] ?? - required(expiresAt, "Subscription renews_at or expires-at is required."), - "expires-at": required(expiresAt, "Subscription renews_at or expires-at is required."), - } - - return { - eventName, - sourceType, - payload: licensePayload(licenseValues), - } -} - -function holderName(attributes) { - const userName = stringValue(attributes.user_name) - const userEmail = stringValue(attributes.user_email) - return required(userName || userEmail, "Lemon Squeezy user_name or user_email is required.") -} - -function inferTier(attributes) { - const explicitTier = stringValue(attributes.custom_data?.ragmir_tier) - if (explicitTier) { - return licenseTier(explicitTier) - } - - const candidate = [ - attributes.variant_name, - attributes.product_name, - attributes.first_order_item?.variant_name, - attributes.first_order_item?.product_name, - ] - .map(stringValue) - .join(" ") - .toLowerCase() - - if (candidate.includes("company") || candidate.includes("enterprise")) return "company" - if (candidate.includes("team")) return "team" - return "solo" -} - -function lemonLicenseId(kind, value) { - const id = stringValue(value) - if (!id) { - throw new Error(`Lemon Squeezy ${kind} id is required.`) - } - return `lemonsqueezy:${kind}:${id}` -} - -function record(value) { - return typeof value === "object" && value !== null ? value : {} -} - -function firstNonEmpty(...values) { - for (const value of values) { - const text = stringValue(value) - if (text) { - return text - } - } - return "" -} - -function stringValue(value) { - if (typeof value === "string") { - return value.trim() - } - if (typeof value === "number" || typeof value === "bigint") { - return String(value) - } - return "" -} diff --git a/packages/ragmir-app/scripts/license-issue.mjs b/packages/ragmir-app/scripts/license-issue.mjs deleted file mode 100644 index ea26f48..0000000 --- a/packages/ragmir-app/scripts/license-issue.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { licensePayload, parseArgs, readPrivateKey, signLicensePayload } from "./license-core.mjs" - -const args = parseArgs(process.argv.slice(2)) -const privateKeyJwk = await readPrivateKey(args) -const payload = licensePayload(args) -console.log(await signLicensePayload(payload, privateKeyJwk)) diff --git a/packages/ragmir-app/scripts/license-keypair.mjs b/packages/ragmir-app/scripts/license-keypair.mjs deleted file mode 100644 index d9abe6d..0000000 --- a/packages/ragmir-app/scripts/license-keypair.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import { webcrypto } from "node:crypto" -import { mkdir, writeFile } from "node:fs/promises" -import path from "node:path" - -const args = parseArgs(process.argv.slice(2)) -const keyPair = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [ - "sign", - "verify", -]) -const privateKey = await webcrypto.subtle.exportKey("jwk", keyPair.privateKey) -const publicKey = await webcrypto.subtle.exportKey("jwk", keyPair.publicKey) -const privateKeyPath = args["private-key"] ?? ".ragmir/license-private.jwk" -const publicKeyPath = args["public-key"] ?? ".ragmir/license-public.jwk" -const writtenPrivateKey = await writeKey(privateKeyPath, JSON.stringify(privateKey, null, 2)) -const writtenPublicKey = await writeKey(publicKeyPath, JSON.stringify(publicKey, null, 2)) - -if (args.json) { - console.log( - JSON.stringify( - { - privateKeyPath: writtenPrivateKey, - publicKeyPath: writtenPublicKey, - vitePublicKeyEnv: JSON.stringify(publicKey), - }, - null, - 2, - ), - ) -} else { - console.log(`wrote ${writtenPrivateKey}`) - console.log(`wrote ${writtenPublicKey}`) - console.log("Private key material was written to disk and was not printed.") -} - -async function writeKey(target, content) { - const resolved = path.resolve(target) - await mkdir(path.dirname(resolved), { recursive: true }) - await writeFile(resolved, `${content}\n`, { mode: 0o600 }) - return resolved -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = "true" - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/scripts/native-bundle-verify-smoke.mjs b/packages/ragmir-app/scripts/native-bundle-verify-smoke.mjs deleted file mode 100644 index 1fb1491..0000000 --- a/packages/ragmir-app/scripts/native-bundle-verify-smoke.mjs +++ /dev/null @@ -1,97 +0,0 @@ -import { spawnSync } from "node:child_process" -import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const CHECKSUMS_SCRIPT = join(SCRIPT_DIR, "native-checksums.mjs") -const MANIFEST_SCRIPT = join(SCRIPT_DIR, "native-release-manifest.mjs") -const VERIFY_SCRIPT = join(SCRIPT_DIR, "native-bundle-verify.mjs") -const tempDir = await mkdtemp(join(tmpdir(), "ragmir-native-bundle-verify-smoke-")) - -try { - for (const target of ["linux", "macos", "windows", "android"]) { - const artifactsDir = join(tempDir, target) - await mkdir(artifactsDir, { recursive: true }) - await writeTargetArtifacts(artifactsDir, target) - runNode([CHECKSUMS_SCRIPT, "--artifacts-dir", artifactsDir]) - runNode([MANIFEST_SCRIPT, "--artifacts-dir", artifactsDir, "--target", target]) - - const result = runNode([ - VERIFY_SCRIPT, - "--artifacts-dir", - artifactsDir, - "--target", - target, - "--json", - ]) - const output = JSON.parse(result.stdout) - assertEqual(output.ok, true, `${target} verification`) - } - - const incompleteLinuxDir = join(tempDir, "incomplete-linux") - await mkdir(incompleteLinuxDir, { recursive: true }) - await writeFile(join(incompleteLinuxDir, "Ragmir_0.0.0.AppImage"), "synthetic appimage\n", "utf8") - runNode([CHECKSUMS_SCRIPT, "--artifacts-dir", incompleteLinuxDir]) - runNode([MANIFEST_SCRIPT, "--artifacts-dir", incompleteLinuxDir, "--target", "linux"]) - assertFails( - [VERIFY_SCRIPT, "--artifacts-dir", incompleteLinuxDir, "--target", "linux", "--json"], - "Linux Debian package", - ) - - console.log("Native bundle verification smoke passed.") -} finally { - await rm(tempDir, { recursive: true, force: true }) -} - -async function writeTargetArtifacts(artifactsDir, target) { - if (target === "linux") { - await writeFile(join(artifactsDir, "Ragmir_0.0.0.AppImage"), "synthetic appimage\n", "utf8") - await writeFile(join(artifactsDir, "ragmir_0.0.0_amd64.deb"), "synthetic deb\n", "utf8") - return - } - if (target === "macos") { - await mkdir(join(artifactsDir, "Ragmir.app", "Contents"), { recursive: true }) - await writeFile(join(artifactsDir, "Ragmir_0.0.0.dmg"), "synthetic dmg\n", "utf8") - await writeFile( - join(artifactsDir, "Ragmir.app", "Contents", "Info.plist"), - "synthetic plist\n", - "utf8", - ) - return - } - if (target === "windows") { - await writeFile(join(artifactsDir, "Ragmir_0.0.0_x64-setup.exe"), "synthetic exe\n", "utf8") - await writeFile(join(artifactsDir, "Ragmir_0.0.0_x64_en-US.msi"), "synthetic msi\n", "utf8") - return - } - await writeFile(join(artifactsDir, "ragmir-universal-release.apk"), "synthetic apk\n", "utf8") -} - -function runNode(args) { - const result = spawnSync(process.execPath, args, { encoding: "utf8", shell: false }) - if (result.status !== 0) { - throw new Error( - `command failed: node ${args.join(" ")}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ) - } - return result -} - -function assertFails(args, expectedOutput) { - const result = spawnSync(process.execPath, args, { encoding: "utf8", shell: false }) - if (result.status === 0) { - throw new Error(`command unexpectedly passed: node ${args.join(" ")}`) - } - const output = `${result.stdout}\n${result.stderr}` - if (!output.includes(expectedOutput)) { - throw new Error(`expected output to include ${expectedOutput}\n${output}`) - } -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label}: expected ${expected}, got ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/native-bundle-verify.mjs b/packages/ragmir-app/scripts/native-bundle-verify.mjs deleted file mode 100644 index 0529bd0..0000000 --- a/packages/ragmir-app/scripts/native-bundle-verify.mjs +++ /dev/null @@ -1,211 +0,0 @@ -import { createHash } from "node:crypto" -import { readdir, readFile } from "node:fs/promises" -import path from "node:path" - -const VALID_TARGETS = new Set(["macos", "windows", "linux", "android"]) -const args = parseArgs(process.argv.slice(2)) -const target = requiredTarget(args.target) -const artifactsDir = path.resolve(args["artifacts-dir"] ?? "src-tauri/target/release/bundle") -const checksumsPath = path.resolve(args.checksums ?? path.join(artifactsDir, "SHA256SUMS")) -const manifestPath = path.resolve( - args.manifest ?? path.join(artifactsDir, "ragmir-app-release.json"), -) -const checksumEntries = await readChecksums(checksumsPath) -const manifest = JSON.parse(await readFile(manifestPath, "utf8")) -const files = await listFiles(artifactsDir) -const report = await verifyBundle({ - artifactsDir, - checksumEntries, - files, - manifest, - target, -}) - -if (args.json) { - console.log(JSON.stringify(report, null, 2)) -} else { - console.log(`Ragmir native bundle verification: ${report.ok ? "ok" : "failed"}`) - for (const check of report.checks) { - console.log(`${check.ok ? "ok" : "missing"} ${check.label}: ${check.detail}`) - } -} - -if (!report.ok) { - process.exitCode = 1 -} - -async function verifyBundle({ artifactsDir, checksumEntries, files, manifest, target }) { - const checksumMap = new Map(checksumEntries.map((entry) => [entry.file, entry.sha256])) - const manifestFiles = Array.isArray(manifest.files) ? manifest.files : [] - const manifestMap = new Map(manifestFiles.map((entry) => [entry.file, entry])) - const artifactFiles = files.filter( - (file) => file !== "SHA256SUMS" && file !== "ragmir-app-release.json", - ) - const checks = [ - { - label: "target", - ok: manifest.target === target, - detail: manifest.target === target ? target : `manifest target is ${manifest.target}`, - }, - { - label: "checksum entries", - ok: artifactFiles.length > 0 && sameSet(artifactFiles, [...checksumMap.keys()]), - detail: `${checksumMap.size} checksum entries for ${artifactFiles.length} artifact files`, - }, - { - label: "manifest entries", - ok: artifactFiles.length > 0 && sameSet(artifactFiles, [...manifestMap.keys()]), - detail: `${manifestMap.size} manifest entries for ${artifactFiles.length} artifact files`, - }, - ...targetChecks(target, artifactFiles), - ] - - for (const file of artifactFiles) { - const content = await readFile(path.join(artifactsDir, file)) - const sha256 = createHash("sha256").update(content).digest("hex") - const manifestEntry = manifestMap.get(file) - checks.push({ - label: `sha256 ${file}`, - ok: checksumMap.get(file) === sha256 && manifestEntry?.sha256 === sha256, - detail: "checksum and manifest must match file content", - }) - checks.push({ - label: `size ${file}`, - ok: manifestEntry?.sizeBytes === content.byteLength, - detail: `manifest size=${manifestEntry?.sizeBytes ?? "missing"} actual=${content.byteLength}`, - }) - checks.push({ - label: `download URL ${file}`, - ok: validOptionalDownloadUrl(manifestEntry?.downloadUrl), - detail: manifestEntry?.downloadUrl ? "HTTPS download URL" : "no download URL", - }) - } - - return { - ok: checks.every((check) => check.ok), - target, - artifactsDir, - checks, - } -} - -function targetChecks(target, files) { - if (target === "linux") { - return [ - expectedFile(files, ".AppImage", "Linux AppImage"), - expectedFile(files, ".deb", "Linux Debian package"), - ] - } - if (target === "macos") { - return [ - expectedFile(files, ".dmg", "macOS disk image"), - expectedPattern(files, /\.app\/Contents\/Info\.plist$/u, "macOS app bundle"), - ] - } - if (target === "windows") { - return [ - expectedFile(files, ".exe", "Windows NSIS installer"), - expectedFile(files, ".msi", "Windows MSI installer"), - ] - } - return [expectedFile(files, ".apk", "Android APK")] -} - -function expectedFile(files, suffix, label) { - return { - label, - ok: files.some((file) => file.endsWith(suffix)), - detail: `expected at least one ${suffix} artifact`, - } -} - -function expectedPattern(files, pattern, label) { - return { - label, - ok: files.some((file) => pattern.test(file)), - detail: `expected a file matching ${pattern}`, - } -} - -function validOptionalDownloadUrl(value) { - if (typeof value === "undefined") { - return true - } - if (typeof value !== "string") { - return false - } - try { - return new URL(value).protocol === "https:" - } catch { - return false - } -} - -async function readChecksums(filePath) { - const lines = (await readFile(filePath, "utf8")).split(/\r?\n/u) - const entries = [] - for (const line of lines) { - if (line.trim() === "") continue - const match = line.match(/^([a-f0-9]{64})\s{2}(.+)$/u) - if (!match) { - throw new Error(`Invalid checksum line: ${line}`) - } - const [, sha256, file] = match - if (path.isAbsolute(file) || file.split("/").includes("..")) { - throw new Error(`Checksum path must be relative and stay inside artifacts dir: ${file}`) - } - entries.push({ file, sha256 }) - } - return entries -} - -async function listFiles(root, base = root) { - const children = await readdir(root, { withFileTypes: true }) - const files = [] - - for (const child of children) { - const filePath = path.join(root, child.name) - if (child.isDirectory()) { - files.push(...(await listFiles(filePath, base))) - continue - } - if (child.isFile()) { - files.push(path.relative(base, filePath).split(path.sep).join("/")) - } - } - - return files.sort() -} - -function sameSet(left, right) { - if (left.length !== right.length) { - return false - } - const rightSet = new Set(right) - return left.every((entry) => rightSet.has(entry)) -} - -function requiredTarget(value) { - if (!VALID_TARGETS.has(value)) { - throw new Error(`--target must be one of ${Array.from(VALID_TARGETS).join(", ")}.`) - } - return value -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (value === "--") continue - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = true - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/scripts/native-checksums-smoke.mjs b/packages/ragmir-app/scripts/native-checksums-smoke.mjs deleted file mode 100644 index e38a371..0000000 --- a/packages/ragmir-app/scripts/native-checksums-smoke.mjs +++ /dev/null @@ -1,68 +0,0 @@ -import { spawnSync } from "node:child_process" -import { createHash } from "node:crypto" -import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const CHECKSUMS_SCRIPT = join(SCRIPT_DIR, "native-checksums.mjs") -const tempDir = await mkdtemp(join(tmpdir(), "ragmir-native-checksums-smoke-")) -const artifactsDir = join(tempDir, "bundle") -const nestedDir = join(artifactsDir, "app") -const manifestPath = join(artifactsDir, "SHA256SUMS") - -try { - await mkdir(nestedDir, { recursive: true }) - await writeFile(join(artifactsDir, "ragmir_0.0.0_amd64.deb"), "synthetic deb\n", "utf8") - await writeFile(join(artifactsDir, "Ragmir_0.0.0.AppImage"), "synthetic appimage\n", "utf8") - await writeFile(join(nestedDir, "ragmir"), "synthetic binary\n", "utf8") - await writeFile(manifestPath, "stale manifest should be replaced\n", "utf8") - - const result = spawnSync( - process.execPath, - [CHECKSUMS_SCRIPT, "--artifacts-dir", artifactsDir, "--out", manifestPath, "--json"], - { encoding: "utf8", shell: false }, - ) - - if (result.status !== 0) { - throw new Error( - `native checksums smoke failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ) - } - - const output = JSON.parse(result.stdout) - assertEqual(output.artifactsDir, artifactsDir, "artifactsDir") - assertEqual(output.outputPath, manifestPath, "outputPath") - - const files = output.files.map((entry) => entry.file) - assertEqual( - JSON.stringify(files), - JSON.stringify(["app/ragmir", "ragmir_0.0.0_amd64.deb", "Ragmir_0.0.0.AppImage"]), - "sorted manifest files", - ) - assertEqual(files.includes("SHA256SUMS"), false, "manifest should not checksum itself") - - const manifest = await readFile(manifestPath, "utf8") - for (const file of files) { - const content = await readFile(join(artifactsDir, file)) - const sha256 = createHash("sha256").update(content).digest("hex") - assertIncludes(manifest, `${sha256} ${file}`, `manifest entry for ${file}`) - } - - console.log("Native checksum smoke passed.") -} finally { - await rm(tempDir, { recursive: true, force: true }) -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label}: expected ${expected}, got ${actual}`) - } -} - -function assertIncludes(actual, expected, label) { - if (!actual.includes(expected)) { - throw new Error(`${label}: expected ${expected} in ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/native-checksums.mjs b/packages/ragmir-app/scripts/native-checksums.mjs deleted file mode 100644 index 5627b6b..0000000 --- a/packages/ragmir-app/scripts/native-checksums.mjs +++ /dev/null @@ -1,86 +0,0 @@ -import { createHash } from "node:crypto" -import { readdir, readFile, writeFile } from "node:fs/promises" -import path from "node:path" - -const args = parseArgs(process.argv.slice(2)) -const artifactsDir = path.resolve(args["artifacts-dir"] ?? "src-tauri/target/release/bundle") -const outputPath = path.resolve(args.out ?? path.join(artifactsDir, "SHA256SUMS")) -const entries = await checksumEntries(artifactsDir, outputPath) - -if (entries.length === 0) { - throw new Error(`No files found under ${artifactsDir}.`) -} - -const content = `${entries.map((entry) => `${entry.sha256} ${entry.file}`).join("\n")}\n` -await writeFile(outputPath, content, "utf8") - -if (args.json) { - console.log( - JSON.stringify( - { - artifactsDir, - outputPath, - files: entries, - }, - null, - 2, - ), - ) -} else { - console.log(`Wrote ${path.relative(process.cwd(), outputPath) || outputPath}`) -} - -async function checksumEntries(root, output) { - const files = await listFiles(root) - const outputAbsolute = path.resolve(output) - const entries = [] - - for (const file of files) { - if (path.resolve(file) === outputAbsolute) { - continue - } - const buffer = await readFile(file) - entries.push({ - file: path.relative(root, file).split(path.sep).join("/"), - sha256: createHash("sha256").update(buffer).digest("hex"), - }) - } - - return entries.sort((a, b) => a.file.localeCompare(b.file)) -} - -async function listFiles(root) { - const children = await readdir(root, { withFileTypes: true }) - const files = [] - - for (const child of children) { - const filePath = path.join(root, child.name) - if (child.isDirectory()) { - files.push(...(await listFiles(filePath))) - continue - } - if (child.isFile()) { - files.push(filePath) - } - } - - return files -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (value === "--") continue - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = true - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/scripts/native-release-manifest-smoke.mjs b/packages/ragmir-app/scripts/native-release-manifest-smoke.mjs deleted file mode 100644 index 8e99295..0000000 --- a/packages/ragmir-app/scripts/native-release-manifest-smoke.mjs +++ /dev/null @@ -1,104 +0,0 @@ -import { spawnSync } from "node:child_process" -import { createHash } from "node:crypto" -import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const CHECKSUMS_SCRIPT = join(SCRIPT_DIR, "native-checksums.mjs") -const MANIFEST_SCRIPT = join(SCRIPT_DIR, "native-release-manifest.mjs") -const tempDir = await mkdtemp(join(tmpdir(), "ragmir-native-release-manifest-smoke-")) -const artifactsDir = join(tempDir, "bundle") -const manifestPath = join(artifactsDir, "ragmir-app-release.json") - -try { - await mkdir(artifactsDir, { recursive: true }) - await writeFile(join(artifactsDir, "ragmir_0.0.0_amd64.deb"), "synthetic deb\n", "utf8") - await writeFile(join(artifactsDir, "Ragmir_0.0.0.AppImage"), "synthetic appimage\n", "utf8") - - runNode([CHECKSUMS_SCRIPT, "--artifacts-dir", artifactsDir]) - assertFails( - [ - MANIFEST_SCRIPT, - "--artifacts-dir", - artifactsDir, - "--target", - "linux", - "--base-url", - "http://downloads.example.invalid/ragmir/linux", - ], - "--base-url must use HTTPS.", - ) - const result = runNode([ - MANIFEST_SCRIPT, - "--artifacts-dir", - artifactsDir, - "--target", - "linux", - "--out", - manifestPath, - "--base-url", - "https://downloads.example.invalid/ragmir/linux", - "--generated-at", - "2026-06-30T00:00:00.000Z", - "--json", - ]) - - const output = JSON.parse(result.stdout) - const manifest = output.manifest - assertEqual(output.outputPath, manifestPath, "outputPath") - assertEqual(manifest.schemaVersion, 1, "schemaVersion") - assertEqual(manifest.product, "Ragmir", "product") - assertEqual(manifest.packageName, "@jcode.labs/ragmir-app", "packageName") - assertEqual(manifest.target, "linux", "target") - assertEqual(manifest.generatedAt, "2026-06-30T00:00:00.000Z", "generatedAt") - assertEqual(manifest.files.length, 2, "files length") - - for (const entry of manifest.files) { - const content = await readFile(join(artifactsDir, entry.file)) - const sha256 = createHash("sha256").update(content).digest("hex") - assertEqual(entry.sha256, sha256, `sha256 for ${entry.file}`) - assertEqual(entry.sizeBytes, content.byteLength, `sizeBytes for ${entry.file}`) - assertEqual( - entry.downloadUrl, - `https://downloads.example.invalid/ragmir/linux/${entry.file}`, - `downloadUrl for ${entry.file}`, - ) - } - - const writtenManifest = JSON.parse(await readFile(manifestPath, "utf8")) - assertEqual(writtenManifest.files.length, 2, "written files length") - - console.log("Native release manifest smoke passed.") -} finally { - await rm(tempDir, { recursive: true, force: true }) -} - -function runNode(args) { - const result = spawnSync(process.execPath, args, { encoding: "utf8", shell: false }) - if (result.status !== 0) { - throw new Error( - `command failed: node ${args.join(" ")}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ) - } - return result -} - -function assertFails(args, expectedStderr) { - const result = spawnSync(process.execPath, args, { encoding: "utf8", shell: false }) - if (result.status === 0) { - throw new Error(`command unexpectedly passed: node ${args.join(" ")}`) - } - if (!result.stderr.includes(expectedStderr)) { - throw new Error( - `expected stderr to include ${expectedStderr}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ) - } -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label}: expected ${expected}, got ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/native-release-manifest.mjs b/packages/ragmir-app/scripts/native-release-manifest.mjs deleted file mode 100644 index 49b8f56..0000000 --- a/packages/ragmir-app/scripts/native-release-manifest.mjs +++ /dev/null @@ -1,122 +0,0 @@ -import { readFile, stat, writeFile } from "node:fs/promises" -import path from "node:path" - -const VALID_TARGETS = new Set(["macos", "windows", "linux", "android"]) -const args = parseArgs(process.argv.slice(2)) -const artifactsDir = path.resolve(args["artifacts-dir"] ?? "src-tauri/target/release/bundle") -const checksumsPath = path.resolve(args.checksums ?? path.join(artifactsDir, "SHA256SUMS")) -const outputPath = path.resolve(args.out ?? path.join(artifactsDir, "ragmir-app-release.json")) -const target = requiredTarget(args.target) -const generatedAt = args["generated-at"] ?? new Date().toISOString() -const baseUrl = optionalHttpsBaseUrl(args["base-url"]) -const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) -const entries = await manifestEntries(artifactsDir, checksumsPath, outputPath, baseUrl) - -if (entries.length === 0) { - throw new Error(`No checksum entries found in ${checksumsPath}.`) -} - -const manifest = { - schemaVersion: 1, - product: "Ragmir", - packageName: packageJson.name, - version: packageJson.version, - target, - generatedAt, - files: entries, -} - -await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8") - -if (args.json) { - console.log(JSON.stringify({ outputPath, manifest }, null, 2)) -} else { - console.log(`Wrote ${path.relative(process.cwd(), outputPath) || outputPath}`) -} - -async function manifestEntries(root, checksums, output, baseUrl) { - const lines = (await readFile(checksums, "utf8")).split(/\r?\n/u) - const outputRelative = path.relative(root, output).split(path.sep).join("/") - const entries = [] - - for (const line of lines) { - if (line.trim() === "") continue - const match = line.match(/^([a-f0-9]{64})\s{2}(.+)$/u) - if (!match) { - throw new Error(`Invalid checksum line: ${line}`) - } - const [, sha256, file] = match - if (file === outputRelative || file === "ragmir-app-release.json") { - continue - } - if (path.isAbsolute(file) || file.split("/").includes("..")) { - throw new Error(`Checksum path must be relative and stay inside artifacts dir: ${file}`) - } - const filePath = path.join(root, file) - const info = await stat(filePath) - if (!info.isFile()) { - throw new Error(`Checksum entry is not a file: ${file}`) - } - entries.push( - appendOptionalUrl( - { - file, - sizeBytes: info.size, - sha256, - }, - baseUrl, - ), - ) - } - - return entries.sort((a, b) => a.file.localeCompare(b.file)) -} - -function appendOptionalUrl(entry, baseUrl) { - if (!baseUrl) { - return entry - } - return { - ...entry, - downloadUrl: `${baseUrl.replace(/\/+$/u, "")}/${entry.file - .split("/") - .map((part) => encodeURIComponent(part)) - .join("/")}`, - } -} - -function optionalHttpsBaseUrl(value) { - if (!value) { - return null - } - const parsed = new URL(value) - if (parsed.protocol !== "https:") { - throw new Error("--base-url must use HTTPS.") - } - return parsed.toString().replace(/\/+$/u, "") -} - -function requiredTarget(value) { - if (!VALID_TARGETS.has(value)) { - throw new Error(`--target must be one of ${Array.from(VALID_TARGETS).join(", ")}.`) - } - return value -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (value === "--") continue - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = true - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/scripts/release-preflight-smoke.mjs b/packages/ragmir-app/scripts/release-preflight-smoke.mjs deleted file mode 100644 index a2006f2..0000000 --- a/packages/ragmir-app/scripts/release-preflight-smoke.mjs +++ /dev/null @@ -1,142 +0,0 @@ -import { spawnSync } from "node:child_process" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const PREFLIGHT_SCRIPT = join(SCRIPT_DIR, "release-preflight.mjs") -const SECRET_VALUES = [ - "synthetic-apple-id@example.invalid", - "synthetic-apple-password", - "synthetic-apple-team-id", - "synthetic-apple-signing-identity", - "synthetic-windows-thumbprint", - "https://timestamp.example.invalid/ragmir", - "/tmp/synthetic-android-sdk", - "/tmp/synthetic-java-home", -] - -for (const target of ["linux", "macos", "windows", "android"]) { - const result = runPreflight(["--target", target, "--soft", "--json"], envForTarget(target)) - assertEqual(result.status, 0, `${target} soft status`) - assertNoSecretValues(result, `${target} output`) - assertEqual(result.output.target, target, `${target} target`) - assertEqual(Array.isArray(result.output.checks), true, `${target} checks array`) - assertIncludes( - checkLabels(result.output), - "Tauri updater configuration", - `${target} updater guard`, - ) -} - -const macos = runPreflight(["--target", "macos", "--soft", "--json"], envForTarget("macos")) -assertIncludes(checkLabels(macos.output), "Apple notarization account", "macOS Apple account check") -assertIncludes(checkDetails(macos.output), "APPLE_ID is set", "macOS Apple ID detail") -assertIncludes(checkDetails(macos.output), "APPLE_PASSWORD is set", "macOS Apple password detail") -assertIncludes(checkDetails(macos.output), "APPLE_TEAM_ID is set", "macOS Apple team detail") - -const windows = runPreflight(["--target", "windows", "--soft", "--json"], envForTarget("windows")) -assertIncludes(checkLabels(windows.output), "certificate thumbprint", "Windows certificate check") -assertIncludes( - checkDetails(windows.output), - "WINDOWS_CERTIFICATE_THUMBPRINT is set", - "Windows thumbprint detail", -) -assertIncludes( - checkDetails(windows.output), - "WINDOWS_TIMESTAMP_URL is set", - "Windows timestamp detail", -) - -const android = runPreflight(["--target", "android", "--soft", "--json"], envForTarget("android")) -assertIncludes(checkLabels(android.output), "Android SDK root", "Android SDK check") -assertIncludes(checkDetails(android.output), "ANDROID_HOME is set", "Android SDK detail") -assertIncludes(checkDetails(android.output), "JAVA_HOME is set", "Android Java detail") - -const invalid = spawnSync( - process.execPath, - [PREFLIGHT_SCRIPT, "--target", "ios", "--soft", "--json"], - { - encoding: "utf8", - shell: false, - }, -) -assertEqual(invalid.status === 0, false, "iOS release target should fail") -assertIncludes( - invalid.stderr, - "target must be one of macos, windows, linux, android", - "invalid target stderr", -) - -console.log("Release preflight smoke passed.") - -function runPreflight(args, env = {}) { - const result = spawnSync(process.execPath, [PREFLIGHT_SCRIPT, ...args], { - encoding: "utf8", - env: { ...process.env, ...env }, - shell: false, - }) - if (result.status !== 0) { - throw new Error( - `release preflight failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, - ) - } - return { - status: result.status, - stdout: result.stdout, - stderr: result.stderr, - output: JSON.parse(result.stdout), - } -} - -function envForTarget(target) { - if (target === "macos") { - return { - APPLE_ID: "synthetic-apple-id@example.invalid", - APPLE_PASSWORD: "synthetic-apple-password", - APPLE_SIGNING_IDENTITY: "synthetic-apple-signing-identity", - APPLE_TEAM_ID: "synthetic-apple-team-id", - } - } - if (target === "windows") { - return { - WINDOWS_CERTIFICATE_THUMBPRINT: "synthetic-windows-thumbprint", - WINDOWS_TIMESTAMP_URL: "https://timestamp.example.invalid/ragmir", - } - } - if (target === "android") { - return { - ANDROID_HOME: "/tmp/synthetic-android-sdk", - JAVA_HOME: "/tmp/synthetic-java-home", - } - } - return {} -} - -function assertNoSecretValues(result, label) { - const output = `${result.stdout}\n${result.stderr}` - for (const value of SECRET_VALUES) { - if (output.includes(value)) { - throw new Error(`${label}: preflight output must not print secret-like value ${value}`) - } - } -} - -function checkLabels(output) { - return output.checks.map((check) => check.label).join("\n") -} - -function checkDetails(output) { - return output.checks.map((check) => check.detail).join("\n") -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label}: expected ${expected}, got ${actual}`) - } -} - -function assertIncludes(actual, expected, label) { - if (!actual.includes(expected)) { - throw new Error(`${label}: expected ${expected} in ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/release-preflight.mjs b/packages/ragmir-app/scripts/release-preflight.mjs deleted file mode 100644 index 2bb9a19..0000000 --- a/packages/ragmir-app/scripts/release-preflight.mjs +++ /dev/null @@ -1,143 +0,0 @@ -import { spawnSync } from "node:child_process" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const UPDATER_GUARD_SCRIPT = join(SCRIPT_DIR, "updater-guard.mjs") -const TARGETS = new Set(["macos", "windows", "linux", "android"]) -const args = parseArgs(process.argv.slice(2)) -const target = args.target ?? currentTarget() -const checks = releaseChecks(target) -const ok = checks.every((check) => check.ok) - -if (args.json) { - console.log(JSON.stringify({ target, ok, checks }, null, 2)) -} else { - console.log(`Ragmir app release preflight: ${target}`) - for (const check of checks) { - console.log(`${check.ok ? "ok" : "missing"} ${check.label}: ${check.detail}`) - } -} - -if (!ok && !args.soft) { - process.exitCode = 1 -} - -function releaseChecks(releaseTarget) { - if (!TARGETS.has(releaseTarget)) { - throw new Error(`target must be one of ${Array.from(TARGETS).join(", ")}.`) - } - - const common = [ - commandCheck("pnpm", ["--version"], "pnpm workspace runner"), - commandCheck("cargo", ["--version"], "Rust/Cargo toolchain"), - commandCheck("rustc", ["--version"], "Rust compiler"), - commandCheck("pnpm", ["exec", "tauri", "--version"], "Tauri CLI"), - updaterGuardCheck(releaseTarget), - ] - - if (releaseTarget === "macos") { - return [ - ...common, - platformCheck("darwin", "macOS release builds must run on macOS."), - commandCheck("security", ["find-identity", "-v", "-p", "codesigning"], "Apple keychain"), - envCheck("APPLE_SIGNING_IDENTITY", "Developer ID Application identity name"), - envCheck("APPLE_ID", "Apple notarization account"), - envCheck("APPLE_PASSWORD", "Apple app-specific notarization password"), - envCheck("APPLE_TEAM_ID", "Apple notarization team"), - ] - } - - if (releaseTarget === "windows") { - return [ - ...common, - platformCheck("win32", "Windows release builds must run on Windows."), - commandCheck("signtool", ["sign", "/?"], "Windows Authenticode signing tool"), - envCheck("WINDOWS_CERTIFICATE_THUMBPRINT", "certificate thumbprint"), - envCheck("WINDOWS_TIMESTAMP_URL", "trusted timestamp server URL"), - ] - } - - if (releaseTarget === "linux") { - return [...common, platformCheck("linux", "Linux AppImage/deb builds must run on Linux.")] - } - - return [ - ...common, - commandCheck("rustup", ["--version"], "Rust target manager"), - envAnyCheck(["ANDROID_HOME", "ANDROID_SDK_ROOT"], "Android SDK root"), - envCheck("JAVA_HOME", "JDK for Android build tooling"), - ] -} - -function commandCheck(command, commandArgs, label) { - const result = spawnSync(command, commandArgs, { encoding: "utf8", shell: false }) - return { - label, - ok: result.status === 0, - detail: - result.status === 0 ? firstLine(result.stdout || result.stderr) : `${command} not available`, - } -} - -function updaterGuardCheck(releaseTarget) { - const commandArgs = [UPDATER_GUARD_SCRIPT] - if (releaseTarget !== "android") { - commandArgs.push("--require-private-key") - } - return commandCheck(process.execPath, commandArgs, "Tauri updater configuration") -} - -function platformCheck(expected, detail) { - return { - label: `platform ${expected}`, - ok: process.platform === expected, - detail: process.platform === expected ? process.platform : detail, - } -} - -function envCheck(name, label) { - return { - label, - ok: Boolean(process.env[name]), - detail: process.env[name] ? `${name} is set` : `${name} is not set`, - } -} - -function envAnyCheck(names, label) { - const found = names.find((name) => process.env[name]) - return { - label, - ok: Boolean(found), - detail: found ? `${found} is set` : `${names.join(" or ")} is not set`, - } -} - -function firstLine(value) { - return value.trim().split(/\r?\n/u).at(0) ?? "available" -} - -function currentTarget() { - if (process.platform === "darwin") return "macos" - if (process.platform === "win32") return "windows" - if (process.platform === "linux") return "linux" - return "android" -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (value === "--") continue - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = "true" - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/scripts/updater-guard-smoke.mjs b/packages/ragmir-app/scripts/updater-guard-smoke.mjs deleted file mode 100644 index c18d13f..0000000 --- a/packages/ragmir-app/scripts/updater-guard-smoke.mjs +++ /dev/null @@ -1,99 +0,0 @@ -import { spawnSync } from "node:child_process" -import { mkdtemp, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const UPDATER_GUARD_SCRIPT = join(SCRIPT_DIR, "updater-guard.mjs") -const tempDir = await mkdtemp(join(tmpdir(), "ragmir-updater-guard-smoke-")) - -try { - const disabledConfig = join(tempDir, "updater-disabled.json") - await writeJson(disabledConfig, { - productName: "Ragmir", - version: "0.0.0", - bundle: {}, - plugins: {}, - }) - - const disabled = runGuard(["--config", disabledConfig, "--json"]) - assertEqual(disabled.status, 0, "disabled updater should pass") - assertEqual(disabled.output.ok, true, "disabled updater output") - assertEqual(disabled.output.checks[0]?.label, "updater disabled", "disabled updater check label") - - const placeholderConfig = join(tempDir, "updater-placeholder.json") - await writeJson(placeholderConfig, { - bundle: { createUpdaterArtifacts: true }, - plugins: { - updater: { - pubkey: "CONTENT FROM PUBLICKEY.PEM", - endpoints: ["https://example.com/latest.json"], - }, - }, - }) - - const placeholder = runGuard(["--config", placeholderConfig, "--json", "--require-private-key"]) - assertEqual(placeholder.status, 1, "placeholder updater should fail") - assertEqual(placeholder.output.ok, false, "placeholder updater output") - assertIncludes( - placeholder.output.checks.map((check) => check.label).join("\n"), - "updater pubkey", - "placeholder updater should audit the public key", - ) - assertIncludes( - placeholder.output.checks.map((check) => check.label).join("\n"), - "updater private key", - "placeholder updater should require a private key for desktop packaging", - ) - - const readyConfig = join(tempDir, "updater-ready.json") - await writeJson(readyConfig, { - bundle: { createUpdaterArtifacts: true }, - plugins: { - updater: { - pubkey: "ragmir-updater-public-key-for-smoke-test-0001", - endpoints: ["https://updates.example.invalid/ragmir/latest.json"], - }, - }, - }) - - const ready = runGuard(["--config", readyConfig, "--json", "--require-private-key"], { - TAURI_SIGNING_PRIVATE_KEY: "smoke-test-private-key", - }) - assertEqual(ready.status, 0, "ready updater should pass") - assertEqual(ready.output.ok, true, "ready updater output") - - console.log("Updater guard smoke passed.") -} finally { - await rm(tempDir, { recursive: true, force: true }) -} - -async function writeJson(path, value) { - await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8") -} - -function runGuard(args, env = {}) { - const result = spawnSync(process.execPath, [UPDATER_GUARD_SCRIPT, ...args], { - encoding: "utf8", - env: { ...process.env, ...env }, - shell: false, - }) - return { - status: result.status, - output: JSON.parse(result.stdout), - stderr: result.stderr, - } -} - -function assertEqual(actual, expected, label) { - if (actual !== expected) { - throw new Error(`${label}: expected ${expected}, got ${actual}`) - } -} - -function assertIncludes(actual, expected, label) { - if (!actual.includes(expected)) { - throw new Error(`${label}: expected ${expected} in ${actual}`) - } -} diff --git a/packages/ragmir-app/scripts/updater-guard.mjs b/packages/ragmir-app/scripts/updater-guard.mjs deleted file mode 100644 index 723aecb..0000000 --- a/packages/ragmir-app/scripts/updater-guard.mjs +++ /dev/null @@ -1,163 +0,0 @@ -import { readFileSync } from "node:fs" -import { dirname, resolve } from "node:path" -import { fileURLToPath } from "node:url" - -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) -const DEFAULT_CONFIG_PATH = resolve(SCRIPT_DIR, "../src-tauri/tauri.conf.json") -const PLACEHOLDER_PATTERNS = [ - /CONTENT FROM PUBLICKEY\.PEM/iu, - /\b(change-?me|placeholder|todo|fake|dummy)\b/iu, - /releases\.myapp\.com/iu, - /github\.com\/user\/repo/iu, - /example\.(com|org|net)/iu, - /localhost|127\.0\.0\.1|0\.0\.0\.0/iu, -] - -const args = parseArgs(process.argv.slice(2)) -const configPath = resolve(process.cwd(), args.config ?? DEFAULT_CONFIG_PATH) -const config = readConfig(configPath) -const checks = auditUpdaterConfig(config, { - requirePrivateKey: Boolean(args["require-private-key"]), -}) -const ok = checks.every((check) => check.ok) - -if (args.json) { - console.log(JSON.stringify({ configPath, ok, checks }, null, 2)) -} else { - console.log(`Ragmir app updater config: ${ok ? "ok" : "needs attention"}`) - for (const check of checks) { - console.log(`${check.ok ? "ok" : "missing"} ${check.label}: ${check.detail}`) - } -} - -if (!ok) { - process.exitCode = 1 -} - -function readConfig(path) { - try { - return JSON.parse(readFileSync(path, "utf8")) - } catch (error) { - throw new Error(`failed to read Tauri config at ${path}: ${error.message}`) - } -} - -function auditUpdaterConfig(value, options) { - const bundle = isRecord(value.bundle) ? value.bundle : {} - const plugins = isRecord(value.plugins) ? value.plugins : {} - const updater = isRecord(plugins.updater) ? plugins.updater : undefined - const createUpdaterArtifacts = bundle.createUpdaterArtifacts - const updaterSignals = Boolean(updater) || typeof createUpdaterArtifacts !== "undefined" - - if (!updaterSignals) { - return [ - { - label: "updater disabled", - ok: true, - detail: "manual direct-download updates remain the active release path", - }, - ] - } - - const checks = [ - { - label: "bundle.createUpdaterArtifacts", - ok: createUpdaterArtifacts === true, - detail: - createUpdaterArtifacts === true - ? "v2 updater artifacts enabled" - : "must be true when the updater is configured", - }, - { - label: "plugins.updater", - ok: Boolean(updater), - detail: updater ? "updater plugin config present" : "missing updater plugin config", - }, - ] - - if (updater) { - checks.push(...updaterChecks(updater)) - } - - if (options.requirePrivateKey && updaterSignals) { - const hasPrivateKey = - Boolean(process.env.TAURI_SIGNING_PRIVATE_KEY) || - Boolean(process.env.TAURI_SIGNING_PRIVATE_KEY_PATH) - checks.push({ - label: "updater private key", - ok: hasPrivateKey, - detail: hasPrivateKey - ? "TAURI_SIGNING_PRIVATE_KEY or TAURI_SIGNING_PRIVATE_KEY_PATH is set" - : "set TAURI_SIGNING_PRIVATE_KEY or TAURI_SIGNING_PRIVATE_KEY_PATH in release secrets", - }) - } - - return checks -} - -function updaterChecks(updater) { - const pubkey = typeof updater.pubkey === "string" ? updater.pubkey.trim() : "" - const endpoints = Array.isArray(updater.endpoints) ? updater.endpoints : [] - - return [ - { - label: "updater pubkey", - ok: isRealValue(pubkey) && pubkey.length >= 32, - detail: - isRealValue(pubkey) && pubkey.length >= 32 - ? "public updater key present" - : "commit a real Tauri updater public key before enabling updates", - }, - { - label: "updater endpoints", - ok: - endpoints.length > 0 && - endpoints.every((endpoint) => typeof endpoint === "string" && isValidEndpoint(endpoint)), - detail: - endpoints.length > 0 - ? "all endpoints must be non-placeholder HTTPS URLs" - : "add at least one HTTPS update endpoint or static manifest URL", - }, - ] -} - -function isValidEndpoint(value) { - if (!isRealValue(value)) return false - - try { - const url = new URL(value) - return url.protocol === "https:" && !isPlaceholder(value) - } catch { - return false - } -} - -function isRealValue(value) { - return value.trim().length > 0 && !isPlaceholder(value) -} - -function isPlaceholder(value) { - return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value)) -} - -function isRecord(value) { - return Boolean(value) && typeof value === "object" && !Array.isArray(value) -} - -function parseArgs(values) { - const parsed = {} - for (let index = 0; index < values.length; index += 1) { - const value = values[index] - if (value === "--") continue - if (!value?.startsWith("--")) continue - const key = value.slice(2) - const next = values[index + 1] - if (!next || next.startsWith("--")) { - parsed[key] = "true" - continue - } - parsed[key] = next - index += 1 - } - return parsed -} diff --git a/packages/ragmir-app/src-tauri/Cargo.lock b/packages/ragmir-app/src-tauri/Cargo.lock deleted file mode 100644 index ee4940f..0000000 --- a/packages/ragmir-app/src-tauri/Cargo.lock +++ /dev/null @@ -1,4555 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.13.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - -[[package]] -name = "cc" -version = "1.2.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.1", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" -dependencies = [ - "bitflags 2.13.0", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.0", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.118", -] - -[[package]] -name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.118", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "dbus" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.118", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.0", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "dom_query" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" -dependencies = [ - "bit-set", - "cssparser", - "foldhash", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.2+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[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-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.13.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "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.118", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -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", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" -dependencies = [ - "log", - "markup5ever", -] - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[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" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.118", -] - -[[package]] -name = "js-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.13.0", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libdbus-sys" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" -dependencies = [ - "libc", -] - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "markup5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" -dependencies = [ - "log", - "tendril", - "web_atoms", -] - -[[package]] -name = "memchr" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "muda" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.18", - "windows-sys 0.61.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.0", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.13.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.0", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.0", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.0", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.0", - "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 = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_codegen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plist" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" -dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.0", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.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 = "ragmir-app" -version = "0.4.10" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-dialog", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "rfd" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" -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", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.118", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "selectors" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" -dependencies = [ - "bitflags 2.13.0", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf", - "phf_codegen", - "precomputed-hash", - "rustc-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[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", -] - -[[package]] -name = "serde_with" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" -dependencies = [ - "base64 0.22.1", - "bs58", - "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 = "serde_with_macros" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "servo_arc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[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" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[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", - "precomputed-hash", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[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 = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[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.118", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.35.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" -dependencies = [ - "bitflags 2.13.0", - "block2", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dbus", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "libc", - "log", - "ndk", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "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.118", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "http-range", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.118", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.118", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "walkdir", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" -dependencies = [ - "anyhow", - "dunce", - "glob", - "log", - "objc2-foundation", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.18", - "toml 1.1.2+spec-1.1.0", - "url", -] - -[[package]] -name = "tauri-runtime" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 1.1.2+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.2+spec-1.1.0", -] - -[[package]] -name = "tendril" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" -dependencies = [ - "new_debug_unreachable", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "time" -version = "0.3.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.3", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.14.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.3", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.3", -] - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags 2.13.0", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.18", - "windows-sys 0.61.2", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.118", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web_atoms" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" -dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -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.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -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", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - -[[package]] -name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wry" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http", - "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", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[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 = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/packages/ragmir-app/src-tauri/Cargo.toml b/packages/ragmir-app/src-tauri/Cargo.toml deleted file mode 100644 index c4af43b..0000000 --- a/packages/ragmir-app/src-tauri/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "ragmir-app" -version = "0.4.10" -description = "Ragmir desktop and mobile shell" -authors = ["Jean-Baptiste Thery"] -edition = "2021" -license = "MIT" - -[lib] -name = "ragmir_app_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tauri = { version = "2", features = ["protocol-asset"] } -tauri-plugin-dialog = "2.7.1" diff --git a/packages/ragmir-app/src-tauri/build.rs b/packages/ragmir-app/src-tauri/build.rs deleted file mode 100644 index d860e1e..0000000 --- a/packages/ragmir-app/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/packages/ragmir-app/src-tauri/capabilities/default.json b/packages/ragmir-app/src-tauri/capabilities/default.json deleted file mode 100644 index afac330..0000000 --- a/packages/ragmir-app/src-tauri/capabilities/default.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Default Ragmir app capabilities.", - "windows": ["main"], - "permissions": ["core:default", "dialog:allow-open"] -} diff --git a/packages/ragmir-app/src-tauri/icons/128x128.png b/packages/ragmir-app/src-tauri/icons/128x128.png deleted file mode 100644 index b38cef3..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/128x128.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/128x128@2x.png b/packages/ragmir-app/src-tauri/icons/128x128@2x.png deleted file mode 100644 index bcb49d6..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/32x32.png b/packages/ragmir-app/src-tauri/icons/32x32.png deleted file mode 100644 index 66ded98..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/32x32.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/64x64.png b/packages/ragmir-app/src-tauri/icons/64x64.png deleted file mode 100644 index 6cd1219..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/64x64.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square107x107Logo.png b/packages/ragmir-app/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index cbe69b1..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square142x142Logo.png b/packages/ragmir-app/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index 288555a..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square150x150Logo.png b/packages/ragmir-app/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index ae36b73..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square284x284Logo.png b/packages/ragmir-app/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index 52dbf2a..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square30x30Logo.png b/packages/ragmir-app/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 29c4400..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square310x310Logo.png b/packages/ragmir-app/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index 770527b..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square44x44Logo.png b/packages/ragmir-app/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index 1ae24ac..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square71x71Logo.png b/packages/ragmir-app/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index e2bb45c..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/Square89x89Logo.png b/packages/ragmir-app/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index 5fa2261..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/StoreLogo.png b/packages/ragmir-app/src-tauri/icons/StoreLogo.png deleted file mode 100644 index 7d5708f..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/ragmir-app/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 2ffbf24..0000000 --- a/packages/ragmir-app/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d2fd601..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index beed24a..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index ac7b448..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0532a0e..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index dcdd86b..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index a7b8a24..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 78ab5c8..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 31eccac..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 340cc62..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ec9a89f..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index d44d50b..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index e70df36..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c4a4be1..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index ece1f01..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index df4d272..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/android/values/ic_launcher_background.xml b/packages/ragmir-app/src-tauri/icons/android/values/ic_launcher_background.xml deleted file mode 100644 index ea9c223..0000000 --- a/packages/ragmir-app/src-tauri/icons/android/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #fff - \ No newline at end of file diff --git a/packages/ragmir-app/src-tauri/icons/icon.icns b/packages/ragmir-app/src-tauri/icons/icon.icns deleted file mode 100644 index 28704a6..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/icon.icns and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/icon.ico b/packages/ragmir-app/src-tauri/icons/icon.ico deleted file mode 100644 index 0eaa452..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/icon.ico and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/icon.png b/packages/ragmir-app/src-tauri/icons/icon.png deleted file mode 100644 index d1efbfd..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/icon.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@1x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@1x.png deleted file mode 100644 index 38439b5..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@1x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png deleted file mode 100644 index 951ec52..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x.png deleted file mode 100644 index 951ec52..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@3x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@3x.png deleted file mode 100644 index 505e7a9..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-20x20@3x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@1x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@1x.png deleted file mode 100644 index 8d8c662..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@1x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png deleted file mode 100644 index b060b8c..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x.png deleted file mode 100644 index b060b8c..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@3x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@3x.png deleted file mode 100644 index 0d1b5ee..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-29x29@3x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@1x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@1x.png deleted file mode 100644 index 951ec52..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@1x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png deleted file mode 100644 index 361b14c..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x.png deleted file mode 100644 index 361b14c..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@3x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@3x.png deleted file mode 100644 index 36333c6..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-40x40@3x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-512@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-512@2x.png deleted file mode 100644 index 85a5a26..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-512@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@2x.png deleted file mode 100644 index 36333c6..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@3x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@3x.png deleted file mode 100644 index ac99f90..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-60x60@3x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@1x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@1x.png deleted file mode 100644 index fe80292..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@1x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@2x.png deleted file mode 100644 index 8e91785..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-76x76@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/packages/ragmir-app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png deleted file mode 100644 index 169445b..0000000 Binary files a/packages/ragmir-app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/ragmir-app/src-tauri/src/lib.rs b/packages/ragmir-app/src-tauri/src/lib.rs deleted file mode 100644 index f697d17..0000000 --- a/packages/ragmir-app/src-tauri/src/lib.rs +++ /dev/null @@ -1,505 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::{ - fs, - path::{Path, PathBuf}, - process::Command, - time::{SystemTime, UNIX_EPOCH}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct RagmirCommandRequest { - project_root: String, - command: RagmirCommandKind, - query: Option, - text: Option, - rebuild: Option, - top_k: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] -enum RagmirCommandKind { - Doctor, - DoctorFix, - Status, - Ingest, - Search, - Ask, - SecurityAudit, - AuditUnsupported, - ModelsPull, - AudioDoctor, - AudioPreload, - AudioSummary, - Chat, - ChatSetup, - ChatDoctor, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct RagmirCommandOutput { - status: i32, - stdout: String, - stderr: String, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct RagmirProjectRequest { - project_root: String, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct RagmirConfigWriteRequest { - project_root: String, - content: String, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct RagmirConfigFile { - exists: bool, - config_path: String, - content: String, -} - -struct RagmirCliInvocation { - program: String, - args_prefix: Vec, -} - -#[tauri::command] -async fn run_ragmir_command(request: RagmirCommandRequest) -> Result { - tauri::async_runtime::spawn_blocking(move || run_ragmir_command_blocking(request)) - .await - .map_err(|error| format!("Unable to join Ragmir CLI task: {error}"))? -} - -fn run_ragmir_command_blocking( - request: RagmirCommandRequest, -) -> Result { - let project_root = normalized_project_root(&request.project_root)?; - - let cli = resolve_ragmir_cli(); - let args = ragmir_args(&request, &project_root)?; - let output = Command::new(&cli.program) - .args(cli.args_prefix) - .args(args) - .output() - .map_err(|error| format!("Unable to run Ragmir CLI: {error}"))?; - - Ok(RagmirCommandOutput { - status: output.status.code().unwrap_or(1), - stdout: String::from_utf8_lossy(&output.stdout).into_owned(), - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - }) -} - -fn resolve_ragmir_cli() -> RagmirCliInvocation { - if let Ok(cli_bin) = std::env::var("RAGMIR_CLI_BIN") { - let cli_path = PathBuf::from(&cli_bin); - if cli_path - .extension() - .and_then(|extension| extension.to_str()) - == Some("js") - { - return RagmirCliInvocation { - program: std::env::var("RAGMIR_NODE_BIN").unwrap_or_else(|_| "node".into()), - args_prefix: vec![cli_bin], - }; - } - - return RagmirCliInvocation { - program: cli_bin, - args_prefix: Vec::new(), - }; - } - - if let Some(local_cli) = find_local_workspace_cli() { - return RagmirCliInvocation { - program: std::env::var("RAGMIR_NODE_BIN").unwrap_or_else(|_| "node".into()), - args_prefix: vec![local_cli.to_string_lossy().into_owned()], - }; - } - - RagmirCliInvocation { - program: "rgr".into(), - args_prefix: Vec::new(), - } -} - -fn find_local_workspace_cli() -> Option { - let current_dir = std::env::current_dir().ok()?; - find_local_workspace_cli_from(¤t_dir) -} - -fn find_local_workspace_cli_from(start: &Path) -> Option { - for ancestor in start.ancestors() { - let candidate = ancestor - .join("packages") - .join("ragmir-core") - .join("dist") - .join("cli.js"); - if candidate.is_file() { - return Some(candidate); - } - } - - None -} - -#[tauri::command] -fn read_ragmir_config(request: RagmirProjectRequest) -> Result { - let project_root = normalized_project_root(&request.project_root)?; - let config_path = ragmir_config_path(&project_root); - let exists = config_path.is_file(); - let content = if exists { - fs::read_to_string(&config_path) - .map_err(|error| format!("Unable to read Ragmir config: {error}"))? - } else { - "{}\n".into() - }; - - Ok(RagmirConfigFile { - exists, - config_path: config_path.to_string_lossy().into_owned(), - content, - }) -} - -#[tauri::command] -fn write_ragmir_config(request: RagmirConfigWriteRequest) -> Result { - let project_root = normalized_project_root(&request.project_root)?; - let parsed: Value = serde_json::from_str(&request.content) - .map_err(|error| format!("Config must be valid JSON: {error}"))?; - if !parsed.is_object() { - return Err("Config must be a JSON object.".into()); - } - - let config_path = ragmir_config_path(&project_root); - let config_dir = config_path - .parent() - .ok_or_else(|| "Unable to resolve Ragmir config directory.".to_string())?; - fs::create_dir_all(config_dir) - .map_err(|error| format!("Unable to create Ragmir config directory: {error}"))?; - ensure_ragmir_gitignore(Path::new(&project_root))?; - - let content = format!( - "{}\n", - serde_json::to_string_pretty(&parsed) - .map_err(|error| format!("Unable to format Ragmir config: {error}"))? - ); - fs::write(&config_path, &content) - .map_err(|error| format!("Unable to write Ragmir config: {error}"))?; - - Ok(RagmirConfigFile { - exists: true, - config_path: config_path.to_string_lossy().into_owned(), - content, - }) -} - -fn normalized_project_root(project_root: &str) -> Result { - let project_root = project_root.trim(); - if project_root.is_empty() { - return Err("Project root is required.".into()); - } - let project_root_path = PathBuf::from(project_root); - if !project_root_path.is_absolute() { - return Err("Project root must be an absolute path.".into()); - } - if !project_root_path.is_dir() { - return Err("Project root must be an existing directory.".into()); - } - Ok(project_root_path.to_string_lossy().into_owned()) -} - -fn ragmir_config_path(project_root: &str) -> PathBuf { - PathBuf::from(project_root) - .join(".ragmir") - .join("config.json") -} - -fn ensure_ragmir_gitignore(project_root: &Path) -> Result<(), String> { - let gitignore_path = project_root.join(".gitignore"); - let existing = match fs::read_to_string(&gitignore_path) { - Ok(content) => content, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => String::new(), - Err(error) => return Err(format!("Unable to read .gitignore: {error}")), - }; - - if existing.lines().any(|line| line.trim() == ".ragmir/") { - return Ok(()); - } - - let mut next = existing; - if !next.is_empty() && !next.ends_with('\n') { - next.push('\n'); - } - next.push_str(".ragmir/\n"); - fs::write(&gitignore_path, next) - .map_err(|error| format!("Unable to update .gitignore: {error}")) -} - -fn ragmir_args(request: &RagmirCommandRequest, project_root: &str) -> Result, String> { - let mut args = vec!["--project-root".into(), project_root.into()]; - - match request.command { - RagmirCommandKind::Doctor => args.extend(["doctor".into(), "--json".into()]), - RagmirCommandKind::DoctorFix => { - args.extend(["doctor".into(), "--fix".into(), "--json".into()]) - } - RagmirCommandKind::Status => args.extend(["status".into(), "--json".into()]), - RagmirCommandKind::Ingest => { - args.extend(["ingest".into(), "--json".into()]); - if request.rebuild.unwrap_or(false) { - args.push("--rebuild".into()); - } - } - RagmirCommandKind::Search => { - let query = required_query(request)?; - args.extend(["search".into(), query, "--json".into()]); - push_top_k(&mut args, request.top_k); - } - RagmirCommandKind::Ask => { - let query = required_query(request)?; - args.extend(["ask".into(), query, "--json".into()]); - push_top_k(&mut args, request.top_k); - } - RagmirCommandKind::SecurityAudit => { - args.extend(["security-audit".into(), "--json".into()]); - } - RagmirCommandKind::AuditUnsupported => { - args.extend(["audit".into(), "--unsupported".into(), "--json".into()]); - } - RagmirCommandKind::ModelsPull => { - args.extend([ - "models".into(), - "pull".into(), - "--enable".into(), - "--json".into(), - ]); - } - RagmirCommandKind::AudioDoctor => { - args.extend(["audio".into(), "--doctor".into(), "--json".into()]); - } - RagmirCommandKind::AudioPreload => { - let text_file = write_audio_preload_text(request, project_root)?; - args.extend([ - "audio".into(), - text_file, - "--engine".into(), - "transformers".into(), - "--allow-remote-models".into(), - "--json".into(), - ]); - } - RagmirCommandKind::AudioSummary => { - let text_file = write_audio_summary_text(request, project_root)?; - args.extend([ - "audio".into(), - text_file, - "--offline".into(), - "--json".into(), - ]); - } - RagmirCommandKind::Chat => { - let query = required_query(request)?; - args.extend(["chat".into(), query, "--offline".into(), "--json".into()]); - push_top_k(&mut args, request.top_k); - } - RagmirCommandKind::ChatSetup => { - args.extend([ - "chat".into(), - "setup".into(), - "--allow-remote-models".into(), - "--json".into(), - ]); - } - RagmirCommandKind::ChatDoctor => { - args.extend(["chat".into(), "doctor".into(), "--json".into()]); - } - } - - Ok(args) -} - -fn required_query(request: &RagmirCommandRequest) -> Result { - let query = request.query.as_deref().unwrap_or("").trim(); - if query.is_empty() { - return Err("Query is required.".into()); - } - Ok(query.into()) -} - -fn required_text(request: &RagmirCommandRequest) -> Result { - let text = request.text.as_deref().unwrap_or("").trim(); - if text.is_empty() { - return Err("Audio text is required.".into()); - } - Ok(text.into()) -} - -fn write_audio_summary_text( - request: &RagmirCommandRequest, - project_root: &str, -) -> Result { - let text = required_text(request)?; - let audio_dir = PathBuf::from(project_root).join(".ragmir").join("audio"); - fs::create_dir_all(&audio_dir) - .map_err(|error| format!("Unable to prepare audio dir: {error}"))?; - - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|error| format!("Unable to create audio timestamp: {error}"))? - .as_secs(); - let text_file = audio_dir.join(format!("retrieval-report-{timestamp}.txt")); - fs::write(&text_file, text).map_err(|error| format!("Unable to write audio text: {error}"))?; - Ok(text_file.to_string_lossy().into_owned()) -} - -fn write_audio_preload_text( - request: &RagmirCommandRequest, - project_root: &str, -) -> Result { - let text = required_text(request)?; - let audio_dir = PathBuf::from(project_root).join(".ragmir").join("audio"); - fs::create_dir_all(&audio_dir) - .map_err(|error| format!("Unable to prepare audio dir: {error}"))?; - - let text_file = audio_dir.join("tts-preload.txt"); - fs::write(&text_file, text).map_err(|error| format!("Unable to write audio text: {error}"))?; - Ok(text_file.to_string_lossy().into_owned()) -} - -fn push_top_k(args: &mut Vec, top_k: Option) { - if let Some(top_k) = top_k { - args.extend(["--top-k".into(), top_k.to_string()]); - } -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_dialog::init()) - .invoke_handler(tauri::generate_handler![ - run_ragmir_command, - read_ragmir_config, - write_ragmir_config - ]) - .run(tauri::generate_context!()) - .expect("error while running Ragmir") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn chat_setup_explicitly_allows_remote_model_preload() { - let request = command_request(RagmirCommandKind::ChatSetup); - - let args = ragmir_args(&request, "/tmp/ragmir-project").expect("chat setup args"); - - assert_eq!( - args, - vec![ - "--project-root", - "/tmp/ragmir-project", - "chat", - "setup", - "--allow-remote-models", - "--json" - ] - ); - } - - #[test] - fn audio_preload_writes_non_sensitive_text_and_allows_remote_model_preload() { - let project_root = unique_test_dir("audio-preload"); - let request = RagmirCommandRequest { - project_root: project_root.to_string_lossy().into_owned(), - command: RagmirCommandKind::AudioPreload, - query: None, - text: Some("Ragmir offline audio model preload.".into()), - rebuild: None, - top_k: None, - }; - - let args = ragmir_args(&request, project_root.to_str().expect("project root")) - .expect("audio preload args"); - - let text_file = project_root - .join(".ragmir") - .join("audio") - .join("tts-preload.txt"); - assert_eq!( - fs::read_to_string(&text_file).expect("preload text"), - "Ragmir offline audio model preload." - ); - assert_eq!( - args, - vec![ - "--project-root", - project_root.to_str().expect("project root"), - "audio", - text_file.to_str().expect("text file"), - "--engine", - "transformers", - "--allow-remote-models", - "--json" - ] - ); - - fs::remove_dir_all(project_root).expect("cleanup"); - } - - #[test] - fn finds_local_workspace_cli_from_nested_tauri_directory() { - let repo_root = unique_test_dir("workspace-cli"); - let cli_path = repo_root - .join("packages") - .join("ragmir-core") - .join("dist") - .join("cli.js"); - fs::create_dir_all(cli_path.parent().expect("cli parent")).expect("cli parent dir"); - fs::write(&cli_path, "#!/usr/bin/env node\n").expect("cli file"); - let nested_tauri_dir = repo_root - .join("packages") - .join("ragmir-app") - .join("src-tauri"); - fs::create_dir_all(&nested_tauri_dir).expect("nested tauri dir"); - - assert_eq!( - find_local_workspace_cli_from(&nested_tauri_dir), - Some(cli_path) - ); - - fs::remove_dir_all(repo_root).expect("cleanup"); - } - - fn command_request(command: RagmirCommandKind) -> RagmirCommandRequest { - RagmirCommandRequest { - project_root: "/tmp/ragmir-project".into(), - command, - query: None, - text: None, - rebuild: None, - top_k: None, - } - } - - fn unique_test_dir(label: &str) -> PathBuf { - let nonce = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("clock") - .as_nanos(); - let dir = std::env::temp_dir().join(format!("ragmir-app-{label}-{nonce}")); - fs::create_dir_all(&dir).expect("test dir"); - dir - } -} diff --git a/packages/ragmir-app/src-tauri/src/main.rs b/packages/ragmir-app/src-tauri/src/main.rs deleted file mode 100644 index 70ee45f..0000000 --- a/packages/ragmir-app/src-tauri/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - ragmir_app_lib::run() -} diff --git a/packages/ragmir-app/src-tauri/tauri.conf.json b/packages/ragmir-app/src-tauri/tauri.conf.json deleted file mode 100644 index 452ff29..0000000 --- a/packages/ragmir-app/src-tauri/tauri.conf.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "Ragmir", - "version": "0.4.10", - "identifier": "works.jcode.ragmir", - "build": { - "beforeDevCommand": "pnpm dev:web", - "beforeBuildCommand": "pnpm build", - "devUrl": "http://localhost:5173", - "frontendDist": "../dist" - }, - "app": { - "windows": [ - { - "title": "Ragmir", - "width": 1180, - "height": 820, - "minWidth": 390, - "minHeight": 720 - } - ], - "security": { - "csp": { - "default-src": "'self'", - "connect-src": "ipc: http://ipc.localhost", - "font-src": "'self'", - "img-src": "'self' asset: http://asset.localhost blob: data:", - "media-src": "'self' asset: http://asset.localhost blob: data:", - "script-src": "'self'", - "style-src": "'self' 'unsafe-inline'" - }, - "assetProtocol": { - "enable": true, - "scope": { - "allow": ["$HOME/**/.ragmir/audio/**"] - } - } - } - }, - "bundle": { - "active": true, - "targets": "all", - "category": "Productivity", - "shortDescription": "Sovereign local RAG for confidential dossiers.", - "longDescription": "Ragmir is a local-first document retrieval app for confidential dossiers and AI agents." - } -} diff --git a/packages/ragmir-app/src/app.tsx b/packages/ragmir-app/src/app.tsx deleted file mode 100644 index 58720db..0000000 --- a/packages/ragmir-app/src/app.tsx +++ /dev/null @@ -1,4601 +0,0 @@ -import { - Badge, - Button, - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - cn, - Field, - FieldContent, - FieldDescription, - FieldGroup, - FieldLabel, - FieldLegend, - FieldSet, - Input, - Progress, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, - Switch, - Textarea, -} from "@jcode.labs/ragmir-ui" -import { convertFileSrc } from "@tauri-apps/api/core" -import { open } from "@tauri-apps/plugin-dialog" -import { - Activity, - ArrowUp, - BookOpenCheck, - CheckCircle2, - Cloud, - Copy, - Download, - ExternalLink, - FileSearch, - FolderOpen, - FolderPlus, - HardDrive, - KeyRound, - LoaderCircle, - LockKeyhole, - MessageSquareText, - Pause, - Play, - RefreshCw, - ShieldCheck, - Sparkles, - Terminal, - Trash2, - TriangleAlert, - Volume2, - Wand2, - WifiOff, -} from "lucide-react" -import { type DragEvent, type FormEvent, lazy, Suspense, useEffect, useRef, useState } from "react" -import type { Components } from "react-markdown" -import { - clearLicenseKey, - type LicenseValidation, - loadLicenseKey, - saveLicenseKey, - validateLicenseKey, -} from "./lib/license.js" -import { - createProject, - joinProjectPath, - loadActiveProjectId, - loadProjects, - normalizeProjectRoot, - type ProjectSourceKind, - type ProjectStatus, - type RagmirProject, - removeProject, - saveActiveProjectId, - saveProjects, - upsertProject, -} from "./lib/project-registry.js" -import { - type AudioDoctorReport, - type AudioRenderResult, - type ChatDoctorReport, - type ChatResult, - type DoctorReport, - type IngestResult, - type RagmirConfigFile, - readRagmirConfig, - runAudioDoctor, - runAudioPreload, - runAudioSummary, - runChat, - runChatDoctor, - runChatSetup, - runDoctor, - runIngest, - runModelsPull, - runSecurityAudit, - runStatus, - type SecurityAuditReport, - type StatusReport, - writeRagmirConfig, -} from "./lib/ragmir-sidecar.js" - -type View = "chat" | "config" | "privacy" | "license" -type SetupStepId = "initialize" | "semantic" | "chat" | "tts" | "index" | "verify" -type SetupStepStatus = "idle" | "running" | "done" | "error" -type ChatMessageRole = "user" | "assistant" -type ChatMessageStatus = "done" | "thinking" | "streaming" | "error" -type ChatMessageAudioStatus = "rendering" | "ready" | "playing" | "paused" | "error" -type ChatRuntime = "local" | "codex" | "claude" | "other-agent" -type ChatSource = ChatResult["sources"][number] - -const EMPTY_LICENSE_VALIDATION: LicenseValidation = { - status: "empty", - message: "No license key is installed.", -} -const AUTO_INGEST_POLL_MS = 30_000 -const AUTO_INGEST_INTERVAL_MS = 5 * 60 * 1000 -const CHAT_TOP_K = 6 -const CHAT_HISTORY_MESSAGE_LIMIT = 8 -const CHAT_HISTORY_CHAR_LIMIT = 2400 -const CHAT_HISTORY_MESSAGE_CHAR_LIMIT = 420 -const CHAT_AUDIO_SOURCE_LIMIT = 3 -const ASSISTANT_STREAM_CHUNK_SIZE = 5 -const ASSISTANT_STREAM_DELAY_MS = 24 -const EMPTY_WORKSPACE_MESSAGE = "Add a project to create a private Ragmir workspace." -const CHAT_THREADS_STORAGE_KEY = "ragmir.chatThreads.v1" -const ACTIVE_CHAT_ID_STORAGE_KEY = "ragmir.activeChatId.v1" -const CHAT_RUNTIME_STORAGE_KEY = "ragmir.chatRuntime.v1" - -const Markdown = lazy(() => import("react-markdown")) - -const CHAT_MARKDOWN_COMPONENTS: Components = { - a({ children, href }) { - return ( - - {children} - - ) - }, - blockquote({ children }) { - return ( -
- {children} -
- ) - }, - code({ children, className }) { - return ( - - {children} - - ) - }, - h1({ children }) { - return

{children}

- }, - h2({ children }) { - return

{children}

- }, - h3({ children }) { - return

{children}

- }, - li({ children }) { - return
  • {children}
  • - }, - ol({ children }) { - return
      {children}
    - }, - p({ children }) { - return

    {children}

    - }, - pre({ children }) { - return ( -
    -        {children}
    -      
    - ) - }, - ul({ children }) { - return
      {children}
    - }, -} - -interface SetupStep { - id: SetupStepId - label: string - detail: string - status: SetupStepStatus -} - -interface ChatMessage { - id: string - role: ChatMessageRole - content: string - createdAt: string - audio?: ChatMessageAudio - result?: ChatResult - status?: ChatMessageStatus - statusLabel?: string -} - -interface ChatMessageAudio { - status: ChatMessageAudioStatus - outputPath?: string - outputFormat?: AudioRenderResult["outputFormat"] - engine?: AudioRenderResult["engine"] - model?: string - renderedAt?: string - error?: string -} - -type ChatMessageAudioPatch = Partial> & { - error?: string | null -} - -type ChatMessagePatch = Partial> & { - audio?: ChatMessageAudio | null - statusLabel?: string | null -} - -interface ChatThread { - id: string - projectId: string - title: string - createdAt: string - updatedAt: string - messages: ChatMessage[] -} - -const SETUP_STEP_DEFINITIONS: Array> = [ - { - id: "initialize", - label: "Initialize local workspace", - detail: "Create ignored .ragmir state and safety defaults.", - }, - { - id: "semantic", - label: "Prepare semantic search", - detail: "Download the local Transformers embedding model once.", - }, - { - id: "chat", - label: "Prepare local chat", - detail: "Preload the Transformers chat model for offline answers.", - }, - { - id: "tts", - label: "Prepare offline audio", - detail: "Preload the local TTS model with a non-sensitive sample.", - }, - { - id: "index", - label: "Index documents", - detail: "Rebuild the private index with semantic embeddings.", - }, - { - id: "verify", - label: "Verify privacy and readiness", - detail: "Check config, index, chat, and local privacy posture.", - }, -] - -type EmbeddingProvider = "local-hash" | "transformers" - -interface ConfigFormState { - rawDir: string - sourcesText: string - embeddingProvider: EmbeddingProvider - transformersAllowRemoteModels: boolean - redactionEnabled: boolean - redactionBuiltIn: boolean - accessLog: boolean - baseConfig: Record -} - -type ConfigFormPatch = Partial> - -const DEFAULT_CONFIG_FORM: ConfigFormState = { - rawDir: ".ragmir/raw", - sourcesText: "", - embeddingProvider: "transformers", - transformersAllowRemoteModels: false, - redactionEnabled: true, - redactionBuiltIn: true, - accessLog: true, - baseConfig: {}, -} - -const DIRECT_FOLDER_CONFIG_PATCH: ConfigFormPatch = { - rawDir: ".ragmir/raw", - sourcesText: ".", - embeddingProvider: "transformers", - transformersAllowRemoteModels: false, - redactionEnabled: true, - redactionBuiltIn: true, - accessLog: true, -} - -export function App(): React.JSX.Element { - const [view, setView] = useState("chat") - const [projects, setProjects] = useState(() => loadProjects()) - const [activeProjectId, setActiveProjectId] = useState(() => loadActiveProjectId()) - const [chatThreads, setChatThreads] = useState(() => loadChatThreads()) - const [activeChatId, setActiveChatId] = useState(() => loadActiveChatId()) - const [chatRuntime, setChatRuntime] = useState(() => loadChatRuntime()) - const [projectRoot, setProjectRoot] = useState("") - const [googleDriveRoot, setGoogleDriveRoot] = useState("") - const [dropStatus, setDropStatus] = useState("Add a project folder or drop it here.") - const [runtimeMessage, setRuntimeMessage] = useState(EMPTY_WORKSPACE_MESSAGE) - const [isRunning, setIsRunning] = useState(false) - const [isChoosingFolder, setIsChoosingFolder] = useState(false) - const [setupSteps, setSetupSteps] = useState(() => createSetupSteps()) - const [question, setQuestion] = useState("") - const [chatResult, setChatResult] = useState(null) - const [securityReport, setSecurityReport] = useState(null) - const [statusReport, setStatusReport] = useState(null) - const [chatDoctorReport, setChatDoctorReport] = useState(null) - const [audioDoctorReport, setAudioDoctorReport] = useState(null) - const [audioRenderingMessageId, setAudioRenderingMessageId] = useState(null) - const [playingAudioMessageId, setPlayingAudioMessageId] = useState(null) - const [configFile, setConfigFile] = useState(null) - const [configForm, setConfigForm] = useState(DEFAULT_CONFIG_FORM) - const [licenseKeyInput, setLicenseKeyInput] = useState(() => loadLicenseKey()) - const [licenseValidation, setLicenseValidation] = - useState(EMPTY_LICENSE_VALIDATION) - const [isLicenseChecking, setIsLicenseChecking] = useState(false) - const projectsRef = useRef(projects) - const isRunningRef = useRef(isRunning) - const audioPlayerRef = useRef(null) - const playingAudioTargetRef = useRef<{ chatId: string; messageId: string } | null>(null) - const autoIngestRunnerRef = useRef<(project: RagmirProject) => Promise>(async () => {}) - const activeProject = projects.find((project) => project.id === activeProjectId) ?? null - const activeProjectChats = activeProject - ? chatThreads.filter((thread) => thread.projectId === activeProject.id) - : [] - const activeChat = - activeProjectChats.find((thread) => thread.id === activeChatId) ?? - activeProjectChats.at(0) ?? - null - const activeChatResult = lastThreadResult(activeChat) ?? chatResult - autoIngestRunnerRef.current = runAutoIngestProject - - useEffect(() => { - document.documentElement.classList.add("dark") - let isCurrent = true - validateLicenseKey(loadLicenseKey()).then((validation) => { - if (isCurrent) { - setLicenseValidation(validation) - } - }) - return () => { - isCurrent = false - } - }, []) - - useEffect(() => { - saveProjects(projects) - projectsRef.current = projects - }, [projects]) - - useEffect(() => { - saveChatThreads(chatThreads) - }, [chatThreads]) - - useEffect(() => { - saveChatRuntime(chatRuntime) - }, [chatRuntime]) - - useEffect(() => { - isRunningRef.current = isRunning - }, [isRunning]) - - useEffect( - () => () => { - audioPlayerRef.current?.pause() - audioPlayerRef.current = null - playingAudioTargetRef.current = null - }, - [], - ) - - useEffect(() => { - const timerId = window.setInterval(() => { - if (isRunningRef.current) { - return - } - const project = projectsRef.current.find(shouldAutoIngestProject) - if (project) { - void autoIngestRunnerRef.current(project) - } - }, AUTO_INGEST_POLL_MS) - - return () => window.clearInterval(timerId) - }, []) - - useEffect(() => { - if (activeProjectId && projects.some((project) => project.id === activeProjectId)) { - saveActiveProjectId(activeProjectId) - return - } - - const firstProjectId = projects.at(0)?.id ?? null - setActiveProjectId(firstProjectId) - saveActiveProjectId(firstProjectId) - }, [activeProjectId, projects]) - - useEffect(() => { - if (!activeProject) { - setActiveChatId(null) - saveActiveChatId(null) - return - } - - const chatBelongsToProject = chatThreads.some( - (thread) => thread.projectId === activeProject.id && thread.id === activeChatId, - ) - if (chatBelongsToProject) { - saveActiveChatId(activeChatId) - return - } - - const nextChatId = - chatThreads.find((thread) => thread.projectId === activeProject.id)?.id ?? null - setActiveChatId(nextChatId) - saveActiveChatId(nextChatId) - }, [activeProject, activeChatId, chatThreads]) - - useEffect(() => { - if (activeProject && runtimeMessage === EMPTY_WORKSPACE_MESSAGE) { - setRuntimeMessage( - `${activeProject.name} selected. Run Prepare workspace before asking questions.`, - ) - } - }, [activeProject, runtimeMessage]) - - function registerProject( - root: string, - sourceKind: ProjectSourceKind = "local-folder", - autoIngestEnabled = false, - ): void { - const normalizedRoot = normalizeProjectRoot(root) - const existingProject = projects.find((project) => project.projectRoot === normalizedRoot) - const now = new Date().toISOString() - const project = existingProject - ? { - ...existingProject, - sourceKind, - autoIngestEnabled: autoIngestEnabled || existingProject.autoIngestEnabled, - updatedAt: now, - } - : createProject({ projectRoot: normalizedRoot, sourceKind, autoIngestEnabled }) - setProjects((currentProjects) => upsertProject(currentProjects, project)) - selectProject(project.id) - setView("chat") - setProjectRoot("") - setGoogleDriveRoot("") - setDropStatus( - sourceKind === "google-drive" - ? `${project.name} is connected as a local Google Drive folder.` - : `${project.name} is ready to prepare.`, - ) - setRuntimeMessage(`${project.name} added. Run Prepare workspace before asking questions.`) - setSetupSteps(createSetupSteps()) - } - - async function handleChooseFolder(sourceKind: ProjectSourceKind = "local-folder"): Promise { - setIsChoosingFolder(true) - setDropStatus("Opening native folder picker...") - setRuntimeMessage("Opening native folder picker...") - try { - const selectedPath = await chooseProjectFolder() - if (!selectedPath) { - setDropStatus("Folder selection cancelled.") - setRuntimeMessage("Folder selection cancelled.") - return - } - registerProject(selectedPath, sourceKind, sourceKind === "google-drive") - } catch (error) { - const message = error instanceof Error ? error.message : "Unable to open the folder picker." - setDropStatus(message) - setRuntimeMessage(message) - } finally { - setIsChoosingFolder(false) - } - } - - function handleProjectSubmit(event: FormEvent): void { - event.preventDefault() - try { - registerProject(projectRoot) - } catch (error) { - setDropStatus(error instanceof Error ? error.message : "Project root is required.") - } - } - - function handleGoogleDriveSubmit(event: FormEvent): void { - event.preventDefault() - try { - registerProject(googleDriveRoot, "google-drive", true) - setRuntimeMessage("Google Drive folder connected. Auto-indexing is enabled locally.") - } catch (error) { - setDropStatus( - error instanceof Error ? error.message : "Google Drive folder path is required.", - ) - } - } - - function handleDrop(event: DragEvent): void { - event.preventDefault() - const droppedPath = droppedProjectPath(event.dataTransfer) - if (droppedPath) { - try { - registerProject(droppedPath) - } catch (error) { - setDropStatus(error instanceof Error ? error.message : "Dropped folder path is invalid.") - } - return - } - - const itemCount = event.dataTransfer.items.length || event.dataTransfer.files.length - setDropStatus( - itemCount > 0 - ? `${itemCount} local item${itemCount === 1 ? "" : "s"} detected. Use Add project if the OS did not expose the path.` - : "Use Add project when drag and drop does not expose the path.", - ) - } - - function handleRemoveProject(projectId: string): void { - if (projectId === activeProjectId) { - setChatResult(null) - setSecurityReport(null) - setStatusReport(null) - setChatDoctorReport(null) - setAudioDoctorReport(null) - setConfigFile(null) - setConfigForm(DEFAULT_CONFIG_FORM) - } - setChatThreads((currentThreads) => - currentThreads.filter((thread) => thread.projectId !== projectId), - ) - setProjects((currentProjects) => removeProject(currentProjects, projectId)) - } - - function selectProject(projectId: string): void { - setActiveProjectId(projectId) - setChatResult(null) - setSecurityReport(null) - setStatusReport(null) - setChatDoctorReport(null) - setAudioDoctorReport(null) - setConfigFile(null) - setConfigForm(DEFAULT_CONFIG_FORM) - setSetupSteps(createSetupSteps()) - setView("chat") - } - - function handleNewChat(): void { - if (!activeProject) { - setRuntimeMessage("Choose a project before creating a chat.") - return - } - - const thread = createChatThread(activeProject.id) - setChatThreads((currentThreads) => [thread, ...currentThreads]) - setActiveChatId(thread.id) - setQuestion("") - setChatResult(null) - setView("chat") - setRuntimeMessage(`New chat created in ${activeProject.name}.`) - } - - function handleSelectChat(chatId: string): void { - const thread = chatThreads.find((entry) => entry.id === chatId) - if (!thread) { - return - } - setActiveChatId(chatId) - setQuestion("") - setChatResult(lastThreadResult(thread)) - setView("chat") - } - - async function handleLicenseSubmit(event: FormEvent): Promise { - event.preventDefault() - setIsLicenseChecking(true) - try { - const validation = await validateLicenseKey(licenseKeyInput) - setLicenseValidation(validation) - if (validation.status === "valid") { - saveLicenseKey(licenseKeyInput.trim()) - } - setRuntimeMessage(validation.message) - } finally { - setIsLicenseChecking(false) - } - } - - function handleClearLicense(): void { - clearLicenseKey() - setLicenseKeyInput("") - setLicenseValidation(EMPTY_LICENSE_VALIDATION) - setRuntimeMessage("License removed from local app storage.") - } - - async function handleRefreshProject(project: RagmirProject): Promise { - await runProjectCommand("Checking workspace", project, async () => { - const [report, status, chatDoctor, audioDoctor] = await Promise.all([ - runDoctor(project.projectRoot), - runStatus(project.projectRoot), - runChatDoctor(project.projectRoot), - runAudioDoctor(project.projectRoot), - ]) - updateProjectFromDoctor(project, report) - setStatusReport(status) - setChatDoctorReport(chatDoctor) - setAudioDoctorReport(audioDoctor) - setRuntimeMessage( - report.ready - ? "Workspace is ready." - : (report.nextSteps.at(0) ?? "Review workspace status."), - ) - }) - } - - async function handlePrepareProject(project: RagmirProject): Promise { - replaceProject({ ...project, status: "indexing", progress: Math.max(project.progress, 25) }) - setSetupSteps(createSetupSteps()) - await runProjectCommand("Preparing workspace", project, async () => { - await runSetupStep("initialize", async () => { - await ensureProjectConfigForPrepare(project, true) - const setupReport = await runDoctor(project.projectRoot, true) - updateProjectFromDoctor(project, setupReport) - }) - - await runSetupStep("semantic", async () => { - const model = await runModelsPull(project.projectRoot) - setRuntimeMessage(`Semantic search model ready: ${model.embeddingModel}.`) - }) - - await runSetupStep("chat", async () => { - const setupResult = await runChatSetup(project.projectRoot) - const doctor = await runChatDoctor(project.projectRoot) - setChatDoctorReport(doctor) - setRuntimeMessage(`Local chat model ready: ${setupResult.model}.`) - }) - - await runSetupStep("tts", async () => { - const preloadResult = await runAudioPreload(project.projectRoot) - const doctor = await runAudioDoctor(project.projectRoot) - setAudioDoctorReport(doctor) - setRuntimeMessage(`Offline audio model ready: ${preloadResult.model}.`) - }) - - let ingestResult: IngestResult | null = null - await runSetupStep("index", async () => { - ingestResult = await runIngest(project.projectRoot, true) - }) - - await runSetupStep("verify", async () => { - const [report, status, chatDoctor, audioDoctor, security] = await Promise.all([ - runDoctor(project.projectRoot), - runStatus(project.projectRoot), - runChatDoctor(project.projectRoot), - runAudioDoctor(project.projectRoot), - runSecurityAudit(project.projectRoot), - ]) - updateProjectFromDoctor(project, report) - setStatusReport(status) - setChatDoctorReport(chatDoctor) - setAudioDoctorReport(audioDoctor) - setSecurityReport(security) - }) - - if (ingestResult) { - setRuntimeMessage(preparedMessage(ingestResult)) - } else { - setRuntimeMessage("Workspace prepared.") - } - }) - } - - async function runSetupStep(stepId: SetupStepId, action: () => Promise): Promise { - const step = SETUP_STEP_DEFINITIONS.find((entry) => entry.id === stepId) - setSetupSteps((currentSteps) => updateSetupStep(currentSteps, stepId, "running")) - setRuntimeMessage(step ? `${step.label}...` : "Preparing workspace...") - try { - await action() - setSetupSteps((currentSteps) => updateSetupStep(currentSteps, stepId, "done")) - } catch (error) { - setSetupSteps((currentSteps) => updateSetupStep(currentSteps, stepId, "error")) - throw error - } - } - - async function handleIngestProject(project: RagmirProject): Promise { - replaceProject({ ...project, status: "indexing", progress: Math.max(project.progress, 40) }) - await runProjectCommand("Indexing new documents", project, async () => { - await ensureProjectConfigForPrepare(project) - const ingestResult = await runIngest(project.projectRoot) - const [report, status] = await Promise.all([ - runDoctor(project.projectRoot), - runStatus(project.projectRoot), - ]) - updateProjectFromDoctor(project, report) - setStatusReport(status) - setRuntimeMessage(preparedMessage(ingestResult)) - }) - } - - async function runAutoIngestProject(project: RagmirProject): Promise { - const startedAt = new Date().toISOString() - const watchedProject = { - ...project, - status: "indexing" as const, - progress: Math.max(project.progress, 40), - lastAutoIngestAt: startedAt, - } - replaceProject(watchedProject) - await runProjectCommand("Auto-indexing watched folder", watchedProject, async () => { - await ensureProjectConfigForPrepare(watchedProject) - const ingestResult = await runIngest(project.projectRoot) - const [report, status] = await Promise.all([ - runDoctor(project.projectRoot), - runStatus(project.projectRoot), - ]) - updateProjectFromDoctor(watchedProject, report, { lastAutoIngestAt: startedAt }) - setStatusReport(status) - setRuntimeMessage(preparedMessage(ingestResult)) - }) - } - - function handleToggleAutoIngest(project: RagmirProject): void { - const autoIngestEnabled = !project.autoIngestEnabled - replaceProject({ - ...project, - autoIngestEnabled, - lastAutoIngestAt: autoIngestEnabled ? project.lastAutoIngestAt : null, - updatedAt: new Date().toISOString(), - }) - setRuntimeMessage( - autoIngestEnabled - ? `${project.name} will re-index local changes every 5 minutes.` - : `${project.name} auto-indexing is disabled.`, - ) - } - - async function handlePrepareChat(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before preparing local chat.") - return - } - - await runProjectCommand("Preparing local chat model", activeProject, async () => { - const setupResult = await runChatSetup(activeProject.projectRoot) - const doctor = await runChatDoctor(activeProject.projectRoot) - setChatDoctorReport(doctor) - setRuntimeMessage(`Local chat model ready: ${setupResult.model}.`) - }) - } - - async function handlePrepareAudio(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before preparing offline audio.") - return - } - - await runProjectCommand("Preparing offline audio model", activeProject, async () => { - const preloadResult = await runAudioPreload(activeProject.projectRoot) - const doctor = await runAudioDoctor(activeProject.projectRoot) - setAudioDoctorReport(doctor) - setRuntimeMessage(`Offline audio model ready: ${preloadResult.model}.`) - }) - } - - async function handlePullModels(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before preparing semantic search.") - return - } - - await runProjectCommand("Preparing semantic search model", activeProject, async () => { - const model = await runModelsPull(activeProject.projectRoot) - const status = await runStatus(activeProject.projectRoot) - setStatusReport(status) - setRuntimeMessage(`Semantic search model ready: ${model.embeddingModel}.`) - }) - } - - async function ensureProjectConfigForPrepare( - project: RagmirProject, - resetTransformersProvider = false, - ): Promise { - const config = await readRagmirConfig(project.projectRoot) - const configState = createConfigFormState(config.content) - const hasSources = splitConfigLines(configState.sourcesText).length > 0 - const shouldResetProvider = - resetTransformersProvider && configState.embeddingProvider === "transformers" - const shouldUseBootstrapProvider = shouldResetProvider || !hasSources - if (config.exists && hasSources && !shouldResetProvider) { - return - } - - const directConfigState: ConfigFormState = { - ...configState, - rawDir: DIRECT_FOLDER_CONFIG_PATCH.rawDir ?? configState.rawDir, - sourcesText: hasSources - ? configState.sourcesText - : (DIRECT_FOLDER_CONFIG_PATCH.sourcesText ?? "."), - embeddingProvider: shouldUseBootstrapProvider ? "local-hash" : configState.embeddingProvider, - transformersAllowRemoteModels: false, - redactionEnabled: configState.redactionEnabled, - redactionBuiltIn: configState.redactionBuiltIn, - accessLog: configState.accessLog, - } - const savedConfig = await writeRagmirConfig( - project.projectRoot, - serializeConfigFormState(directConfigState), - ) - setConfigFile(savedConfig) - setConfigForm(createConfigFormState(savedConfig.content)) - } - - async function handleLoadConfig(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before loading Ragmir config.") - return - } - - await runProjectCommand("Loading Ragmir config", activeProject, async () => { - const config = await readRagmirConfig(activeProject.projectRoot) - setConfigFile(config) - setConfigForm(createConfigFormState(config.content)) - setRuntimeMessage( - config.exists - ? `Loaded ${config.configPath}.` - : "No config exists yet. Initialize the folder or save a config draft.", - ) - }) - } - - async function handleSaveConfig(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before saving Ragmir config.") - return - } - - await runProjectCommand("Saving Ragmir config", activeProject, async () => { - const config = await writeRagmirConfig( - activeProject.projectRoot, - serializeConfigFormState(configForm), - ) - const report = await runDoctor(activeProject.projectRoot) - updateProjectFromDoctor(activeProject, report) - setConfigFile(config) - setConfigForm(createConfigFormState(config.content)) - setRuntimeMessage("Config saved locally. Re-index if source or retrieval settings changed.") - }) - } - - async function handleInitializeConfigFolder(): Promise { - if (!activeProject) { - setRuntimeMessage("Add or select a project before initializing Ragmir.") - return - } - - await runProjectCommand("Initializing Ragmir folder", activeProject, async () => { - const report = await runDoctor(activeProject.projectRoot, true) - const [status, config] = await Promise.all([ - runStatus(activeProject.projectRoot), - readRagmirConfig(activeProject.projectRoot), - ]) - updateProjectFromDoctor(activeProject, report) - setStatusReport(status) - setConfigFile(config) - setConfigForm(createConfigFormState(config.content)) - setRuntimeMessage("Folder initialized. Config is ready to edit locally.") - }) - } - - function handleUseDirectFolderConfig(): void { - setConfigForm((currentConfig) => ({ ...currentConfig, ...DIRECT_FOLDER_CONFIG_PATCH })) - setRuntimeMessage('Config form updated to index the selected folder directly with source ".".') - } - - function handleConfigFormChange(patch: ConfigFormPatch): void { - setConfigForm((currentConfig) => ({ ...currentConfig, ...patch })) - } - - async function handleAskSubmit(event: FormEvent): Promise { - event.preventDefault() - if (!activeProject) { - setRuntimeMessage("Choose a workspace before asking Ragmir.") - return - } - - const trimmedQuestion = question.trim() - if (!trimmedQuestion) { - setRuntimeMessage("Write a question before asking Ragmir.") - return - } - - const targetThread = activeChat ?? createChatThread(activeProject.id, trimmedQuestion) - const userMessage = createChatMessage("user", trimmedQuestion, undefined, { status: "done" }) - const assistantMessage = createChatMessage("assistant", "", undefined, { - status: "thinking", - statusLabel: "Searching local context", - }) - const initialThread: ChatThread = { - ...targetThread, - title: - targetThread.messages.length === 0 - ? chatTitleFromQuestion(trimmedQuestion) - : targetThread.title, - messages: [...targetThread.messages, userMessage, assistantMessage], - updatedAt: assistantMessage.createdAt, - } - const contextualQuestion = buildContextualChatQuestion(targetThread.messages, trimmedQuestion) - - setActiveChatId(initialThread.id) - setQuestion("") - setChatThreads((currentThreads) => upsertChatThread(currentThreads, initialThread)) - setView("chat") - setIsRunning(true) - setRuntimeMessage("Searching local Ragmir context...") - - try { - const rawResult = await runChat(activeProject.projectRoot, contextualQuestion, CHAT_TOP_K) - const result = normalizeChatResultForDisplay(rawResult, trimmedQuestion) - setRuntimeMessage("Writing the answer...") - await streamAssistantMessage(initialThread.id, assistantMessage.id, result) - setChatResult(result) - setRuntimeMessage( - result.emptyContext - ? "No relevant local context found. Add or index documents, then ask again." - : `Answered from ${result.sources.length} cited source${result.sources.length === 1 ? "" : "s"} with recent chat context.`, - ) - } catch (error) { - const message = error instanceof Error ? error.message : "Local chat failed." - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, initialThread.id, assistantMessage.id, { - content: `I could not complete the local answer.\n\n${message}`, - status: "error", - statusLabel: "Failed", - }), - ) - setRuntimeMessage(message) - } finally { - setIsRunning(false) - } - } - - async function streamAssistantMessage( - chatId: string, - messageId: string, - result: ChatResult, - ): Promise { - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { - content: "", - status: "streaming", - statusLabel: "Writing answer", - }), - ) - - const chunks = chunkMarkdownForStream(result.answer) - let streamedContent = "" - for (const chunk of chunks) { - streamedContent += chunk - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { - content: streamedContent, - status: "streaming", - statusLabel: "Writing answer", - }), - ) - await sleep(ASSISTANT_STREAM_DELAY_MS) - } - - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { - content: result.answer, - result, - status: "done", - statusLabel: null, - }), - ) - } - - function handleExportMarkdown(): void { - if (!activeProject || !activeChatResult) { - setRuntimeMessage("Ask Ragmir before exporting a Markdown report.") - return - } - - downloadTextFile( - `${safeFilename(activeProject.name)}-ragmir-answer.md`, - retrievalReportMarkdown(activeProject, activeChatResult), - "text/markdown;charset=utf-8", - ) - setRuntimeMessage("Markdown report exported from the current answer.") - } - - async function handleRenderMessageAudio( - chatId: string, - messageId: string, - autoplay = false, - ): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before rendering audio.") - return - } - - const message = findChatMessage(chatThreads, chatId, messageId) - if (message?.role !== "assistant" || !message.content.trim()) { - setRuntimeMessage("Choose a completed Ragmir answer before rendering audio.") - return - } - - const audioText = chatMessageTtsText(activeProject, message) - setAudioRenderingMessageId(messageId) - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { - audio: { status: "rendering" }, - }), - ) - setRuntimeMessage("Rendering local offline audio for this answer...") - - try { - const result: AudioRenderResult = await runAudioSummary(activeProject.projectRoot, audioText) - const audio = chatAudioFromRenderResult(result) - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { audio }), - ) - setRuntimeMessage(`Audio ready: ${audio.outputPath}.`) - if (autoplay) { - await playChatMessageAudio(chatId, messageId, audio) - } - } catch (error) { - const messageText = error instanceof Error ? error.message : "Offline audio rendering failed." - setChatThreads((currentThreads) => - updateChatMessage(currentThreads, chatId, messageId, { - audio: { - status: "error", - error: audioErrorMessage(messageText), - }, - }), - ) - setRuntimeMessage(audioErrorMessage(messageText)) - } finally { - setAudioRenderingMessageId(null) - } - } - - async function handleToggleMessageAudio(chatId: string, messageId: string): Promise { - const message = findChatMessage(chatThreads, chatId, messageId) - if (!message) { - setRuntimeMessage("Audio target message was not found.") - return - } - - if (playingAudioMessageId === messageId) { - stopActiveAudio("paused") - return - } - - if (message.audio?.outputPath) { - await playChatMessageAudio(chatId, messageId, message.audio) - return - } - - await handleRenderMessageAudio(chatId, messageId, true) - } - - async function playChatMessageAudio( - chatId: string, - messageId: string, - audio: ChatMessageAudio, - ): Promise { - if (!audio.outputPath) { - setRuntimeMessage("Render audio before playing this answer.") - return - } - - stopActiveAudio("paused") - const player = new Audio(audioFileUrl(audio.outputPath)) - audioPlayerRef.current = player - playingAudioTargetRef.current = { chatId, messageId } - setPlayingAudioMessageId(messageId) - setChatThreads((currentThreads) => - updateChatMessageAudio(currentThreads, chatId, messageId, { - status: "playing", - error: null, - }), - ) - - player.addEventListener( - "ended", - () => { - audioPlayerRef.current = null - playingAudioTargetRef.current = null - setPlayingAudioMessageId(null) - setChatThreads((currentThreads) => - updateChatMessageAudio(currentThreads, chatId, messageId, { status: "ready" }), - ) - }, - { once: true }, - ) - player.addEventListener( - "error", - () => { - audioPlayerRef.current = null - playingAudioTargetRef.current = null - setPlayingAudioMessageId(null) - setChatThreads((currentThreads) => - updateChatMessageAudio(currentThreads, chatId, messageId, { - status: "error", - error: "The generated audio file could not be loaded by the desktop webview.", - }), - ) - }, - { once: true }, - ) - - try { - await player.play() - setRuntimeMessage("Playing the local audio answer.") - } catch (error) { - audioPlayerRef.current = null - playingAudioTargetRef.current = null - setPlayingAudioMessageId(null) - const messageText = - error instanceof Error ? error.message : "The audio player could not start." - setChatThreads((currentThreads) => - updateChatMessageAudio(currentThreads, chatId, messageId, { - status: "ready", - error: messageText, - }), - ) - setRuntimeMessage(`${messageText} Use Play again now that the file is ready.`) - } - } - - function stopActiveAudio(nextStatus: Extract): void { - const target = playingAudioTargetRef.current - audioPlayerRef.current?.pause() - audioPlayerRef.current = null - playingAudioTargetRef.current = null - setPlayingAudioMessageId(null) - if (target) { - setChatThreads((currentThreads) => - updateChatMessageAudio(currentThreads, target.chatId, target.messageId, { - status: nextStatus, - }), - ) - } - } - - async function handleSecurityAudit(): Promise { - if (!activeProject) { - setRuntimeMessage("Choose a workspace before running the privacy check.") - return - } - - await runProjectCommand("Checking privacy posture", activeProject, async () => { - const [report, status] = await Promise.all([ - runSecurityAudit(activeProject.projectRoot), - runStatus(activeProject.projectRoot), - ]) - setSecurityReport(report) - setStatusReport(status) - setRuntimeMessage( - report.warnings.length === 0 - ? "Privacy check passed." - : `Privacy check found ${report.warnings.length} warning${report.warnings.length === 1 ? "" : "s"}.`, - ) - }) - } - - async function runProjectCommand( - label: string, - project: RagmirProject, - action: () => Promise, - ): Promise { - setIsRunning(true) - setRuntimeMessage(`${label}...`) - try { - await action() - } catch (error) { - replaceProject({ ...project, status: "needs-review", progress: project.progress }) - setRuntimeMessage(error instanceof Error ? error.message : "Native Ragmir runtime failed.") - } finally { - setIsRunning(false) - } - } - - function updateProjectFromDoctor( - project: RagmirProject, - report: DoctorReport, - updates: Partial> = {}, - ): void { - replaceProject({ - ...project, - ...updates, - rawDir: report.rawDir, - storageDir: report.storageDir, - filesIndexed: report.indexedFiles, - chunksIndexed: report.chunksIndexed, - progress: projectProgress(report), - status: projectStatusFromDoctor(report), - updatedAt: new Date().toISOString(), - }) - } - - function replaceProject(project: RagmirProject): void { - setProjects((currentProjects) => - currentProjects.map((entry) => (entry.id === project.id ? project : entry)), - ) - } - - return ( -
    -
    - void handleChooseFolder()} - onNewChat={handleNewChat} - onRemoveProject={handleRemoveProject} - onSelectProject={selectProject} - onSelectChat={handleSelectChat} - onViewChange={setView} - projects={projects} - runtimeMessage={runtimeMessage} - view={view} - /> - -
    - {view !== "chat" ? ( - void handleChooseFolder()} - onPrepareProject={handlePrepareProject} - runtimeMessage={runtimeMessage} - view={view} - /> - ) : null} - - {view === "chat" ? ( - void handleChooseFolder()} - onChooseGoogleDriveFolder={() => void handleChooseFolder("google-drive")} - onDrop={handleDrop} - onGoogleDriveRootChange={setGoogleDriveRoot} - onGoogleDriveSubmit={handleGoogleDriveSubmit} - onIngestProject={handleIngestProject} - onExportMarkdown={handleExportMarkdown} - onNewChat={handleNewChat} - onPrepareAudio={handlePrepareAudio} - onPrepareChat={handlePrepareChat} - onPrepareProject={handlePrepareProject} - onProjectRootChange={setProjectRoot} - onProjectSubmit={handleProjectSubmit} - onRefreshProject={handleRefreshProject} - onRenderMessageAudio={handleRenderMessageAudio} - onStopMessageAudio={() => stopActiveAudio("paused")} - onToggleAutoIngest={handleToggleAutoIngest} - onToggleMessageAudio={handleToggleMessageAudio} - onAskSubmit={handleAskSubmit} - onQuestionChange={setQuestion} - onChatRuntimeChange={setChatRuntime} - onViewChange={setView} - playingAudioMessageId={playingAudioMessageId} - projectRoot={projectRoot} - question={question} - runtimeMessage={runtimeMessage} - setupSteps={setupSteps} - statusReport={statusReport} - /> - ) : null} - - {view === "config" ? ( - - ) : null} - - {view === "privacy" ? ( - - ) : null} - - {view === "license" ? ( - - ) : null} -
    -
    -
    - ) -} - -interface AppSidebarProps { - activeProject: RagmirProject | null - activeProjectId: string | null - activeChatId: string | null - activeProjectChats: ChatThread[] - isRunning: boolean - isChoosingFolder: boolean - licenseValidation: LicenseValidation - onChooseFolder: () => void - onNewChat: () => void - onRemoveProject: (projectId: string) => void - onSelectProject: (projectId: string) => void - onSelectChat: (chatId: string) => void - onViewChange: (view: View) => void - projects: RagmirProject[] - runtimeMessage: string - view: View -} - -function AppSidebar({ - activeProject, - activeProjectId, - activeChatId, - activeProjectChats, - isRunning, - isChoosingFolder, - licenseValidation, - onChooseFolder, - onNewChat, - onRemoveProject, - onSelectProject, - onSelectChat, - onViewChange, - projects, - runtimeMessage, - view, -}: AppSidebarProps): React.JSX.Element { - const navItems: Array<{ view: View; label: string; icon: React.ReactNode }> = [ - { view: "chat", label: "Chat", icon: