diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..3ac49fcf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,94 @@ +name: Release + +# release-please watches conventional commits on `main` and maintains a "release PR" +# that bumps the version (app/package.json + Cargo.toml + tauri.conf.json) and the +# changelog. Merging that PR cuts the GitHub release + tag, and the build job then +# attaches the platform bundles to it. workflow_dispatch runs a build-only smoke test +# (no release). Activates once this branch is merged to main. +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.rp.outputs.release_created }} + tag_name: ${{ steps.rp.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@v4 + id: rp + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + build: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' || github.event_name == 'workflow_dispatch' }} + strategy: + fail-fast: false + matrix: + include: + - platform: ubuntu-22.04 # Linux x64 (deb + AppImage) + bundles: 'deb,appimage' + - platform: macos-14 # macOS Apple Silicon + bundles: 'dmg' + - platform: macos-13 # macOS Intel + bundles: 'dmg' + - platform: windows-latest # Windows x64 + bundles: 'nsis' + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + # Tauri's webview + bundling deps (Linux only). The Ubuntu runner is Debian- + # based, so unlike an Arch host its linuxdeploy/AppImage tooling works. + - name: Install Linux build dependencies + if: startsWith(matrix.platform, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get install -y \ + libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \ + libayatana-appindicator3-dev patchelf file + + # Rust toolchain for the Tauri shell. + - uses: dtolnay/rust-toolchain@stable + - uses: swatinem/rust-cache@v2 + with: + workspaces: app/src-tauri + + # vite-plus toolchain + app deps (same as ci.yml). + - uses: voidzero-dev/setup-vp@v1 + with: + node-version: '24' + cache: true + - name: Install app dependencies + working-directory: app + run: vp install + + # Vendor the node sidecar for this runner's platform (gitignored, so it isn't + # in the checkout). vendor-node.sh resolves the host triple itself. + - name: Vendor the node sidecar + working-directory: app/src-tauri + shell: bash + run: ./vendor-node.sh + + # Build, sign the updater artifacts, attach bundles to the release-please + # release, and generate/aggregate latest.json across the matrix. tauri-action + # runs `vp build` (beforeBuildCommand) then the tauri build via the CLI vp + # installed. With no tag (workflow_dispatch), it builds only — a smoke test. + - name: Build, sign, and publish + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + with: + projectPath: app + tagName: ${{ needs.release-please.outputs.tag_name }} + args: --bundles ${{ matrix.bundles }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..ee53e341 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + "app": "0.1.0" +} diff --git a/AGENTS.md b/AGENTS.md index 01ddceab..48775ad5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,18 +13,20 @@ This is a single repo with three cooperating parts: in one process. Hosts the SQLite store, the block solver, the Factorio UDP bridge, the data-dump + icon pipeline, and the AI assistant. This is where almost all work happens. It has its own `AGENTS.md`/`CLAUDE.md` (Vite+ toolchain notes). + `app/src-tauri/` is the Tauri **desktop shell** that wraps this server in a native + window and packages it into a self-contained bundle (vendored Node + bundled + resources) with self-update — see [`docs/desktop.md`](docs/desktop.md). - **`mod/`** — the Factorio mod (`pyops`, Factorio 2.0): in-game panel, UDP link to the app, data-dump trigger, Helmod-style production-block view, and the request-combinator planner. Pure Lua, no build step. -- **`scripts/`** — one-off dev/test scripts (icon-atlas builder, atlas preview - server, a Factorio reload helper). Not part of the app build; has its own - `package.json` (uses `sharp`). +- **`scripts/`** — dev-only helpers (currently `tunnel-dev`, to expose the dev + server through a tunnel). Not part of the app build. Per-project SQLite stores live in `projects/*.db` (each self-describes its name in its own `meta`); `app-config.json` holds app-level config (active project + the -OpenRouter key/model). All are generated/gitignored. A legacy root `dev.db` is -migrated to `projects/default.db` on first run, and any old `projects.json` -registry is imported into the dbs' `meta` and backed up to `.bak`. +OpenRouter key/model). Both live under the app's **data dir** — the working dir in +dev, a per-OS user-data dir for a packaged build, overridable via `PYOPS_DATA_DIR` +(see `app/src/server/paths.ts`). All are generated/gitignored. ## App architecture (`app/src/`) @@ -67,8 +69,16 @@ from inside `app/`: the UDP-bridge round-trip, and `responsive.e2e.ts`, which screenshots every route across a desktop/tablet/phone matrix and **asserts no route scrolls sideways** at tablet/phone widths. See `app/e2e/README.md`. -- DB: `pnpm db:generate` / `db:migrate` / `db:push` / `db:studio` (drizzle-kit), - `pnpm db:import` to load the Factorio dump. +- DB schema: edit `src/db/schema.ts`, then `vp run db:generate ` to write a + migration under `drizzle/` (a name is required, so files stay meaningful). + `server/provision.ts` applies the migrations **in-process** on first connect to + each project db. `db:studio` opens the drizzle DB browser. A Factorio dump loads + through the in-app data sync. +- Desktop shell: `vp run tauri dev` runs the app in a native window; `vp run tauri + build` packages a bundle (run `src-tauri/vendor-node.sh` first). Releases are + automated by **release-please** — **don't hand-edit the version** in + `package.json` / `Cargo.toml` / `tauri.conf.json`; it bumps all three from + conventional commits. See [`docs/desktop.md`](docs/desktop.md). If setup/runtime/package-manager behavior looks wrong, run `vp env doctor`. diff --git a/README.md b/README.md index 6d59d468..e4bb44f9 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,29 @@ set, with produced-by / used-in (here: Iron plate — 14 producers, 160 consumer --- +## Desktop app + +PyOps also ships as a self-contained **desktop app** — a [Tauri](https://tauri.app) +window around the same server, bundling its own Node runtime so it needs no toolchain +to run. Grab the build for your OS from the +[Releases](https://github.com/ApocDev/pyops/releases) page: + +- **Linux** — `.AppImage` (`chmod +x` it and run; self-updates) or `.deb` +- **macOS** — `.dmg` +- **Windows** — the `-setup.exe` installer + +It still needs **Factorio + the Py mods** installed locally (it reads your recipe +data via a data sync), and a fresh install starts empty — open **⚙ Settings › Game +data** and run a sync on first launch, same as below. Bundled builds **check for +updates on launch** and prompt to install. For how it's built and released, see +[`docs/desktop.md`](docs/desktop.md). + +--- + ## Requirements +_To run from source (below). The desktop app needs none of this._ + - **Node.js** (current LTS) and **pnpm** — the app's toolchain ([Vite+](https://viteplus.dev/), the `vp` CLI) handles the rest. - **Factorio 2.0** installed locally, with the **Pyanodons** mod suite and diff --git a/app/drizzle/0000_salty_pixie.sql b/app/drizzle/0000_salty_pixie.sql new file mode 100644 index 00000000..8ce2b351 --- /dev/null +++ b/app/drizzle/0000_salty_pixie.sql @@ -0,0 +1,375 @@ +CREATE TABLE IF NOT EXISTS `beacons` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `distribution_effectivity` real, + `module_slots` integer DEFAULT 0 NOT NULL, + `energy_usage_w` real, + `hidden` integer DEFAULT false NOT NULL, + `allowed_effects` text, + `allowed_module_categories` text, + `profile` text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `belts` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `order` text, + `speed` real NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `block_flows` ( + `block_id` integer NOT NULL, + `item` text NOT NULL, + `kind` text NOT NULL, + `role` text NOT NULL, + `rate` real NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `bf_item_role_idx` ON `block_flows` (`item`,`role`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `bf_block_idx` ON `block_flows` (`block_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `block_groups` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `parent_id` integer, + `sort_order` integer, + `created_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `block_machines` ( + `block_id` integer NOT NULL, + `machine` text NOT NULL, + `recipe` text NOT NULL, + `count` real NOT NULL, + PRIMARY KEY(`block_id`, `machine`, `recipe`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `bm_machine_idx` ON `block_machines` (`machine`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `blocks` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `icon_kind` text, + `icon_name` text, + `data` text NOT NULL, + `electricity_w` real, + `solve_status` text, + `data_fingerprint` text, + `sort_order` integer, + `group_id` integer, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `built_machines` ( + `name` text NOT NULL, + `recipe` text NOT NULL, + `count` integer NOT NULL, + PRIMARY KEY(`name`, `recipe`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `conversation_messages` ( + `id` text PRIMARY KEY NOT NULL, + `conversation_id` text NOT NULL, + `role` text NOT NULL, + `parts` text NOT NULL, + `seq` integer NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `cm_conv_idx` ON `conversation_messages` (`conversation_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `conversations` ( + `id` text PRIMARY KEY NOT NULL, + `title` text, + `model` text, + `reasoning_effort` text, + `last_input_tokens` integer, + `last_output_tokens` integer, + `last_total_tokens` integer, + `last_model_id` text, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `cost_analysis` ( + `scope` text NOT NULL, + `name` text NOT NULL, + `kind` text NOT NULL, + `cost` real NOT NULL, + PRIMARY KEY(`scope`, `name`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `crafting_machines` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `kind` text NOT NULL, + `crafting_speed` real NOT NULL, + `module_slots` integer DEFAULT 0 NOT NULL, + `energy_usage_w` real, + `energy_source` text, + `allowed_effects` text, + `allowed_module_categories` text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `drill_resource_categories` ( + `drill` text NOT NULL, + `resource_category` text NOT NULL, + PRIMARY KEY(`drill`, `resource_category`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `fluids` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `order` text, + `default_temperature` real, + `max_temperature` real, + `fuel_value_j` real, + `heat_capacity_j` real +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `inserters` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `order` text, + `rotation_speed` real NOT NULL, + `extension_speed` real NOT NULL, + `pickup_x` real NOT NULL, + `pickup_y` real NOT NULL, + `drop_x` real NOT NULL, + `drop_y` real NOT NULL, + `bulk` integer DEFAULT false NOT NULL, + `base_stack_bonus` integer DEFAULT 0 NOT NULL, + `max_belt_stack_size` integer DEFAULT 1 NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `items` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `subgroup` text, + `order` text, + `stack_size` integer, + `weight` real, + `fuel_value_j` real, + `fuel_category` text, + `spoil_result` text, + `spoil_ticks` integer, + `burnt_result` text, + `plant_result` text +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `items_subgroup_idx` ON `items` (`subgroup`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `loaders` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `order` text, + `speed` real NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `machine_categories` ( + `machine` text NOT NULL, + `category` text NOT NULL, + PRIMARY KEY(`machine`, `category`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `mc_category_idx` ON `machine_categories` (`category`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `machine_fuel_categories` ( + `machine` text NOT NULL, + `fuel_category` text NOT NULL, + PRIMARY KEY(`machine`, `fuel_category`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `meta` ( + `key` text PRIMARY KEY NOT NULL, + `value` text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `mining_drills` ( + `name` text PRIMARY KEY NOT NULL, + `mining_speed` real NOT NULL, + `module_slots` integer DEFAULT 0 NOT NULL, + `energy_usage_w` real, + `energy_source` text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `module_limitations` ( + `module` text NOT NULL, + `recipe` text NOT NULL, + PRIMARY KEY(`module`, `recipe`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `module_presets` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `modules` text NOT NULL, + `beacons` text NOT NULL, + `created_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `modules` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `category` text, + `hidden` integer DEFAULT false NOT NULL, + `tier` integer, + `eff_speed` real DEFAULT 0 NOT NULL, + `eff_productivity` real DEFAULT 0 NOT NULL, + `eff_consumption` real DEFAULT 0 NOT NULL, + `eff_pollution` real DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `notes` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `title` text, + `body` text, + `sort_order` integer, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `production_stats` ( + `name` text PRIMARY KEY NOT NULL, + `kind` text NOT NULL, + `produced` real NOT NULL, + `consumed` real NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `recipe_categories` ( + `name` text PRIMARY KEY NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `recipe_ingredients` ( + `recipe` text NOT NULL, + `idx` integer NOT NULL, + `kind` text NOT NULL, + `name` text NOT NULL, + `amount` real NOT NULL, + `min_temp` real, + `max_temp` real, + PRIMARY KEY(`recipe`, `idx`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `ing_recipe_idx` ON `recipe_ingredients` (`recipe`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `ing_name_idx` ON `recipe_ingredients` (`name`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `recipe_products` ( + `recipe` text NOT NULL, + `idx` integer NOT NULL, + `kind` text NOT NULL, + `name` text NOT NULL, + `amount` real, + `amount_min` real, + `amount_max` real, + `probability` real DEFAULT 1 NOT NULL, + `temperature` real, + `ignored_by_productivity` integer DEFAULT false NOT NULL, + PRIMARY KEY(`recipe`, `idx`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `prod_recipe_idx` ON `recipe_products` (`recipe`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `prod_name_idx` ON `recipe_products` (`name`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `recipes` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `kind` text DEFAULT 'real' NOT NULL, + `category` text, + `energy_required` real, + `enabled` integer DEFAULT true NOT NULL, + `hidden` integer DEFAULT false NOT NULL, + `allow_productivity` integer DEFAULT false NOT NULL, + `allowed_module_categories` text, + `main_product` text, + `subgroup` text, + `order` text, + `source_entity` text +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `recipes_category_idx` ON `recipes` (`category`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `recipes_kind_idx` ON `recipes` (`kind`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `task_links` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `task_id` integer NOT NULL, + `ref_kind` text NOT NULL, + `ref_name` text NOT NULL, + `sort_order` integer +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `task_links_task_idx` ON `task_links` (`task_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `task_links_ref_idx` ON `task_links` (`ref_kind`,`ref_name`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `task_steps` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `task_id` integer NOT NULL, + `text` text NOT NULL, + `done` integer DEFAULT false NOT NULL, + `sort_order` integer +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `task_steps_task_idx` ON `task_steps` (`task_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `tasks` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `parent_id` integer, + `title` text, + `body` text, + `status` text DEFAULT 'open' NOT NULL, + `done` integer DEFAULT false NOT NULL, + `priority` text, + `priority_reason` text, + `priority_at` integer, + `sort_order` integer, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `task_parent_idx` ON `tasks` (`parent_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `tech_ingredients` ( + `technology` text NOT NULL, + `name` text NOT NULL, + `amount` real NOT NULL, + PRIMARY KEY(`technology`, `name`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `ti_tech_idx` ON `tech_ingredients` (`technology`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `tech_prerequisites` ( + `technology` text NOT NULL, + `prerequisite` text NOT NULL, + PRIMARY KEY(`technology`, `prerequisite`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `tp_prereq_idx` ON `tech_prerequisites` (`prerequisite`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `tech_stack_bonuses` ( + `technology` text NOT NULL, + `effect` text NOT NULL, + `modifier` real NOT NULL, + PRIMARY KEY(`technology`, `effect`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `tech_unlocks` ( + `technology` text NOT NULL, + `recipe` text NOT NULL, + PRIMARY KEY(`technology`, `recipe`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `tu_recipe_idx` ON `tech_unlocks` (`recipe`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `technologies` ( + `name` text PRIMARY KEY NOT NULL, + `display` text, + `order` text, + `unit_count` real, + `enabled` integer DEFAULT true NOT NULL, + `is_turd` integer DEFAULT false NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `todos` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `title` text NOT NULL, + `created_at` integer DEFAULT (unixepoch()) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `turd_replacements` ( + `sub_tech` text NOT NULL, + `old_recipe` text NOT NULL, + `new_recipe` text NOT NULL, + PRIMARY KEY(`sub_tech`, `old_recipe`) +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `tr_old_idx` ON `turd_replacements` (`old_recipe`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `turd_selections` ( + `master_tech` text PRIMARY KEY NOT NULL, + `sub_tech` text NOT NULL, + `updated_at` integer DEFAULT (unixepoch()) +); diff --git a/app/drizzle/meta/0000_snapshot.json b/app/drizzle/meta/0000_snapshot.json new file mode 100644 index 00000000..370ab777 --- /dev/null +++ b/app/drizzle/meta/0000_snapshot.json @@ -0,0 +1,2277 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f44fdf71-c921-4aec-8ca3-1206429b835a", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "beacons": { + "name": "beacons", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "distribution_effectivity": { + "name": "distribution_effectivity", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "module_slots": { + "name": "module_slots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "energy_usage_w": { + "name": "energy_usage_w", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden": { + "name": "hidden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "allowed_effects": { + "name": "allowed_effects", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_module_categories": { + "name": "allowed_module_categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "profile": { + "name": "profile", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "belts": { + "name": "belts", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "speed": { + "name": "speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "block_flows": { + "name": "block_flows", + "columns": { + "block_id": { + "name": "block_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item": { + "name": "item", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rate": { + "name": "rate", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "bf_item_role_idx": { + "name": "bf_item_role_idx", + "columns": [ + "item", + "role" + ], + "isUnique": false + }, + "bf_block_idx": { + "name": "bf_block_idx", + "columns": [ + "block_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "block_groups": { + "name": "block_groups", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "block_machines": { + "name": "block_machines", + "columns": { + "block_id": { + "name": "block_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "machine": { + "name": "machine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "bm_machine_idx": { + "name": "bm_machine_idx", + "columns": [ + "machine" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "block_machines_block_id_machine_recipe_pk": { + "columns": [ + "block_id", + "machine", + "recipe" + ], + "name": "block_machines_block_id_machine_recipe_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "blocks": { + "name": "blocks", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_kind": { + "name": "icon_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "electricity_w": { + "name": "electricity_w", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "solve_status": { + "name": "solve_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "data_fingerprint": { + "name": "data_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "built_machines": { + "name": "built_machines", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "built_machines_name_recipe_pk": { + "columns": [ + "name", + "recipe" + ], + "name": "built_machines_name_recipe_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "conversation_messages": { + "name": "conversation_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parts": { + "name": "parts", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "cm_conv_idx": { + "name": "cm_conv_idx", + "columns": [ + "conversation_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "conversations": { + "name": "conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning_effort": { + "name": "reasoning_effort", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_input_tokens": { + "name": "last_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_output_tokens": { + "name": "last_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_total_tokens": { + "name": "last_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_model_id": { + "name": "last_model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_analysis": { + "name": "cost_analysis", + "columns": { + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "cost_analysis_scope_name_pk": { + "columns": [ + "scope", + "name" + ], + "name": "cost_analysis_scope_name_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "crafting_machines": { + "name": "crafting_machines", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "crafting_speed": { + "name": "crafting_speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "module_slots": { + "name": "module_slots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "energy_usage_w": { + "name": "energy_usage_w", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "energy_source": { + "name": "energy_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_effects": { + "name": "allowed_effects", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_module_categories": { + "name": "allowed_module_categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "drill_resource_categories": { + "name": "drill_resource_categories", + "columns": { + "drill": { + "name": "drill", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_category": { + "name": "resource_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "drill_resource_categories_drill_resource_category_pk": { + "columns": [ + "drill", + "resource_category" + ], + "name": "drill_resource_categories_drill_resource_category_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "fluids": { + "name": "fluids", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_temperature": { + "name": "default_temperature", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_temperature": { + "name": "max_temperature", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fuel_value_j": { + "name": "fuel_value_j", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heat_capacity_j": { + "name": "heat_capacity_j", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inserters": { + "name": "inserters", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_speed": { + "name": "rotation_speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "extension_speed": { + "name": "extension_speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pickup_x": { + "name": "pickup_x", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pickup_y": { + "name": "pickup_y", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "drop_x": { + "name": "drop_x", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "drop_y": { + "name": "drop_y", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bulk": { + "name": "bulk", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "base_stack_bonus": { + "name": "base_stack_bonus", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "max_belt_stack_size": { + "name": "max_belt_stack_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "items": { + "name": "items", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subgroup": { + "name": "subgroup", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stack_size": { + "name": "stack_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fuel_value_j": { + "name": "fuel_value_j", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fuel_category": { + "name": "fuel_category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "spoil_result": { + "name": "spoil_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "spoil_ticks": { + "name": "spoil_ticks", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "burnt_result": { + "name": "burnt_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plant_result": { + "name": "plant_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "items_subgroup_idx": { + "name": "items_subgroup_idx", + "columns": [ + "subgroup" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "loaders": { + "name": "loaders", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "speed": { + "name": "speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_categories": { + "name": "machine_categories", + "columns": { + "machine": { + "name": "machine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "mc_category_idx": { + "name": "mc_category_idx", + "columns": [ + "category" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "machine_categories_machine_category_pk": { + "columns": [ + "machine", + "category" + ], + "name": "machine_categories_machine_category_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_fuel_categories": { + "name": "machine_fuel_categories", + "columns": { + "machine": { + "name": "machine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fuel_category": { + "name": "fuel_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "machine_fuel_categories_machine_fuel_category_pk": { + "columns": [ + "machine", + "fuel_category" + ], + "name": "machine_fuel_categories_machine_fuel_category_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "meta": { + "name": "meta", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mining_drills": { + "name": "mining_drills", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mining_speed": { + "name": "mining_speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "module_slots": { + "name": "module_slots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "energy_usage_w": { + "name": "energy_usage_w", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "energy_source": { + "name": "energy_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "module_limitations": { + "name": "module_limitations", + "columns": { + "module": { + "name": "module", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "module_limitations_module_recipe_pk": { + "columns": [ + "module", + "recipe" + ], + "name": "module_limitations_module_recipe_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "module_presets": { + "name": "module_presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "modules": { + "name": "modules", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "beacons": { + "name": "beacons", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "modules": { + "name": "modules", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden": { + "name": "hidden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "tier": { + "name": "tier", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_speed": { + "name": "eff_speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "eff_productivity": { + "name": "eff_productivity", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "eff_consumption": { + "name": "eff_consumption", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "eff_pollution": { + "name": "eff_pollution", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notes": { + "name": "notes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "production_stats": { + "name": "production_stats", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "produced": { + "name": "produced", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed": { + "name": "consumed", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recipe_categories": { + "name": "recipe_categories", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recipe_ingredients": { + "name": "recipe_ingredients", + "columns": { + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idx": { + "name": "idx", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "min_temp": { + "name": "min_temp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_temp": { + "name": "max_temp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "ing_recipe_idx": { + "name": "ing_recipe_idx", + "columns": [ + "recipe" + ], + "isUnique": false + }, + "ing_name_idx": { + "name": "ing_name_idx", + "columns": [ + "name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "recipe_ingredients_recipe_idx_pk": { + "columns": [ + "recipe", + "idx" + ], + "name": "recipe_ingredients_recipe_idx_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recipe_products": { + "name": "recipe_products", + "columns": { + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idx": { + "name": "idx", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_min": { + "name": "amount_min", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_max": { + "name": "amount_max", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "probability": { + "name": "probability", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "temperature": { + "name": "temperature", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ignored_by_productivity": { + "name": "ignored_by_productivity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "prod_recipe_idx": { + "name": "prod_recipe_idx", + "columns": [ + "recipe" + ], + "isUnique": false + }, + "prod_name_idx": { + "name": "prod_name_idx", + "columns": [ + "name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "recipe_products_recipe_idx_pk": { + "columns": [ + "recipe", + "idx" + ], + "name": "recipe_products_recipe_idx_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recipes": { + "name": "recipes", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'real'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "energy_required": { + "name": "energy_required", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "hidden": { + "name": "hidden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "allow_productivity": { + "name": "allow_productivity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "allowed_module_categories": { + "name": "allowed_module_categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "main_product": { + "name": "main_product", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subgroup": { + "name": "subgroup", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_entity": { + "name": "source_entity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "recipes_category_idx": { + "name": "recipes_category_idx", + "columns": [ + "category" + ], + "isUnique": false + }, + "recipes_kind_idx": { + "name": "recipes_kind_idx", + "columns": [ + "kind" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_links": { + "name": "task_links", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "task_id": { + "name": "task_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_kind": { + "name": "ref_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_name": { + "name": "ref_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "task_links_task_idx": { + "name": "task_links_task_idx", + "columns": [ + "task_id" + ], + "isUnique": false + }, + "task_links_ref_idx": { + "name": "task_links_ref_idx", + "columns": [ + "ref_kind", + "ref_name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_steps": { + "name": "task_steps", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "task_id": { + "name": "task_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "task_steps_task_idx": { + "name": "task_steps_task_idx", + "columns": [ + "task_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority_reason": { + "name": "priority_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority_at": { + "name": "priority_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "task_parent_idx": { + "name": "task_parent_idx", + "columns": [ + "parent_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tech_ingredients": { + "name": "tech_ingredients", + "columns": { + "technology": { + "name": "technology", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "ti_tech_idx": { + "name": "ti_tech_idx", + "columns": [ + "technology" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tech_ingredients_technology_name_pk": { + "columns": [ + "technology", + "name" + ], + "name": "tech_ingredients_technology_name_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tech_prerequisites": { + "name": "tech_prerequisites", + "columns": { + "technology": { + "name": "technology", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prerequisite": { + "name": "prerequisite", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tp_prereq_idx": { + "name": "tp_prereq_idx", + "columns": [ + "prerequisite" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tech_prerequisites_technology_prerequisite_pk": { + "columns": [ + "technology", + "prerequisite" + ], + "name": "tech_prerequisites_technology_prerequisite_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tech_stack_bonuses": { + "name": "tech_stack_bonuses", + "columns": { + "technology": { + "name": "technology", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "modifier": { + "name": "modifier", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tech_stack_bonuses_technology_effect_pk": { + "columns": [ + "technology", + "effect" + ], + "name": "tech_stack_bonuses_technology_effect_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tech_unlocks": { + "name": "tech_unlocks", + "columns": { + "technology": { + "name": "technology", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipe": { + "name": "recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tu_recipe_idx": { + "name": "tu_recipe_idx", + "columns": [ + "recipe" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tech_unlocks_technology_recipe_pk": { + "columns": [ + "technology", + "recipe" + ], + "name": "tech_unlocks_technology_recipe_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "technologies": { + "name": "technologies", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display": { + "name": "display", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_count": { + "name": "unit_count", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_turd": { + "name": "is_turd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "todos": { + "name": "todos", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "turd_replacements": { + "name": "turd_replacements", + "columns": { + "sub_tech": { + "name": "sub_tech", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_recipe": { + "name": "old_recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_recipe": { + "name": "new_recipe", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tr_old_idx": { + "name": "tr_old_idx", + "columns": [ + "old_recipe" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "turd_replacements_sub_tech_old_recipe_pk": { + "columns": [ + "sub_tech", + "old_recipe" + ], + "name": "turd_replacements_sub_tech_old_recipe_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "turd_selections": { + "name": "turd_selections", + "columns": { + "master_tech": { + "name": "master_tech", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sub_tech": { + "name": "sub_tech", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json new file mode 100644 index 00000000..042ce3fc --- /dev/null +++ b/app/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1782787858823, + "tag": "0000_salty_pixie", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/app/package.json b/app/package.json index 696966f4..4a0d009e 100644 --- a/app/package.json +++ b/app/package.json @@ -1,5 +1,6 @@ { "name": "app", + "version": "0.1.0", "private": true, "type": "module", "imports": { @@ -11,12 +12,9 @@ "build": "vp build", "preview": "vp preview", "test": "vp test run", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:push": "drizzle-kit push", - "db:pull": "drizzle-kit pull", + "db:generate": "node scripts/db-generate.mjs", "db:studio": "drizzle-kit studio", - "db:import": "tsx src/db/import-factorio.ts" + "tauri": "tauri" }, "dependencies": { "@ai-sdk/react": "^3.0.206", @@ -69,6 +67,7 @@ "@tailwindcss/typography": "^0.5.16", "@tanstack/devtools-vite": "latest", "@tanstack/router-cli": "^1.132.0", + "@tauri-apps/cli": "^2.11.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.0", "@types/better-sqlite3": "^7.6.0", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 83b17103..7fb5a1fc 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -153,6 +153,9 @@ importers: '@tanstack/router-cli': specifier: ^1.132.0 version: 1.167.17 + '@tauri-apps/cli': + specifier: ^2.11.0 + version: 2.11.4 '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -2876,6 +2879,82 @@ packages: resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} engines: {node: '>=20.19'} + '@tauri-apps/cli-darwin-arm64@2.11.4': + resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.11.4': + resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tauri-apps/cli-linux-x64-musl@2.11.4': + resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.11.4': + resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} + engines: {node: '>= 10'} + hasBin: true + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -7983,6 +8062,53 @@ snapshots: '@tanstack/virtual-file-routes@1.162.0': {} + '@tauri-apps/cli-darwin-arm64@2.11.4': + optional: true + + '@tauri-apps/cli-darwin-x64@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + optional: true + + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.11.4': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + optional: true + + '@tauri-apps/cli@2.11.4': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.11.4 + '@tauri-apps/cli-darwin-x64': 2.11.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 + '@tauri-apps/cli-linux-arm64-musl': 2.11.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-musl': 2.11.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 + '@tauri-apps/cli-win32-x64-msvc': 2.11.4 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 diff --git a/app/scripts/db-generate.mjs b/app/scripts/db-generate.mjs new file mode 100644 index 00000000..5ae73cca --- /dev/null +++ b/app/scripts/db-generate.mjs @@ -0,0 +1,22 @@ +#!/usr/bin/env node +// `drizzle-kit generate`, but with a REQUIRED migration name so migrations get +// meaningful filenames (e.g. `0001_add_block_health.sql`) instead of drizzle's +// random auto-names. Usage: vp run db:generate +import { execFileSync } from "node:child_process"; + +// tolerate a stray `--` separator; take the first real (non-flag) argument +const name = process.argv.slice(2).find((a) => a !== "--" && !a.startsWith("-")); +if (!name) { + console.error( + [ + "migration name required — usage: vp run db:generate ", + "", + "Use a short, descriptive snake_case name for the schema change it makes,", + "like a commit summary — what the migration does, not when you wrote it:", + " ✓ add_block_health ✓ rename_turd_columns ✓ drop_legacy_todos", + " ✗ update ✗ fix ✗ migration2", + ].join("\n"), + ); + process.exit(1); +} +execFileSync("drizzle-kit", ["generate", "--name", name], { stdio: "inherit" }); diff --git a/app/src-tauri/.gitignore b/app/src-tauri/.gitignore new file mode 100644 index 00000000..904e13c6 --- /dev/null +++ b/app/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +/gen/schemas + +# vendored sidecar binaries (regenerated per-platform at build time) +/binaries/ diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock new file mode 100644 index 00000000..9ce70ccb --- /dev/null +++ b/app/src-tauri/Cargo.lock @@ -0,0 +1,5859 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[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_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[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 = "app" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-log", + "tauri-plugin-opener", + "tauri-plugin-shell", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "tauri-plugin-window-state", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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 = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" +dependencies = [ + "once_cell", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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 = "byte-unit" +version = "5.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a813de7f2bbedb7dce265b64f1cf5908ebe4d56281ece8d847e98113788b9b0" +dependencies = [ + "rust_decimal", + "schemars 1.2.1", + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +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 = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[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 = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +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_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" +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 = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[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 = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[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 = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[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 = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[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 = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[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-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[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" +dependencies = [ + "ahash", +] + +[[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 = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[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 = "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-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[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 = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[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" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.118", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[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" +dependencies = [ + "value-bag", +] + +[[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 = "minisign-verify" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" + +[[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 = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[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-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-app-kit", + "objc2-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 = "open" +version = "5.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[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" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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 = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +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 = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[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 = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[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 = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +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 = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[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 = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[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-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "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 = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[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 = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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 = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[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", + "quote", + "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 0.21.1", + "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 = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2616f96cb644bf2c5c456d9de4d5d5100e592d7424c74d8b55c5cb96e359e93" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni 0.21.1", + "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-plugin-log" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" +dependencies = [ + "android_logger", + "byte-unit", + "fern", + "log", + "objc2", + "objc2-foundation", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8f29386f5e9fdc699182388a33ee80a56de436d91b67459e86afef426282af" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.18", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.18", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + +[[package]] +name = "tauri-plugin-window-state" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704" +dependencies = [ + "bitflags 2.13.0", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + +[[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 0.21.1", + "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe41e015bf8fc4d6477ff4926a0ef769dc64ff34c7b0038b6f7cacae892acb5c" +dependencies = [ + "gtk", + "http", + "jni 0.21.1", + "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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[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.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +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-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[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-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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 = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[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 = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[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-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" + +[[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 = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[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 = "webpki-root-certs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +dependencies = [ + "rustls-pki-types", +] + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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 0.21.1", + "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 = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[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 = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[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 = "zbus" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.3", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow 1.0.3", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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 = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[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 = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.14.0", + "memchr", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.3", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.118", + "winnow 1.0.3", +] diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml new file mode 100644 index 00000000..5b35a1a7 --- /dev/null +++ b/app/src-tauri/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.6.3", features = [] } + +[dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +log = "0.4" +tauri = { version = "2.11.3", features = [] } +tauri-plugin-dialog = "2" +tauri-plugin-log = "2" +tauri-plugin-opener = "2" +tauri-plugin-shell = "2" +tauri-plugin-single-instance = "2" +tauri-plugin-updater = "2" +tauri-plugin-window-state = "2" diff --git a/app/src-tauri/build.rs b/app/src-tauri/build.rs new file mode 100644 index 00000000..795b9b7c --- /dev/null +++ b/app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/app/src-tauri/capabilities/default.json b/app/src-tauri/capabilities/default.json new file mode 100644 index 00000000..485c0757 --- /dev/null +++ b/app/src-tauri/capabilities/default.json @@ -0,0 +1,13 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the default permissions", + "windows": ["main"], + "permissions": [ + "core:default", + { + "identifier": "shell:allow-execute", + "allow": [{ "name": "binaries/node", "sidecar": true, "args": true }] + } + ] +} diff --git a/app/src-tauri/icons/128x128.png b/app/src-tauri/icons/128x128.png new file mode 100644 index 00000000..bd6b6918 Binary files /dev/null and b/app/src-tauri/icons/128x128.png differ diff --git a/app/src-tauri/icons/128x128@2x.png b/app/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..2ffa4f31 Binary files /dev/null and b/app/src-tauri/icons/128x128@2x.png differ diff --git a/app/src-tauri/icons/32x32.png b/app/src-tauri/icons/32x32.png new file mode 100644 index 00000000..a522009a Binary files /dev/null and b/app/src-tauri/icons/32x32.png differ diff --git a/app/src-tauri/icons/64x64.png b/app/src-tauri/icons/64x64.png new file mode 100644 index 00000000..dc214f71 Binary files /dev/null and b/app/src-tauri/icons/64x64.png differ diff --git a/app/src-tauri/icons/Square107x107Logo.png b/app/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 00000000..0747e0d6 Binary files /dev/null and b/app/src-tauri/icons/Square107x107Logo.png differ diff --git a/app/src-tauri/icons/Square142x142Logo.png b/app/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 00000000..b5a25217 Binary files /dev/null and b/app/src-tauri/icons/Square142x142Logo.png differ diff --git a/app/src-tauri/icons/Square150x150Logo.png b/app/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 00000000..74ffca42 Binary files /dev/null and b/app/src-tauri/icons/Square150x150Logo.png differ diff --git a/app/src-tauri/icons/Square284x284Logo.png b/app/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 00000000..05238a3d Binary files /dev/null and b/app/src-tauri/icons/Square284x284Logo.png differ diff --git a/app/src-tauri/icons/Square30x30Logo.png b/app/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 00000000..f3d3e467 Binary files /dev/null and b/app/src-tauri/icons/Square30x30Logo.png differ diff --git a/app/src-tauri/icons/Square310x310Logo.png b/app/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 00000000..06717097 Binary files /dev/null and b/app/src-tauri/icons/Square310x310Logo.png differ diff --git a/app/src-tauri/icons/Square44x44Logo.png b/app/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 00000000..7eefce94 Binary files /dev/null and b/app/src-tauri/icons/Square44x44Logo.png differ diff --git a/app/src-tauri/icons/Square71x71Logo.png b/app/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 00000000..8e686e4a Binary files /dev/null and b/app/src-tauri/icons/Square71x71Logo.png differ diff --git a/app/src-tauri/icons/Square89x89Logo.png b/app/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 00000000..0297b07a Binary files /dev/null and b/app/src-tauri/icons/Square89x89Logo.png differ diff --git a/app/src-tauri/icons/StoreLogo.png b/app/src-tauri/icons/StoreLogo.png new file mode 100644 index 00000000..3b164902 Binary files /dev/null and b/app/src-tauri/icons/StoreLogo.png differ diff --git a/app/src-tauri/icons/icon.icns b/app/src-tauri/icons/icon.icns new file mode 100644 index 00000000..6e2b68f8 Binary files /dev/null and b/app/src-tauri/icons/icon.icns differ diff --git a/app/src-tauri/icons/icon.ico b/app/src-tauri/icons/icon.ico new file mode 100644 index 00000000..23276f34 Binary files /dev/null and b/app/src-tauri/icons/icon.ico differ diff --git a/app/src-tauri/icons/icon.png b/app/src-tauri/icons/icon.png new file mode 100644 index 00000000..58c73f91 Binary files /dev/null and b/app/src-tauri/icons/icon.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@1x.png b/app/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000..9b466bd4 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000..91d27473 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@2x.png b/app/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000..91d27473 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@3x.png b/app/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000..cbf6903c Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@1x.png b/app/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000..1aac6efc Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000..bb2599b8 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@2x.png b/app/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000..bb2599b8 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@3x.png b/app/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000..d1a33188 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@1x.png b/app/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000..91d27473 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000..d5d0bafa Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@2x.png b/app/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000..d5d0bafa Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@3x.png b/app/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000..a69b985b Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-512@2x.png b/app/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 00000000..55804b5f Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-60x60@2x.png b/app/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000..a69b985b Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-60x60@3x.png b/app/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000..735b797e Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-76x76@1x.png b/app/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000..5ff44aa6 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-76x76@2x.png b/app/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000..1f11e812 Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000..93cfc7fa Binary files /dev/null and b/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs new file mode 100644 index 00000000..8dbf2563 --- /dev/null +++ b/app/src-tauri/src/lib.rs @@ -0,0 +1,229 @@ +// PyOps desktop shell. The whole app (UI + backend) is the Nitro server; this shell +// runs that server and shows it in a window. +// - dev: `beforeDevCommand` starts the server (see tauri.conf.json); we just wait +// for it and open the window. +// - bundle: we start the server ourselves via the vendored `node` sidecar against +// the bundled `.output`, with the data/migrations/mod dirs passed in. +use std::net::TcpStream; +use std::time::{Duration, Instant}; + +use tauri::webview::PageLoadEvent; +use tauri::{Manager, RunEvent, WebviewUrl, WebviewWindowBuilder}; +use tauri_plugin_opener::OpenerExt; +use tauri_plugin_window_state::{StateFlags, WindowExt}; + +// External `` clicks (incl. target=_blank, which on_navigation alone misses) +// become a same-frame navigation, which the on_navigation hook cancels and hands to +// the system browser — so links like the GitHub button open in the user's browser +// instead of hijacking the app window. +const EXTERNAL_LINKS_SCRIPT: &str = r#" +window.addEventListener('click', function (e) { + var a = e.target && e.target.closest && e.target.closest('a[href]'); + if (a && /^https?:\/\//i.test(a.href) && a.origin !== window.location.origin) { + e.preventDefault(); + window.location.href = a.href; + } +}, true); +"#; + +#[cfg(not(debug_assertions))] +use std::sync::Mutex; +#[cfg(not(debug_assertions))] +use tauri::path::BaseDirectory; +#[cfg(not(debug_assertions))] +use tauri_plugin_shell::{process::CommandChild, ShellExt}; + +const PORT: u16 = 34115; + +/// Holds the sidecar server process so it can be killed when the app exits. +#[cfg(not(debug_assertions))] +struct ServerChild(Mutex>); + +/// Block until something accepts connections on the port, or time out. +fn wait_for_port(port: u16, timeout: Duration) -> bool { + let start = Instant::now(); + while start.elapsed() < timeout { + if TcpStream::connect(("127.0.0.1", port)).is_ok() { + return true; + } + std::thread::sleep(Duration::from_millis(150)); + } + false +} + +/// Open the main window (hidden) pointed at the local server, revealing it only once +/// the first page has painted — so the user never sees a blank webview while the +/// server boots / server-renders. +fn open_main_window(app: &tauri::AppHandle) { + let url = format!("http://localhost:{PORT}"); + // First-run size, wide enough for the desktop nav even with fractional display + // scaling (the inline bar collapses to a hamburger below 1400 CSS px, and a 1.25x + // scale makes the CSS viewport ~physical/1.25; the Deck's ~1280 intentionally + // stays collapsed). After the first run, the window-state plugin restores whatever + // size/position the user left it at. Title carries the version (tauri.conf.json). + let title = format!("PyOps v{}", app.package_info().version); + let nav_handle = app.clone(); + let win = WebviewWindowBuilder::new(app, "main", WebviewUrl::External(url.parse().unwrap())) + .title(title) + .inner_size(1800.0, 1100.0) + .min_inner_size(900.0, 600.0) + .visible(false) + .initialization_script(EXTERNAL_LINKS_SCRIPT) + .on_navigation(move |url| { + // Stay on the local server; send any other web link to the system browser. + let is_local = matches!(url.host_str(), Some("localhost") | Some("127.0.0.1")); + if matches!(url.scheme(), "http" | "https") && !is_local { + let _ = nav_handle.opener().open_url(url.as_str(), None::<&str>); + return false; + } + true + }) + .on_page_load(|window, payload| { + if matches!(payload.event(), PageLoadEvent::Finished) { + let _ = window.show(); + let _ = window.set_focus(); + } + }) + .build(); + // Restore the user's last size/position (a no-op on first run). The window-state + // plugin saves it again on exit. + if let Ok(w) = win { + let _ = w.restore_state(StateFlags::all()); + } +} + +/// Check GitHub for a newer release and, if one exists, offer to install it via a +/// native dialog showing the version + changelog (from the release's `latest.json` +/// notes). Bundled builds only — the updater can't replace a `cargo tauri dev` +/// binary — and entirely Rust-side, so the web UI stays Tauri-agnostic. +#[cfg(not(debug_assertions))] +async fn check_for_update(app: tauri::AppHandle) { + use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; + use tauri_plugin_updater::UpdaterExt; + + let updater = match app.updater() { + Ok(u) => u, + Err(_) => return, + }; + let update = match updater.check().await { + Ok(Some(u)) => u, + // already current, or the check failed (offline, etc.) — stay quiet + _ => return, + }; + + let notes = update.body.clone().unwrap_or_default(); + let message = if notes.trim().is_empty() { + format!("PyOps {} is available.", update.version) + } else { + format!("PyOps {} is available.\n\n{}", update.version, notes) + }; + + let install = app + .dialog() + .message(message) + .title("Update available") + .buttons(MessageDialogButtons::OkCancelCustom( + "Install & Restart".to_string(), + "Later".to_string(), + )) + .blocking_show(); + + if install && update.download_and_install(|_, _| {}, || {}).await.is_ok() { + app.restart(); + } +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + // webkit2gtk on Wayland raises "Error 71 (Protocol error)" and its DMABUF renderer + // glitches on some GPU/compositor combos, so force XWayland + the non-DMABUF path. + // Must be set before GTK initializes; only fill in what the user hasn't overridden. + #[cfg(target_os = "linux")] + { + if std::env::var_os("GDK_BACKEND").is_none() { + std::env::set_var("GDK_BACKEND", "x11"); + } + if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() { + std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + } + } + + let app = tauri::Builder::default() + // Must be the first plugin: a second launch focuses the existing window and + // exits before setup() spawns another server, so we never collide on the port. + // (Multiple instances / multiple open projects is tracked in #41.) + .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { + if let Some(w) = app.get_webview_window("main") { + let _ = w.unminimize(); + let _ = w.show(); + let _ = w.set_focus(); + } + })) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_window_state::Builder::default().build()) + .setup(|app| { + if cfg!(debug_assertions) { + app.handle().plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + )?; + } + + // Bundled build: start the server via the vendored node sidecar. The data + // dir is the per-OS app-data dir; migrations + mod source are bundled + // resources. (In dev the server is already up from beforeDevCommand.) + #[cfg(not(debug_assertions))] + { + let server_entry = + app.path().resolve("output/server/index.mjs", BaseDirectory::Resource)?; + let drizzle = app.path().resolve("drizzle", BaseDirectory::Resource)?; + let mod_dir = app.path().resolve("mod", BaseDirectory::Resource)?; + let data_dir = app.path().app_data_dir()?; + std::fs::create_dir_all(&data_dir).ok(); + + let (mut rx, child) = app + .shell() + .sidecar("node")? + .arg(server_entry.to_string_lossy().to_string()) + .env("PORT", PORT.to_string()) + .env("HOST", "127.0.0.1") + .env("PYOPS_DATA_DIR", data_dir.to_string_lossy().to_string()) + .env("PYOPS_MIGRATIONS_DIR", drizzle.to_string_lossy().to_string()) + .env("PYOPS_MOD_DIR", mod_dir.to_string_lossy().to_string()) + .spawn()?; + app.manage(ServerChild(Mutex::new(Some(child)))); + // keep the pipe drained so the child never blocks on a full stdout + tauri::async_runtime::spawn(async move { while rx.recv().await.is_some() {} }); + + // check GitHub for a newer release in the background; prompt if found + tauri::async_runtime::spawn(check_for_update(app.handle().clone())); + } + + // Wait for the server off the main thread, then open the window on it. + let handle = app.handle().clone(); + std::thread::spawn(move || { + wait_for_port(PORT, Duration::from_secs(90)); + let h = handle.clone(); + let _ = handle.run_on_main_thread(move || open_main_window(&h)); + }); + + Ok(()) + }) + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + app.run(|_handle, event| { + if let RunEvent::Exit = event { + #[cfg(not(debug_assertions))] + if let Some(state) = _handle.try_state::() { + if let Some(child) = state.0.lock().unwrap().take() { + let _ = child.kill(); + } + } + } + }); +} diff --git a/app/src-tauri/src/main.rs b/app/src-tauri/src/main.rs new file mode 100644 index 00000000..ad5fe839 --- /dev/null +++ b/app/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + app_lib::run(); +} diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json new file mode 100644 index 00000000..67a5a435 --- /dev/null +++ b/app/src-tauri/tauri.conf.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "PyOps", + "version": "0.1.0", + "identifier": "com.apocdev.pyops", + "build": { + "beforeDevCommand": "PORT=34115 HOST=127.0.0.1 node .output/server/index.mjs", + "beforeBuildCommand": "vp build", + "frontendDist": "../.output/public", + "devUrl": "http://localhost:34115" + }, + "app": { + "windows": [], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "createUpdaterArtifacts": true, + "targets": ["deb"], + "externalBin": ["binaries/node"], + "resources": { + "../.output": "output", + "../drizzle": "drizzle", + "../../mod": "mod" + }, + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "android": { + "debugApplicationIdSuffix": ".debug" + } + }, + "plugins": { + "updater": { + "endpoints": ["https://github.com/ApocDev/pyops/releases/latest/download/latest.json"], + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENBOTFGMkJFNTE1REQ2NUYKUldSZjFsMVJ2dktSeXNLb1FTbVBzNEprb0twUVJPWnh3WmF1cVp4Z2RBcVczNFhNK1dCdjVYeFUK" + } + } +} diff --git a/app/src-tauri/vendor-node.sh b/app/src-tauri/vendor-node.sh new file mode 100755 index 00000000..d1165118 --- /dev/null +++ b/app/src-tauri/vendor-node.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Vendor the Node runtime as the Tauri sidecar (binaries/node-), so a +# packaged build needs no system Node. The binary is gitignored (per-platform, +# ~80-120MB) — run this once before `tauri build` on a fresh checkout or new platform, +# and again after a Node major bump (keep it matching the ABI the native modules in +# .output/server/node_modules were built for). +# +# NODE_VERSION=24.18.0 ./vendor-node.sh +set -euo pipefail + +NODE_VERSION="${NODE_VERSION:-24.18.0}" +TRIPLE="$(rustc -Vv | sed -n 's/host: //p')" +DIR="$(cd "$(dirname "$0")" && pwd)" + +case "$TRIPLE" in + x86_64-unknown-linux-gnu) NODE_ARCH=linux-x64 ;; + aarch64-unknown-linux-gnu) NODE_ARCH=linux-arm64 ;; + x86_64-apple-darwin) NODE_ARCH=darwin-x64 ;; + aarch64-apple-darwin) NODE_ARCH=darwin-arm64 ;; + x86_64-pc-windows-msvc) NODE_ARCH=win-x64 ;; + *) echo "unsupported target triple: $TRIPLE" >&2; exit 1 ;; +esac + +mkdir -p "$DIR/binaries" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +if [[ "$NODE_ARCH" == win-* ]]; then + curl -fsSL "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-$NODE_ARCH.zip" -o "$TMP/node.zip" + # extract with whatever's available (Git Bash often lacks unzip; 7z/bsdtar do zip) + if command -v unzip >/dev/null 2>&1; then unzip -q "$TMP/node.zip" -d "$TMP" + elif command -v 7z >/dev/null 2>&1; then 7z x -o"$TMP" "$TMP/node.zip" >/dev/null + else tar -xf "$TMP/node.zip" -C "$TMP"; fi + cp "$TMP/node-v$NODE_VERSION-$NODE_ARCH/node.exe" "$DIR/binaries/node-$TRIPLE.exe" +else + curl -fsSL "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-$NODE_ARCH.tar.xz" -o "$TMP/node.tar.xz" + tar -xJf "$TMP/node.tar.xz" -C "$TMP" "node-v$NODE_VERSION-$NODE_ARCH/bin/node" + cp "$TMP/node-v$NODE_VERSION-$NODE_ARCH/bin/node" "$DIR/binaries/node-$TRIPLE" + chmod +x "$DIR/binaries/node-$TRIPLE" +fi + +echo "vendored node v$NODE_VERSION -> binaries/node-$TRIPLE" diff --git a/app/src/db/conversations.ts b/app/src/db/conversations.ts index 457c4564..7e305367 100644 --- a/app/src/db/conversations.ts +++ b/app/src/db/conversations.ts @@ -1,9 +1,7 @@ /** - * Persisted assistant conversations, per-project. Pure queries over the - * active project's db. Because these tables are newer than most existing project - * dbs, `ensureSchema()` creates them idempotently on first use so the feature - * works without a manual `db:push` per project (new projects get them from the - * schema via drizzle-kit push). schema.ts stays the canonical definition. + * Persisted assistant conversations, per-project. Pure queries over the active + * project's db. `ensureSchema()` creates these tables idempotently on first use, so + * every project db has them. schema.ts stays the canonical definition. */ import { desc, eq, sql } from "drizzle-orm"; diff --git a/app/src/db/import-factorio.ts b/app/src/db/import-factorio.ts index cfb9db6c..b632a1aa 100644 --- a/app/src/db/import-factorio.ts +++ b/app/src/db/import-factorio.ts @@ -3,10 +3,8 @@ * into the SQLite db. REAL prototypes only — synthetic recipes (mining/boiling/burning/ * spoiling) and fluid temperature variants come in pass 2. * - * Library: importFactorioDump({ dumpPath, dbUrl }) — called in-process by the - * server-side data sync (src/server/dump.ts). - * CLI: vp run db:import (uses ~/.factorio/script-output/data-raw-dump.json) - * vp run db:import -- + * `importFactorioDump({ dumpPath, dbUrl })` is called in-process by the server-side + * data sync (src/server/dump.ts). * * Idempotent: clears the reference tables and re-inserts. Uses raw better-sqlite3 * prepared statements in one transaction (fast for ~10k recipes / ~60k rows). @@ -15,11 +13,8 @@ import Database from "better-sqlite3"; import { readdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; -import { pathToFileURL } from "node:url"; -import { config } from "dotenv"; import { synthesizePass2 } from "./synthesize.ts"; - -config({ path: [".env.local", ".env"] }); +import { PROJECTS_DIR } from "../server/paths.ts"; const DEFAULT_DUMP = join(homedir(), ".factorio", "script-output", "data-raw-dump.json"); @@ -147,7 +142,7 @@ export function importFactorioDump( opts: { dumpPath?: string; dbUrl?: string } = {}, ): ImportSummary { const DUMP = opts.dumpPath ?? DEFAULT_DUMP; - const DB_URL = opts.dbUrl ?? process.env.DATABASE_URL ?? "projects/default.db"; + const DB_URL = opts.dbUrl ?? process.env.DATABASE_URL ?? join(PROJECTS_DIR, "default.db"); const raw = JSON.parse(readFileSync(DUMP, "utf8")) as Record>; @@ -511,10 +506,3 @@ export function importFactorioDump( db.close(); return { dump: DUMP, dbUrl: DB_URL, ms: Date.now() - t0, counts: { ...counts, ...synthetic } }; } - -/* CLI entry — `vp run db:import [-- ]` */ -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - const summary = importFactorioDump({ dumpPath: process.argv[2] }); - console.log(`Imported ${summary.dump} -> ${summary.dbUrl} in ${summary.ms}ms`); - for (const [t, n] of Object.entries(summary.counts)) console.log(` ${t.padEnd(22)} ${n}`); -} diff --git a/app/src/db/index.ts b/app/src/db/index.ts index 22df59c3..ed812fe3 100644 --- a/app/src/db/index.ts +++ b/app/src/db/index.ts @@ -1,7 +1,7 @@ import { drizzle } from "drizzle-orm/better-sqlite3"; -import { sql } from "drizzle-orm"; import * as schema from "./schema.ts"; +import { migrateConnection } from "../server/provision.ts"; import { activeProjectFile } from "./projects-fs.ts"; /** @@ -25,30 +25,14 @@ function connection(file: string): Drizzle { let c = connections.get(file); if (!c) { c = drizzle(file, { schema }); - ensureCoreUpgrades(c); + // Provision/upgrade the db to the current schema on first connect by applying + // the bundled drizzle migrations. Idempotent and runs once per file (cached). + migrateConnection(c); connections.set(file, c); } return c; } -/** - * Columns added to core tables after some project dbs were already provisioned. - * `drizzle-kit push` gives brand-new dbs the full schema, but existing dbs have no - * migrate step — so without this, selecting a newer column throws "no such column". - * Apply each ALTER idempotently on first connect (the ALTER errors harmlessly when - * the column already exists). Mirrors the per-feature `ensureSchema()` guards used - * by conversations/tasks. Runs once per db file (connections are cached). - */ -function ensureCoreUpgrades(c: Drizzle) { - for (const stmt of [sql`ALTER TABLE blocks ADD COLUMN solve_status text`]) { - try { - c.run(stmt); - } catch { - /* column already present (or table not created yet — push will include it) */ - } - } -} - /** Point `db` at another project's database file. */ export function switchDatabase(file: string) { activeFile = file; diff --git a/app/src/db/projects-fs.ts b/app/src/db/projects-fs.ts index 5e8b2d48..18de8a82 100644 --- a/app/src/db/projects-fs.ts +++ b/app/src/db/projects-fs.ts @@ -2,16 +2,16 @@ * File-derived project model. Each project is a sqlite file under `projects/` * (`projects/.db`); the file *is* the project, and it self-describes its name * and creation time in its own `meta` table. The only app-level bit — which - * project is active — lives in `app-config.json`. There is no `projects.json` - * registry anymore (a one-time migration retires it). + * project is active — lives in `app-config.json`. */ import Database from "better-sqlite3"; -import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync } from "node:fs"; +import { existsSync, mkdirSync, readdirSync, renameSync, statSync } from "node:fs"; import { join } from "node:path"; -import { readAppConfig, writeAppConfig } from "../server/app-config.ts"; +import { readAppConfig } from "../server/app-config.ts"; +import { PROJECTS_DIR } from "../server/paths.ts"; -export const PROJECTS_DIR = "projects"; +export { PROJECTS_DIR }; export const DEFAULT_ID = "default"; const REMOVED_DIR = join(PROJECTS_DIR, "_removed"); @@ -49,51 +49,8 @@ export function writeProjectMeta(file: string, name?: string, createdAt?: string } } -let migrated = false; -/** One-time migration to the file-derived model. Best-effort and guarded so it can - * be called from anywhere that's about to touch projects: - * 1. move legacy `dev.db` -> `projects/default.db` (atomic rename, data preserved); - * 2. import any `projects.json` names into each db's meta + `active` into app-config; - * 3. back `projects.json` up to `.bak` (kept, not deleted); - * 4. ensure the default project has a name. */ -export function migrateProjectsOnce() { - if (migrated) return; - migrated = true; - try { - const defaultFile = fileForProject(DEFAULT_ID); - if (!existsSync(defaultFile) && existsSync("dev.db")) { - mkdirSync(PROJECTS_DIR, { recursive: true }); - for (const suf of ["", "-wal", "-shm"]) { - if (existsSync("dev.db" + suf)) renameSync("dev.db" + suf, defaultFile + suf); - } - } - if (existsSync("projects.json")) { - try { - const reg = JSON.parse(readFileSync("projects.json", "utf8")) as { - active?: string; - projects?: { id: string; name?: string; dbFile?: string; createdAt?: string }[]; - }; - for (const p of reg.projects ?? []) { - const file = p.id === DEFAULT_ID ? defaultFile : (p.dbFile ?? fileForProject(p.id)); - if (existsSync(file)) writeProjectMeta(file, p.name, p.createdAt); - } - if (reg.active && !readAppConfig().active) writeAppConfig({ active: reg.active }); - } catch { - /* ignore a malformed registry */ - } - renameSync("projects.json", "projects.json.bak"); - } - if (existsSync(defaultFile) && !readProjectMeta(defaultFile).name) { - writeProjectMeta(defaultFile, "Default", new Date(0).toISOString()); - } - } catch { - /* never block startup on migration */ - } -} - /** The active project's db file: app-config.active -> its file, else the default. */ export function activeProjectFile(): string { - migrateProjectsOnce(); const id = readAppConfig().active ?? DEFAULT_ID; const file = fileForProject(id); return existsSync(file) ? file : fileForProject(DEFAULT_ID); @@ -104,7 +61,6 @@ export type ProjectInfo = { id: string; name: string; file: string; createdAt: s /** List projects by scanning `projects/` for `*.db` files; each one self-describes * its name. Default first, then by name. */ export function listProjectFiles(): ProjectInfo[] { - migrateProjectsOnce(); const out: ProjectInfo[] = []; try { mkdirSync(PROJECTS_DIR, { recursive: true }); diff --git a/app/src/db/tasks.ts b/app/src/db/tasks.ts index 877a493d..7edfc6fe 100644 --- a/app/src/db/tasks.ts +++ b/app/src/db/tasks.ts @@ -6,9 +6,8 @@ * "milestone" is just a parent task; a child task is a full task shown indented * on its parent. Notes are a separate flat scratch surface. * - * These tables are newer than most existing project dbs, so `ensureSchema()` - * creates/upgrades them idempotently on first use — no manual `db:push` per - * project. schema.ts stays the canonical definition. + * `ensureSchema()` creates/upgrades these tables idempotently on first use, so every + * project db has them. schema.ts stays the canonical definition. */ import { asc, eq, sql } from "drizzle-orm"; diff --git a/app/src/db/test-helpers.ts b/app/src/db/test-helpers.ts index 1fa2a6c5..b8a9e077 100644 --- a/app/src/db/test-helpers.ts +++ b/app/src/db/test-helpers.ts @@ -2,8 +2,8 @@ * Test-only helpers: spin up a throwaway SQLite database with the REAL schema. * * The schema DDL is derived straight from `schema.ts` via drizzle-kit's - * programmatic api (the same engine `db:push` uses), so fixtures never drift - * from the production schema — change a column and the next test run builds it. + * programmatic api, so fixtures never drift from the production schema — change a + * column and the next test run builds it. * Not imported by app code; only `*.test.ts` files use it. */ import { mkdtempSync, rmSync } from "node:fs"; diff --git a/app/src/lib/icons.tsx b/app/src/lib/icons.tsx index 4a595901..03876bd8 100644 --- a/app/src/lib/icons.tsx +++ b/app/src/lib/icons.tsx @@ -21,7 +21,7 @@ export function fmtSpoilTime(ticks: number): string { /** * Renders Factorio icons from the sprite atlas (manifest comes from a typed - * server fn; atlas sheets are served from /icons by the dev middleware). Each + * server fn; atlas sheets are served from /icons). Each * icon is a background-position slice. Manifest keys are `${prototypeType}/${name}`. * * The manifest loads CLIENT-ONLY (useEffect) so it isn't fetched during SSR. diff --git a/app/src/routeTree.gen.ts b/app/src/routeTree.gen.ts index a826b80c..a729832f 100644 --- a/app/src/routeTree.gen.ts +++ b/app/src/routeTree.gen.ts @@ -21,6 +21,7 @@ import { Route as BlockRouteImport } from './routes/block' import { Route as AssistantRouteImport } from './routes/assistant' import { Route as IndexRouteImport } from './routes/index' import { Route as BlockIndexRouteImport } from './routes/block.index' +import { Route as IconsSplatRouteImport } from './routes/icons.$' import { Route as BlockIdRouteImport } from './routes/block.$id' import { Route as ApiChatRouteImport } from './routes/api.chat' @@ -84,6 +85,11 @@ const BlockIndexRoute = BlockIndexRouteImport.update({ path: '/', getParentRoute: () => BlockRoute, } as any) +const IconsSplatRoute = IconsSplatRouteImport.update({ + id: '/icons/$', + path: '/icons/$', + getParentRoute: () => rootRouteImport, +} as any) const BlockIdRoute = BlockIdRouteImport.update({ id: '/$id', path: '/$id', @@ -109,6 +115,7 @@ export interface FileRoutesByFullPath { '/whatif': typeof WhatifRoute '/api/chat': typeof ApiChatRoute '/block/$id': typeof BlockIdRoute + '/icons/$': typeof IconsSplatRoute '/block/': typeof BlockIndexRoute } export interface FileRoutesByTo { @@ -124,6 +131,7 @@ export interface FileRoutesByTo { '/whatif': typeof WhatifRoute '/api/chat': typeof ApiChatRoute '/block/$id': typeof BlockIdRoute + '/icons/$': typeof IconsSplatRoute '/block': typeof BlockIndexRoute } export interface FileRoutesById { @@ -141,6 +149,7 @@ export interface FileRoutesById { '/whatif': typeof WhatifRoute '/api/chat': typeof ApiChatRoute '/block/$id': typeof BlockIdRoute + '/icons/$': typeof IconsSplatRoute '/block/': typeof BlockIndexRoute } export interface FileRouteTypes { @@ -159,6 +168,7 @@ export interface FileRouteTypes { | '/whatif' | '/api/chat' | '/block/$id' + | '/icons/$' | '/block/' fileRoutesByTo: FileRoutesByTo to: @@ -174,6 +184,7 @@ export interface FileRouteTypes { | '/whatif' | '/api/chat' | '/block/$id' + | '/icons/$' | '/block' id: | '__root__' @@ -190,6 +201,7 @@ export interface FileRouteTypes { | '/whatif' | '/api/chat' | '/block/$id' + | '/icons/$' | '/block/' fileRoutesById: FileRoutesById } @@ -206,6 +218,7 @@ export interface RootRouteChildren { TurdRoute: typeof TurdRoute WhatifRoute: typeof WhatifRoute ApiChatRoute: typeof ApiChatRoute + IconsSplatRoute: typeof IconsSplatRoute } declare module '@tanstack/react-router' { @@ -294,6 +307,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlockIndexRouteImport parentRoute: typeof BlockRoute } + '/icons/$': { + id: '/icons/$' + path: '/icons/$' + fullPath: '/icons/$' + preLoaderRoute: typeof IconsSplatRouteImport + parentRoute: typeof rootRouteImport + } '/block/$id': { id: '/block/$id' path: '/$id' @@ -336,6 +356,7 @@ const rootRouteChildren: RootRouteChildren = { TurdRoute: TurdRoute, WhatifRoute: WhatifRoute, ApiChatRoute: ApiChatRoute, + IconsSplatRoute: IconsSplatRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/app/src/routes/icons.$.ts b/app/src/routes/icons.$.ts new file mode 100644 index 00000000..cfb32e03 --- /dev/null +++ b/app/src/routes/icons.$.ts @@ -0,0 +1,51 @@ +/** + * Serves the generated icon atlas (sheets + manifest.json) at `/icons/*` from the + * data dir, where it lives outside `public/` because it's user-generated (built from + * a Factorio dump). In dev a Vite middleware serves the same path (see + * `serveIconsDev` in vite.config); this handler covers production. Node access is + * dynamically imported so the route file stays client-bundle-safe. + */ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/icons/$")({ + server: { + handlers: { + GET: async ({ request }) => { + const fs = await import("node:fs/promises"); + const path = await import("node:path"); + const { ICON_DATA_DIR } = await import("#/server/paths.ts"); + + const url = new URL(request.url); + const rel = decodeURIComponent(url.pathname.replace(/^\/icons\//, "")); + const file = path.join(ICON_DATA_DIR, path.normalize(rel)); + // never escape the data dir + const within = path.relative(ICON_DATA_DIR, file); + if (within.startsWith("..") || path.isAbsolute(within)) { + return new Response("Not found", { status: 404 }); + } + + try { + const data = await fs.readFile(file); + const ext = path.extname(file); + const type = + ext === ".json" + ? "application/json" + : ext === ".png" + ? "image/png" + : "application/octet-stream"; + // Sheets are requested as `atlas-N.png?v=`; the fingerprint + // changes whenever the atlas does (per project + per dump), so a versioned + // URL is safe to cache immutably. Unversioned requests revalidate. + const cacheControl = url.searchParams.has("v") + ? "public, max-age=31536000, immutable" + : "no-cache"; + return new Response(new Uint8Array(data), { + headers: { "Content-Type": type, "Cache-Control": cacheControl }, + }); + } catch { + return new Response("Not found", { status: 404 }); + } + }, + }, + }, +}); diff --git a/app/src/routes/settings.tsx b/app/src/routes/settings.tsx index 30a192ac..f2acfa17 100644 --- a/app/src/routes/settings.tsx +++ b/app/src/routes/settings.tsx @@ -1,9 +1,10 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; -import { Check, Star } from "lucide-react"; +import { Check, Copy, Star } from "lucide-react"; import { aiConfigFn, + dataPathsFn, dataStatusFn, exclusionsFn, modDriftFn, @@ -146,10 +147,74 @@ function GameDataTab() { + + ); } +/** Where the app stores its data on disk. Per-OS for a packaged build, the working + * dir in dev — shown here so it's findable when sharing a db or filing a bug. */ +function StorageCard() { + const paths = useQuery({ queryKey: ["dataPaths"], queryFn: () => dataPathsFn() }); + const [copied, setCopied] = useState(null); + const copy = (value: string) => { + void navigator.clipboard?.writeText(value); + setCopied(value); + setTimeout(() => setCopied((c) => (c === value ? null : c)), 1200); + }; + + const rows: { label: string; value: string }[] = paths.data + ? [ + { label: "Data folder", value: paths.data.dataDir }, + { label: "Projects (databases)", value: paths.data.projectsDir }, + { label: "Icon atlas", value: paths.data.iconDataDir }, + { label: "App config", value: paths.data.appConfig }, + ] + : []; + + return ( + + + Storage location + +
+

+ Project databases, the icon atlas, and config live here. Useful when sharing a database or + filing a bug report. +

+
+ {rows.map((r) => ( +
+
{r.label}
+
+ + {r.value} + + +
+
+ ))} +
+
+
+ ); +} + /** Mod drift: the game's CURRENT mod set vs the baseline this project's data was * dumped from (#28), by name and version. Spells out exactly what changed and * whether a re-dump is due, so the re-sync below isn't a black box. */ diff --git a/app/src/server/app-config.ts b/app/src/server/app-config.ts index 6c9bcdb0..23ab2891 100644 --- a/app/src/server/app-config.ts +++ b/app/src/server/app-config.ts @@ -8,16 +8,18 @@ * Env always wins (deployment override); the stored value is the friendly default * for someone who didn't set env. */ -import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; + +import { APP_CONFIG_FILE } from "./paths.ts"; /** Default OpenRouter model when neither env nor app-config sets one. */ export const DEFAULT_MODEL = "~anthropic/claude-sonnet-latest"; -const FILE = join(process.cwd(), "app-config.json"); +const FILE = APP_CONFIG_FILE; export type AppConfig = { - active?: string; // the selected project id (replaces projects.json's `active`) + active?: string; // the selected project id openrouterApiKey?: string; model?: string; }; @@ -37,6 +39,7 @@ export function writeAppConfig(patch: Partial): AppConfig { for (const k of Object.keys(next) as (keyof AppConfig)[]) { if (next[k] === "" || next[k] == null) delete next[k]; } + mkdirSync(dirname(FILE), { recursive: true }); // data dir may not exist yet writeFileSync(FILE, JSON.stringify(next, null, 2)); try { chmodSync(FILE, 0o600); diff --git a/app/src/server/companion-mod.ts b/app/src/server/companion-mod.ts index c8885ccf..dcc47277 100644 --- a/app/src/server/companion-mod.ts +++ b/app/src/server/companion-mod.ts @@ -16,12 +16,15 @@ import { cp, lstat, mkdir, readFile, realpath, rm, symlink } from "node:fs/promises"; import { existsSync } from "node:fs"; import { homedir, platform } from "node:os"; -import { join, resolve } from "node:path"; +import { join } from "node:path"; + +import { MOD_SOURCE_DIR } from "./paths.ts"; const FACTORIO_DATA = process.env.FACTORIO_DATA_DIR ?? join(homedir(), ".factorio"); const MODS_DIR = join(FACTORIO_DATA, "mods"); -// the app runs from app/; the mod source is the sibling mod/ directory -const SOURCE_DIR = resolve(process.cwd(), "../mod"); +// the bundled mod source (sibling mod/ dir in the source tree; overridable for a +// packaged build via PYOPS_MOD_DIR — see server/paths.ts) +const SOURCE_DIR = MOD_SOURCE_DIR; const TARGET = join(MODS_DIR, "pyops"); export type CompanionPlatform = "linux" | "mac" | "windows" | "other"; diff --git a/app/src/server/dump.ts b/app/src/server/dump.ts index 187fc9cd..eebf2836 100644 --- a/app/src/server/dump.ts +++ b/app/src/server/dump.ts @@ -9,29 +9,38 @@ * prototypes that integration leaves engine-invalid) * 2. factorio --dump-data / --dump-prototype-locale / --dump-icon-sprites * 3. restore mod-list.json (the helper must NEVER stay enabled for play) - * 4. import the dump into sqlite (tsx src/db/import-factorio.ts) - * 5. rebuild the icon atlas (scripts/build-icon-atlas.mjs) + * 4. import the dump into sqlite (importFactorioDump) + * 5. rebuild the icon atlas (buildIconAtlas) * 6. stamp the mod-list fingerprint into meta * * Long-running (~1-2 min): state is held in-module and polled by the UI. */ -import { spawn } from "node:child_process"; +import { execFile, spawn } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; -import { join, resolve } from "node:path"; +import { join } from "node:path"; +import { promisify } from "node:util"; import { eq, sql } from "drizzle-orm"; +import { currentDatabaseFile, db } from "../db/index.ts"; +import { importFactorioDump } from "../db/import-factorio.ts"; +import * as q from "../db/queries.ts"; +import { meta } from "../db/schema.ts"; +import { computeCostAnalysis } from "./cost-analysis.ts"; +import { buildIconAtlas } from "./icon-atlas.ts"; +import { applyRenames, readModMigrations } from "./migrations.ts"; +import { ICON_DATA_DIR } from "./paths.ts"; + const FACTORIO_BIN = process.env.FACTORIO_BIN ?? join(homedir(), ".local/share/Steam/steamapps/common/Factorio/bin/x64/factorio"); const FACTORIO_DATA = process.env.FACTORIO_DATA_DIR ?? join(homedir(), ".factorio"); const MODS_DIR = join(FACTORIO_DATA, "mods"); -const LOCK_FILE = join(FACTORIO_DATA, ".lock"); const SCRIPT_OUTPUT = join(FACTORIO_DATA, "script-output"); const APP_DIR = process.cwd(); -const ATLAS_SCRIPT = resolve(APP_DIR, "../scripts/build-icon-atlas.mjs"); -const ICON_DATA = join(APP_DIR, "icon-data"); + +const execFileAsync = promisify(execFile); /* ── helper mod ──────────────────────────────────────────────────────────────── */ @@ -228,28 +237,32 @@ async function readModList(): Promise { export const GAME_RUNNING_MSG = "Factorio is already running. Close the game first, then sync again — PyOps launches its own headless copy to read the data, and the engine won't allow two instances at once."; -/** Best-effort: is Factorio already running? The engine holds a BSD `flock` on - * `~/.factorio/.lock`, so we try a non-blocking flock on the same file via the - * `flock(1)` util. `true` = locked (running), `false` = we acquired it (not - * running), `null` = can't tell (no flock util / no lock file / non-Linux) — in - * which case callers fall back to attempting the dump and mapping the lock error. */ -export function factorioRunning(): Promise { - return new Promise((resolve) => { - let settled = false; - const done = (v: boolean | null) => { - if (!settled) { - settled = true; - resolve(v); - } - }; - try { - const child = spawn("flock", ["-n", LOCK_FILE, "-c", "true"], { stdio: "ignore" }); - child.on("error", () => done(null)); // flock util not present (e.g. macOS/Windows) - child.on("close", (code) => done(code === 0 ? false : code === 1 ? true : null)); - } catch { - done(null); +/** Best-effort: is a Factorio instance already running? Checked before a dump, + * which launches its own headless Factorio and can't share the data dir with a live + * game. Cross-platform via the OS process list — `tasklist` on Windows, `pgrep` + * elsewhere. `true` = running, `false` = not, `null` = couldn't tell, in which case + * callers fall back to attempting the dump and mapping the lock error. */ +export async function factorioRunning(): Promise { + try { + if (process.platform === "win32") { + const { stdout } = await execFileAsync("tasklist", [ + "/FI", + "IMAGENAME eq factorio.exe", + "/NH", + ]); + return /factorio\.exe/i.test(stdout); } - }); + // macOS + Linux: pgrep matches by process name; exit code 1 just means "no match" + const { stdout } = await execFileAsync("pgrep", ["-x", "factorio"]).catch( + (e: { code?: number }) => { + if (e?.code === 1) return { stdout: "", stderr: "" }; + throw e; + }, + ); + return stdout.trim().length > 0; + } catch { + return null; // pgrep/tasklist missing or unexpected — fall back to the lock error + } } /** Map a raw dump failure to a friendly message for the known "game is running" @@ -379,13 +392,10 @@ async function applyModMigrations(): Promise<{ blocksChanged: number; renames: number; }> { - const { readModMigrations, applyRenames } = await import("./migrations.ts"); const mods = await readMods(); const files = await readModMigrations(MODS_DIR, mods); const allKeys = files.map((f) => f.key); - const { db } = await import("../db/index.ts"); - const { meta } = await import("../db/schema.ts"); const writeApplied = (keys: string[]) => db .insert(meta) @@ -411,7 +421,6 @@ async function applyModMigrations(): Promise<{ let blocksChanged = 0; let renames = 0; if (newFiles.length) { - const q = await import("../db/queries.ts"); for (const b of q.listBlocks()) { const rowB = q.getBlock(b.id); if (!rowB) continue; @@ -573,20 +582,23 @@ export function startDataSync(opts: { icons?: boolean } = {}): SyncState { state.log.push("pyops-dump disabled again"); } step("import", "importing dump into sqlite"); - // in-process: the server already runs TS — no child process needed - const { importFactorioDump } = await import("../db/import-factorio.ts"); - const { currentDatabaseFile } = await import("../db/index.ts"); const summary = importFactorioDump({ dbUrl: currentDatabaseFile() }); state.log.push( `imported ${summary.counts.recipes} recipes / ${summary.counts.items} items in ${summary.ms}ms`, ); if (icons) { step("atlas", "rebuilding icon atlas"); - await run("node", [ATLAS_SCRIPT, SCRIPT_OUTPUT, ICON_DATA]); + const atlas = await buildIconAtlas({ + src: SCRIPT_OUTPUT, + out: ICON_DATA_DIR, + onLog: (m) => state.log.push(m), + }); + state.log.push( + `atlas: ${atlas.sheets.length} sheet(s), ${atlas.counts.unique} unique icons`, + ); } step("costs", "computing cost analysis (LP)"); - const { computeCostAnalysis } = await import("./cost-analysis.ts"); const costs = await computeCostAnalysis(currentDatabaseFile()); state.log.push(`priced ${costs.goods} goods / ${costs.recipes} recipes in ${costs.ms}ms`); @@ -602,8 +614,6 @@ export function startDataSync(opts: { icons?: boolean } = {}): SyncState { const fp = await modListFingerprint(); const mods = await readMods(); - const { db } = await import("../db/index.ts"); - const { meta } = await import("../db/schema.ts"); db.insert(meta) .values([ { key: "data_fingerprint", value: fp }, diff --git a/app/src/server/factorio.ts b/app/src/server/factorio.ts index 941d77c6..aaf84dd4 100644 --- a/app/src/server/factorio.ts +++ b/app/src/server/factorio.ts @@ -1874,7 +1874,8 @@ export const iconManifestFn = createServerFn({ method: "GET" }).handler( async (): Promise => { const fs = await import("node:fs/promises"); const path = await import("node:path"); - const raw = await fs.readFile(path.join(process.cwd(), "icon-data", "manifest.json"), "utf8"); + const { ICON_DATA_DIR } = await import("./paths.ts"); + const raw = await fs.readFile(path.join(ICON_DATA_DIR, "manifest.json"), "utf8"); // file content is untyped input — assert the shape at this boundary only const manifest = JSON.parse(raw) as IconManifest; // Cache-bust the atlas sheets: the PNGs are served at stable URLs (/icons/ @@ -1889,3 +1890,25 @@ export const iconManifestFn = createServerFn({ method: "GET" }).handler( return manifest; }, ); + +export type DataPaths = { + dataDir: string; + projectsDir: string; + iconDataDir: string; + appConfig: string; +}; + +// Where the app keeps its on-disk state. Surfaced in Settings so a user (or a bug +// report) can find the project dbs / atlas / config — handy since the location is +// per-OS for a packaged build but the working dir in dev. +export const dataPathsFn = createServerFn({ method: "GET" }).handler( + async (): Promise => { + const { DATA_DIR, PROJECTS_DIR, ICON_DATA_DIR, APP_CONFIG_FILE } = await import("./paths.ts"); + return { + dataDir: DATA_DIR, + projectsDir: PROJECTS_DIR, + iconDataDir: ICON_DATA_DIR, + appConfig: APP_CONFIG_FILE, + }; + }, +); diff --git a/app/src/server/icon-atlas.ts b/app/src/server/icon-atlas.ts new file mode 100644 index 00000000..838bb72e --- /dev/null +++ b/app/src/server/icon-atlas.ts @@ -0,0 +1,144 @@ +/** + * Icon-atlas builder: pack Factorio's dumped icon sprites (the output of + * `factorio --dump-icon-sprites`) into content-hash-deduped atlas sheets plus a + * `(type/name) -> slot` manifest. Called in-process by the data sync (`dump.ts`). + * + * Layout: a uniform grid of CELL x CELL cells in a 4096x4096 sheet. Icons are + * square, so placement is trivial and deterministic — no bin-packing. Identical + * sprites (by file-byte hash) share one slot, so many keys map to the same cell. + */ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { mkdir, readdir, stat, writeFile } from "node:fs/promises"; +import { extname, join, relative } from "node:path"; +import sharp from "sharp"; + +const SHEET = 4096; + +export type IconSlot = { s: number; x: number; y: number }; + +export type AtlasManifest = { + cell: number; + atlasSize: number; + sheets: string[]; + counts: { keys: number; unique: number; skipped: number }; + icons: Record; +}; + +export type AtlasResult = AtlasManifest & { ms: number }; + +type BuildOpts = { + /** Source dir of dumped sprites (recursively scanned for *.png). */ + src: string; + /** Output dir for the sheets + manifest.json (created if missing). */ + out: string; + /** Cell size in px (default 64). */ + cell?: number; + /** Optional progress sink (the sync forwards these into its log). */ + onLog?: (msg: string) => void; +}; + +async function collectPngs(dir: string): Promise { + const out: string[] = []; + for (const ent of await readdir(dir, { withFileTypes: true })) { + const p = join(dir, ent.name); + if (ent.isDirectory()) out.push(...(await collectPngs(p))); + else if (ent.isFile() && p.toLowerCase().endsWith(".png")) out.push(p); + } + return out; +} + +export async function buildIconAtlas({ + src, + out, + cell = 64, + onLog, +}: BuildOpts): Promise { + const t0 = Date.now(); + const ms = () => `${((Date.now() - t0) / 1000).toFixed(1)}s`; + const log = (m: string) => onLog?.(m); + const cols = Math.floor(SHEET / cell); + const perSheet = cols * cols; + + const files = await collectPngs(src); + log(`[${ms()}] found ${files.length} png files`); + + // key = "type/name" (relative path minus .png); dedup by raw file-byte hash + const keyToHash = new Map(); + const hashToFile = new Map(); + for (const f of files) { + const rel = relative(src, f).split("\\").join("/"); + const key = rel.slice(0, rel.length - extname(rel).length); + const hash = createHash("sha1").update(readFileSync(f)).digest("hex"); + keyToHash.set(key, hash); + if (!hashToFile.has(hash)) hashToFile.set(hash, f); + } + const uniqueHashes = [...hashToFile.keys()]; + const sheetCount = Math.ceil(uniqueHashes.length / perSheet); + + await mkdir(out, { recursive: true }); + + // build + encode one sheet at a time (cap peak memory at ~one 64MB RGBA buffer) + const sheetFiles: string[] = []; + let skipped = 0; + for (let s = 0; s < sheetCount; s++) { + const buf = Buffer.alloc(SHEET * SHEET * 4); // transparent RGBA + const start = s * perSheet; + const end = Math.min(start + perSheet, uniqueHashes.length); + for (let i = start; i < end; i++) { + const idx = i - start; + const cx = (idx % cols) * cell; + const cy = Math.floor(idx / cols) * cell; + try { + const raw = await sharp(hashToFile.get(uniqueHashes[i])) + .resize(cell, cell, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .ensureAlpha() + .raw() + .toBuffer(); + for (let row = 0; row < cell; row++) { + const srcStart = row * cell * 4; + raw.copy(buf, ((cy + row) * SHEET + cx) * 4, srcStart, srcStart + cell * 4); + } + } catch { + skipped++; + } + } + const name = `atlas-${s}.png`; + await sharp(buf, { raw: { width: SHEET, height: SHEET, channels: 4 } }) + .png({ compressionLevel: 9 }) + .toFile(join(out, name)); + sheetFiles.push(name); + log(`[${ms()}] wrote ${name} (slots ${start}..${end - 1})`); + } + + // manifest: every logical key -> its slot (dedup means many keys share a slot) + const slotOf = new Map(uniqueHashes.map((h, i) => [h, i])); + const icons: Record = {}; + for (const [key, hash] of keyToHash) { + const slot = slotOf.get(hash) ?? 0; + const idx = slot % perSheet; + icons[key] = { + s: Math.floor(slot / perSheet), + x: (idx % cols) * cell, + y: Math.floor(idx / cols) * cell, + }; + } + const manifest: AtlasManifest = { + cell, + atlasSize: SHEET, + sheets: sheetFiles, + counts: { keys: keyToHash.size, unique: uniqueHashes.length, skipped }, + icons, + }; + await writeFile(join(out, "manifest.json"), JSON.stringify(manifest)); + + let totalOut = 0; + for (const f of [...sheetFiles, "manifest.json"]) totalOut += (await stat(join(out, f))).size; + log( + `[${ms()}] atlas: ${keyToHash.size} keys -> ${uniqueHashes.length} unique -> ` + + `${sheetFiles.length} sheet(s), ${(totalOut / 1048576).toFixed(1)} MB` + + (skipped ? `, ${skipped} skipped` : ""), + ); + + return { ...manifest, ms: Date.now() - t0 }; +} diff --git a/app/src/server/paths.ts b/app/src/server/paths.ts new file mode 100644 index 00000000..2ea67de0 --- /dev/null +++ b/app/src/server/paths.ts @@ -0,0 +1,61 @@ +/** + * Filesystem layout for the app. Two roots, both overridable by env so a packaged + * build can point them wherever it bundles/stores things — nothing else in the app + * should join paths off `process.cwd()` directly. + * + * - DATA_DIR — user-writable state: the project sqlite files (`projects/`), the + * generated icon atlas (`icon-data/`), and `app-config.json`. + * Defaults to the working directory, which is the dev layout + * (`app/projects`, `app/icon-data`, `app/app-config.json`). A + * packaged build sets `PYOPS_DATA_DIR` to a per-OS user-data dir + * (see `defaultUserDataDir`). + * - RESOURCE_DIR — read-only assets shipped WITH the app: the drizzle migrations + * and the companion mod source. Defaults to the source tree; a + * packaged build sets `PYOPS_RESOURCE_DIR` (or the per-asset + * overrides) to where those are bundled. + */ +import { mkdirSync } from "node:fs"; +import { homedir } from "node:os"; +import { join, resolve } from "node:path"; + +/** The per-OS user-data dir a packaged build should store state in. This is *not* + * the dev default (dev keeps state in-tree, see `DATA_DIR`) — it's exported so the + * launcher can set `PYOPS_DATA_DIR` to it and the UI can show where data lives. */ +export function defaultUserDataDir(): string { + if (process.platform === "win32") { + return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "pyops"); + } + if (process.platform === "darwin") { + return join(homedir(), "Library", "Application Support", "pyops"); + } + return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "pyops"); +} + +/** User-writable state root: env override → in-tree working dir (dev). */ +export const DATA_DIR = process.env.PYOPS_DATA_DIR ?? process.cwd(); + +/** Read-only shipped-asset root: env override → the app source tree (cwd is `app/` + * in dev, so the mod is its sibling and `drizzle/` lives under it). */ +export const RESOURCE_DIR = process.env.PYOPS_RESOURCE_DIR ?? process.cwd(); + +/** Project sqlite files live here, one `.db` per project. */ +export const PROJECTS_DIR = join(DATA_DIR, "projects"); + +/** The generated icon atlas (sheets + manifest.json), served at `/icons`. */ +export const ICON_DATA_DIR = join(DATA_DIR, "icon-data"); + +/** Cross-project app config (active project + AI account settings), written 0600. */ +export const APP_CONFIG_FILE = join(DATA_DIR, "app-config.json"); + +/** Bundled drizzle migrations, applied at runtime to provision/upgrade a db. */ +export const MIGRATIONS_DIR = process.env.PYOPS_MIGRATIONS_DIR ?? join(RESOURCE_DIR, "drizzle"); + +/** Companion mod source, written into the user's Factorio mods dir on install. + * In the source tree the mod is a sibling of `app/`. */ +export const MOD_SOURCE_DIR = process.env.PYOPS_MOD_DIR ?? resolve(RESOURCE_DIR, "..", "mod"); + +/** Ensure the writable data dirs exist. Cheap + idempotent; call before a write. */ +export function ensureDataDir(): void { + mkdirSync(PROJECTS_DIR, { recursive: true }); + mkdirSync(ICON_DATA_DIR, { recursive: true }); +} diff --git a/app/src/server/projects.ts b/app/src/server/projects.ts index 7a797fec..352eca45 100644 --- a/app/src/server/projects.ts +++ b/app/src/server/projects.ts @@ -3,23 +3,19 @@ * (usually a different mod list). The files are the source of truth — there is no * registry; each db self-describes its name/createdAt in its own `meta`, and the * active project id lives in `app-config.json`. Switching repoints the shared `db` - * proxy; creating provisions a fresh db with the current schema (drizzle-kit push) - * then the user runs a data sync to fill it. + * proxy; creating provisions a fresh db with the current schema by applying the + * bundled migrations, then the user runs a data sync to fill it. */ -import { spawn } from "node:child_process"; -import { mkdir } from "node:fs/promises"; import { currentDatabaseFile, switchDatabase } from "../db/index.ts"; import { DEFAULT_ID, fileForProject, listProjectFiles, - migrateProjectsOnce, removeProjectFiles, writeProjectMeta, } from "../db/projects-fs.ts"; import { readAppConfig, writeAppConfig } from "./app-config.ts"; - -const APP_DIR = process.cwd(); +import { migrateToLatest } from "./provision.ts"; export type Project = { id: string; name: string; dbFile: string; createdAt: string }; @@ -30,7 +26,6 @@ const slugify = (s: string) => .replace(/^-+|-+$/g, "") || "project"; export async function listProjects() { - migrateProjectsOnce(); const projects: Project[] = listProjectFiles().map((p) => ({ id: p.id, name: p.name, @@ -57,26 +52,10 @@ export async function createProject(name: string): Promise { const existing = new Set(listProjectFiles().map((p) => p.id)); let id = slugify(name); while (existing.has(id) || id === DEFAULT_ID) id = `${id}-2`; - await mkdir("projects", { recursive: true }); const dbFile = fileForProject(id); - // provision the schema (drizzle-kit push against the new file) - await new Promise((resolvePromise, reject) => { - const child = spawn("node", ["node_modules/drizzle-kit/bin.cjs", "push", "--force"], { - cwd: APP_DIR, - env: { ...process.env, DATABASE_URL: dbFile }, - stdio: ["ignore", "pipe", "pipe"], - }); - let out = ""; - child.stdout.on("data", (d: Buffer) => (out += d.toString())); - child.stderr.on("data", (d: Buffer) => (out += d.toString())); - child.on("error", reject); - child.on("close", (code) => - code === 0 - ? resolvePromise() - : reject(new Error(`drizzle push failed:\n${out.slice(-1500)}`)), - ); - }); + // provision the schema in-process by applying the bundled drizzle migrations + migrateToLatest(dbFile); const createdAt = new Date().toISOString(); writeProjectMeta(dbFile, name, createdAt); diff --git a/app/src/server/provision.ts b/app/src/server/provision.ts new file mode 100644 index 00000000..dfb93977 --- /dev/null +++ b/app/src/server/provision.ts @@ -0,0 +1,36 @@ +/** + * Database provisioning: bring a project's sqlite file up to the current schema by + * applying the bundled drizzle migrations in-process. The schema lives entirely in + * `drizzle/`, so provisioning a database needs no dev tooling (drizzle-kit) on the + * machine — a packaged build is self-contained. + * + * Works on a brand-new (empty) file and on one whose tables already exist: the + * baseline migration uses `CREATE TABLE/INDEX IF NOT EXISTS`, so applying it to a + * populated db records the migration as applied without touching existing tables. + */ +import Database from "better-sqlite3"; +import { drizzle } from "drizzle-orm/better-sqlite3"; +import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { mkdirSync } from "node:fs"; +import { dirname } from "node:path"; + +import * as schema from "../db/schema.ts"; +import { MIGRATIONS_DIR } from "./paths.ts"; + +/** Apply pending migrations to an already-open drizzle connection (used on the hot + * path in `db/index.ts`, where the connection is cached and reused). Idempotent. */ +export function migrateConnection(db: ReturnType>): void { + migrate(db, { migrationsFolder: MIGRATIONS_DIR }); +} + +/** Create (if needed) and migrate a db file to the current schema, then close. + * Used when provisioning a freshly-created project before anything writes to it. */ +export function migrateToLatest(file: string): void { + mkdirSync(dirname(file), { recursive: true }); + const sqlite = new Database(file); + try { + migrateConnection(drizzle(sqlite, { schema })); + } finally { + sqlite.close(); + } +} diff --git a/app/vite.config.ts b/app/vite.config.ts index e4b0402f..58f3970e 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -73,7 +73,11 @@ function serveIconsDev() { : "application/octet-stream", ); res.setHeader("Content-Length", String(size)); - res.setHeader("Cache-Control", "no-cache"); + // versioned (`?v=`) sheet URLs are immutable; else revalidate + res.setHeader( + "Cache-Control", + /[?&]v=/.test(req.url ?? "") ? "public, max-age=31536000, immutable" : "no-cache", + ); createReadStream(file).pipe(res); }); }, @@ -84,10 +88,10 @@ const config = defineConfig({ // generated/data files: TanStack Router output, sqlite db, icon atlas + manifest. // `e2e/**` is a standalone Playwright package (own deps + tooling) — not ours. fmt: { - ignorePatterns: ["src/routeTree.gen.ts", "dev.db*", "icon-data/**", "e2e/**"], + ignorePatterns: ["src/routeTree.gen.ts", "dev.db*", "icon-data/**", "drizzle/**", "e2e/**"], }, lint: { - ignorePatterns: ["src/routeTree.gen.ts", "dev.db*", "icon-data/**", "e2e/**"], + ignorePatterns: ["src/routeTree.gen.ts", "dev.db*", "icon-data/**", "drizzle/**", "e2e/**"], jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], rules: { "vite-plus/prefer-vite-plus-imports": "error" }, options: { typeAware: true, typeCheck: true }, diff --git a/docs/README.md b/docs/README.md index 74dc0b11..4f2a55ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,8 @@ PyOps, see the [top-level README](../README.md). companion mod, and what flows across it. - [AI assistant](ai-assistant.md) — the planning agent, its tools, and the MCP surface. +- [Desktop app & releases](desktop.md) — the Tauri shell, packaging into a + self-contained bundle, the release-please pipeline, and self-update. See also [`AGENTS.md`](../AGENTS.md) for the contributor/agent working guide (toolchain commands, conventions, issue tracking). diff --git a/docs/architecture.md b/docs/architecture.md index 499eec72..f9a0fcdd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -46,6 +46,8 @@ pyops/ │ │ ├── server/ server-only modules (data, solver glue, bridge, AI) │ │ ├── solver/ pure-TS linear-system block solver + tests │ │ └── db/ Drizzle schema, import, synthesize, queries +│ ├── src-tauri/ Tauri desktop shell + packaging (see desktop.md) +│ ├── drizzle/ generated SQL migrations, applied in-process │ ├── icon-data/ generated icon atlas (gitignored) │ ├── projects/ per-project .db files, each self-named (gitignored) │ └── app-config.json app-level config: active project + AI key/model (gitignored) @@ -54,7 +56,7 @@ pyops/ │ ├── summary.lua Helmod-style production-block view │ ├── combinator.lua in-game request-combinator planner │ └── data.lua, settings.lua -├── scripts/ one-off dev/test scripts (icon-atlas builder, atlas server) +├── scripts/ dev-only helpers (tunnel-dev) └── docs/ this documentation ``` @@ -84,7 +86,9 @@ Each "project" (usually a different mod list) is its own SQLite file under self-describes its name/createdAt in its own `meta`, and the active project id lives in `app-config.json`. The `db` export (`app/src/db/index.ts`) is a proxy that always points at the active connection, so the query layer never changes when -you switch. Creating a project provisions a fresh schema (`drizzle-kit push`); you -then run a [data sync](data-pipeline.md) to -fill it. The relevant code lives in `app/src/server/projects.ts` and -`app/src/db/index.ts`. +you switch. Schema is provisioned in-process: on first connect (and when creating a +project) the bundled `drizzle/` migrations are applied via drizzle-orm's `migrate()` +(`app/src/server/provision.ts`), so no dev tooling is needed at runtime. A new +project starts empty; you then run a [data sync](data-pipeline.md) to fill it. The +relevant code lives in `app/src/server/projects.ts`, `app/src/server/provision.ts`, +and `app/src/db/index.ts`. diff --git a/docs/data-pipeline.md b/docs/data-pipeline.md index 5d8a80b4..965c5651 100644 --- a/docs/data-pipeline.md +++ b/docs/data-pipeline.md @@ -20,9 +20,11 @@ orchestrated end-to-end from **Settings › Game data** in the UI synthesize the recipes the engine doesn't model as recipes: mining, boiling, burning, spoiling, and per-temperature fluid variants (`app/src/db/synthesize.ts`). -5. **Rebuild the icon atlas** (`scripts/build-icon-atlas.mjs`): pack the dumped - sprites into content-hash-deduped 4096² sheets + a `(type, name) → slot` - manifest, served `immutable`. +5. **Rebuild the icon atlas** (`buildIconAtlas`, `app/src/server/icon-atlas.ts`): + pack the dumped sprites into content-hash-deduped 4096² sheets + a + `(type, name) → slot` manifest, written to the data dir's `icon-data/`. The app + serves them at `/icons/*` (`app/src/routes/icons.$.ts`), cached `immutable` and + cache-busted by the data fingerprint in the sheet URLs (`?v=…`). 6. **Compute cost analysis** — a YAFC-style LP that assigns each good an intrinsic cost (`app/src/server/cost-analysis.ts`, a port of YAFC's `CostAnalysis.cs`). 7. **Apply mod migrations** — read each enabled mod's `migrations/*.json` and diff --git a/docs/desktop.md b/docs/desktop.md new file mode 100644 index 00000000..c6742675 --- /dev/null +++ b/docs/desktop.md @@ -0,0 +1,103 @@ +# Desktop app & releases + +PyOps ships as a desktop app (a [Tauri](https://tauri.app) shell) in addition to +running as a plain web app. The shell wraps the **same** Nitro server — UI and +backend — in a native window; the React UI stays Tauri-agnostic, so anything +desktop-specific (external links, self-update) lives in the Rust shell, not the web +app. The shell is `app/src-tauri/` (`src/lib.rs` is the whole of it). + +## How it runs + +The window *is* the web app: it loads the local Nitro server. + +- **Dev** — `beforeDevCommand` (in `tauri.conf.json`) starts the server on port + 34115; Tauri waits for it and the window loads it. Run with `vp run tauri dev` + (or `cargo tauri dev`). +- **Bundled** — there is no `vp`/Node on the user's machine, so the Rust shell + starts the server itself: it spawns a **vendored `node` sidecar** against the + bundled `.output` server, with the data/migrations/mod paths resolved from the + bundle and the OS. The window opens hidden and reveals on first paint (no white + flash while the server boots). + +`PORT` is fixed (34115), which is why only **one instance** runs at a time +(`tauri-plugin-single-instance`); a second launch just focuses the existing window. +Supporting multiple instances / multiple open projects is tracked in +[#41](https://github.com/ApocDev/pyops/issues/41). + +## Where data lives + +All on-disk state resolves through a single **data dir** (`app/src/server/paths.ts`): +project databases (`projects/`), the icon atlas (`icon-data/`), and `app-config.json`. + +- **Dev** — the working directory (`app/`), so it shares your dev data. +- **Bundled** — the per-OS app-data dir (e.g. `~/.local/share/com.apocdev.pyops`), + overridable with `PYOPS_DATA_DIR`. A fresh install starts **empty**; you run a data + sync to populate it. + +The resolved path is shown in **Settings → Game data → Storage location** (copy-able) +so it's findable for debugging or sharing a database. + +## Building a bundle + +```bash +cd app/src-tauri +./vendor-node.sh # fetch the node sidecar (gitignored, per-platform) +cd .. && vp run tauri build --bundles deb,appimage # or dmg / nsis on mac / windows +``` + +- The vendored `node` is the runtime; `.output` (the server), `drizzle/` + (migrations), and `../mod` are bundled as resources (see `tauri.conf.json` + `bundle.resources` / `externalBin`). +- **Targets**: `deb` + `AppImage` (Linux), `dmg` (macOS), NSIS installer (Windows). + The local default is `deb` only — AppImage's `linuxdeploy` tooling is unhappy on a + non-Debian host, but it builds cleanly on the Ubuntu CI runner. +- **Linux**: the shell forces `GDK_BACKEND=x11` + `WEBKIT_DISABLE_DMABUF_RENDERER` + to avoid a webkit2gtk Wayland "Error 71" crash. AppImages need FUSE; if missing, + run with `--appimage-extract-and-run`. + +### Plugins in the shell + +`single-instance`, `window-state` (remembers size/position), `opener` (external +links open in the system browser), `dialog` (the update prompt), `updater`, and +`shell` (the node sidecar). + +## Releases + +Releases are driven by **conventional commits** via +[release-please](https://github.com/googleapis/release-please) — `feat:` → minor, +`fix:` → patch, `feat!:`/`BREAKING CHANGE:` → major. + +1. Push conventional commits to `main`. +2. release-please opens/maintains a **release PR** that bumps the version (kept in + lockstep across `app/package.json`, `app/src-tauri/Cargo.toml`, and + `tauri.conf.json` — **don't hand-edit these**) and updates the changelog. +3. Merging the release PR creates the tag + GitHub release; the build matrix then + attaches the bundles + `latest.json` to it. + +Config: `release-please-config.json` + `.release-please-manifest.json`; workflow: +`.github/workflows/release.yml` (release-please job → gated build job, same workflow +so no PAT is needed). `workflow_dispatch` runs a build-only smoke test. + +## Self-update + +The app updates itself from GitHub Releases. + +- The release build (via `tauri-action`, with the signing key from CI secrets) + produces **signed** updater artifacts and an aggregated `latest.json` listing each + platform's artifact URL + signature + the changelog (`notes`). +- On launch, a bundled app checks `releases/latest/download/latest.json`; if a newer + version exists it shows a native **"Update available"** dialog with the version and + changelog, and **Install & Restart** downloads, verifies the signature against the + baked-in public key, installs, and relaunches. All Rust-side (`check_for_update` in + `lib.rs`); dev builds skip it. +- Self-update rides the **AppImage / NSIS / .app** artifacts — the `.deb` does not + self-update (use the AppImage on Linux for updates). + +**Signing key**: the public key lives in `tauri.conf.json` (`plugins.updater.pubkey`); +the private key + password are the `TAURI_SIGNING_PRIVATE_KEY` / +`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` repo secrets. The private key must be backed up — +if it's lost, existing installs can no longer verify updates and the key has to be +rotated (which changes the public key). + +A **nightly / prerelease channel** (a second `latest.json` the app can opt into) is a +planned follow-on, not yet wired up. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..a17bae75 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "include-component-in-tag": false, + "packages": { + "app": { + "release-type": "node", + "extra-files": [ + { "type": "json", "path": "src-tauri/tauri.conf.json", "jsonpath": "$.version" }, + { "type": "toml", "path": "src-tauri/Cargo.toml", "jsonpath": "$.package.version" } + ] + } + } +} diff --git a/scripts/atlas-preview.html b/scripts/atlas-preview.html deleted file mode 100644 index bcf0c537..00000000 --- a/scripts/atlas-preview.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - PyOps icon atlas preview - - - -
- PyOps icon atlasloading…   - -
-
- - - diff --git a/scripts/build-icon-atlas.mjs b/scripts/build-icon-atlas.mjs deleted file mode 100644 index f048b410..00000000 --- a/scripts/build-icon-atlas.mjs +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node -// One-off test: pack Factorio's dumped icon sprites into content-hash-deduped -// atlas sheets + a (type/name)->slot manifest. -// -// Usage: -// node build-icon-atlas.mjs [srcDir] [outDir] [cellPx] -// srcDir default ~/.factorio/script-output (output of `factorio --dump-icon-sprites`) -// outDir default ./atlas-out -// cellPx default 64 -// -// Layout: uniform grid, CELL x CELL cells in a 4096x4096 sheet. No bin-packing -// (icons are square), so placement is trivial and deterministic. - -import { readdir, mkdir, writeFile, stat } from "node:fs/promises"; -import { readFileSync } from "node:fs"; -import { createHash } from "node:crypto"; -import { join, relative, extname } from "node:path"; -import sharp from "sharp"; - -const HOME = process.env.HOME ?? ""; -const SRC = process.argv[2] ?? join(HOME, ".factorio", "script-output"); -const OUT = process.argv[3] ?? new URL("./atlas-out", import.meta.url).pathname; -const CELL = Number(process.argv[4] ?? 64); -const SHEET = 4096; -const COLS = Math.floor(SHEET / CELL); -const PER_SHEET = COLS * COLS; - -const t0 = Date.now(); -const ms = () => `${((Date.now() - t0) / 1000).toFixed(1)}s`; - -async function collectPngs(dir) { - const out = []; - for (const ent of await readdir(dir, { withFileTypes: true })) { - const p = join(dir, ent.name); - if (ent.isDirectory()) out.push(...(await collectPngs(p))); - else if (ent.isFile() && p.toLowerCase().endsWith(".png")) out.push(p); - } - return out; -} - -console.log(`src: ${SRC}`); -console.log(`out: ${OUT}`); -console.log(`cell: ${CELL}px (${COLS}x${COLS} = ${PER_SHEET} per ${SHEET}^2 sheet)\n`); - -const files = await collectPngs(SRC); -console.log(`[${ms()}] found ${files.length} png files`); - -// key = "type/name" (relative path minus .png); dedup by raw file-byte hash -const keyToHash = new Map(); -const hashToFile = new Map(); -for (const f of files) { - const rel = relative(SRC, f).split("\\").join("/"); - const key = rel.slice(0, rel.length - extname(rel).length); - const hash = createHash("sha1").update(readFileSync(f)).digest("hex"); - keyToHash.set(key, hash); - if (!hashToFile.has(hash)) hashToFile.set(hash, f); -} -const uniqueHashes = [...hashToFile.keys()]; -const sheetCount = Math.ceil(uniqueHashes.length / PER_SHEET); -console.log( - `[${ms()}] ${keyToHash.size} keys -> ${uniqueHashes.length} unique images (${( - (1 - uniqueHashes.length / keyToHash.size) * - 100 - ).toFixed(0)}% dedup) -> ${sheetCount} sheet(s)`, -); - -await mkdir(OUT, { recursive: true }); - -// build + encode one sheet at a time (cap peak memory at ~one 64MB RGBA buffer) -const sheetFiles = []; -let skipped = 0; -for (let s = 0; s < sheetCount; s++) { - const buf = Buffer.alloc(SHEET * SHEET * 4); // transparent RGBA - const start = s * PER_SHEET; - const end = Math.min(start + PER_SHEET, uniqueHashes.length); - for (let i = start; i < end; i++) { - const idx = i - start; - const cx = (idx % COLS) * CELL; - const cy = Math.floor(idx / COLS) * CELL; - try { - const raw = await sharp(hashToFile.get(uniqueHashes[i])) - .resize(CELL, CELL, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) - .ensureAlpha() - .raw() - .toBuffer(); - for (let row = 0; row < CELL; row++) { - const srcStart = row * CELL * 4; - raw.copy(buf, ((cy + row) * SHEET + cx) * 4, srcStart, srcStart + CELL * 4); - } - } catch (err) { - skipped++; - } - } - const name = `atlas-${s}.png`; - await sharp(buf, { raw: { width: SHEET, height: SHEET, channels: 4 } }) - .png({ compressionLevel: 9 }) - .toFile(join(OUT, name)); - sheetFiles.push(name); - console.log(`[${ms()}] wrote ${name} (slots ${start}..${end - 1})`); -} - -// manifest: every logical key -> its slot (dedup means many keys share a slot) -const slotOf = new Map(uniqueHashes.map((h, i) => [h, i])); -const icons = {}; -for (const [key, hash] of keyToHash) { - const slot = slotOf.get(hash); - const idx = slot % PER_SHEET; - icons[key] = { - s: Math.floor(slot / PER_SHEET), - x: (idx % COLS) * CELL, - y: Math.floor(idx / COLS) * CELL, - }; -} -const manifest = { - cell: CELL, - atlasSize: SHEET, - sheets: sheetFiles, - counts: { keys: keyToHash.size, unique: uniqueHashes.length, skipped }, - icons, -}; -await writeFile(join(OUT, "manifest.json"), JSON.stringify(manifest)); - -// report output sizes -let totalOut = 0; -for (const f of [...sheetFiles, "manifest.json"]) { - const sz = (await stat(join(OUT, f))).size; - totalOut += sz; - console.log(` ${f.padEnd(14)} ${(sz / 1048576).toFixed(2)} MB`); -} -console.log( - `\n[${ms()}] done. ${sheetFiles.length} sheet(s), ${(totalOut / 1048576).toFixed(1)} MB total${ - skipped ? `, ${skipped} skipped` : "" - }`, -); diff --git a/scripts/reload-factorio-game b/scripts/reload-factorio-game deleted file mode 100755 index b46b24aa..00000000 --- a/scripts/reload-factorio-game +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: scripts/reload-factorio-game [--dry-run|--focus-only] - -Focus Factorio, then perform: - Escape -> click Save -> click Save game (confirm) -> click Exit -> click Continue - -Positions are normalized x,y (0..1) on Factorio's monitor. - -Environment overrides: - FACTORIO_WINDOW_MATCH Window title/class substring. Default: factorio - The default matches Factorio's window class/name exactly. - FACTORIO_OUTPUT KDE output name. Default: active KWin output - SAVE_MENU_POS 'Save' button in the pause menu. Default: 0.498,0.535 - SAVE_CONFIRM_POS 'Save game' confirm button (save screen). Default: 0.786,0.748 - EXIT_POS 'Exit'/quit-to-menu button. Default: 0.453,0.624 - CONTINUE_POS 'Continue' button on the main menu. Default: 0.501,0.358 - BEFORE_ACTION_DELAY Seconds after focusing Factorio. Default: 0.8 - AFTER_ESCAPE_DELAY Seconds after Escape. Default: 0.6 - AFTER_SAVE_MENU_DELAY Seconds after Save click (save screen opens). Default: 0.6 - AFTER_SAVE_CONFIRM_DELAY Seconds after Save game confirm (writing save). Default: 2.0 - AFTER_EXIT_DELAY Seconds after Exit click (main menu loads). Default: 1.5 - -Example tuning: - SAVE_MENU_POS=0.5,0.53 EXIT_POS=0.45,0.62 CONTINUE_POS=0.5,0.36 \ - scripts/reload-factorio-game -EOF -} - -dry_run=0 -focus_only=0 -case "${1:-}" in - --dry-run) dry_run=1 ;; - --focus-only) focus_only=1 ;; - -h|--help) usage; exit 0 ;; - "") ;; - *) usage >&2; exit 2 ;; -esac - -require() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "missing required command: $1" >&2 - exit 1 - fi -} - -require qdbus6 -require kscreen-doctor -require jq -require ydotool - -window_match="${FACTORIO_WINDOW_MATCH:-factorio}" -output_name="${FACTORIO_OUTPUT:-$(qdbus6 org.kde.KWin /KWin org.kde.KWin.activeOutputName)}" - -save_menu_pos="${SAVE_MENU_POS:-0.498,0.535}" -save_confirm_pos="${SAVE_CONFIRM_POS:-0.786,0.748}" -exit_pos="${EXIT_POS:-0.453,0.624}" -continue_pos="${CONTINUE_POS:-0.501,0.358}" - -before_action_delay="${BEFORE_ACTION_DELAY:-0.8}" -after_escape_delay="${AFTER_ESCAPE_DELAY:-0.6}" -after_save_menu_delay="${AFTER_SAVE_MENU_DELAY:-0.6}" -after_save_confirm_delay="${AFTER_SAVE_CONFIRM_DELAY:-2.0}" -after_exit_delay="${AFTER_EXIT_DELAY:-1.5}" - -if (( ! dry_run )); then - if ! systemctl --user is-active --quiet ydotool.service; then - if systemctl --user list-unit-files ydotool.service >/dev/null 2>&1; then - systemctl --user start ydotool.service - sleep 0.3 - fi - fi - - if ! ydotool key 0 >/dev/null 2>&1; then - cat >&2 <<'EOF' -ydotoold is not reachable. Start it with: - systemctl --user start ydotool.service - -To enable it after login: - systemctl --user enable --now ydotool.service -EOF - exit 1 - fi -fi - -json_escape() { - jq -Rn --arg value "$1" '$value' -} - -# Report the currently active window via a KWin script (non-interactive). -# The script prints "||" (lowercased) to -# the KWin log, which we read back from the journal using a unique nonce. -# -# We intentionally avoid org.kde.KWin.queryWindowInfo here: that DBus method -# pops an interactive crosshair window-picker and blocks until the user clicks, -# which is unusable from an automated script. -active_window_descriptor() { - local nonce script plugin line - nonce="pyops-active-$$-${RANDOM}-${RANDOM}" - script="$(mktemp --suffix=.js)" - plugin="pyops-reload-factorio-active" - trap 'rm -f "$script"; qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript pyops-reload-factorio-active >/dev/null 2>&1 || true' RETURN - - cat >"$script" </dev/null 2>&1 || true - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript "$script" "$plugin" >/dev/null - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.start >/dev/null - sleep 0.2 - - line="$(journalctl _COMM=kwin_wayland -n 100 -o cat --since "10 seconds ago" 2>/dev/null | grep -F "$nonce" | tail -n 1 || true)" - printf '%s\n' "${line#"$nonce" }" -} - -active_window_matches_factorio() { - local descriptor - descriptor="$(active_window_descriptor || true)" - [[ -n "$descriptor" ]] || return 1 - - jq -Rn \ - --arg desc "$descriptor" \ - --arg needle "$window_match" ' - ($desc | split("|")) as $parts - | ($parts[0] // "") as $cls - | ($parts[1] // "") as $nm - | ($parts[2] // "") as $cap - | ($needle | ascii_downcase) as $needle - | if $needle == "factorio" then - ($cls == "factorio") or ($nm == "factorio") or ($cap | startswith("factorio ")) - else - (($cls + " " + $nm + " " + $cap) | contains($needle)) - end - ' | - grep -qx true -} - -focus_factorio() { - local script plugin - script="$(mktemp --suffix=.js)" - plugin="pyops-reload-factorio-focus" - trap 'rm -f "$script"; qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript pyops-reload-factorio-focus >/dev/null 2>&1 || true' RETURN - - cat >"$script" </dev/null 2>&1 || true - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript "$script" "$plugin" >/dev/null - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.start >/dev/null -} - -resolve_output_geometry() { - read -r output_x output_y output_w output_h < <( - kscreen-doctor -j | - jq -r --arg name "$output_name" ' - .outputs[] - | select(.name == $name and .enabled == true) - | "\(.pos.x) \(.pos.y) \((.size.width / .scale) | floor) \((.size.height / .scale) | floor)" - ' | - head -n 1 - ) || true - - if [[ -z "${output_x:-}" ]]; then - echo "could not find enabled KDE output: $output_name" >&2 - exit 1 - fi -} - -point_to_xy() { - local point="$1" - local px="${point%,*}" - local py="${point#*,}" - - awk \ - -v ox="$output_x" \ - -v oy="$output_y" \ - -v ow="$output_w" \ - -v oh="$output_h" \ - -v px="$px" \ - -v py="$py" \ - 'BEGIN { printf "%d %d\n", ox + (ow * px), oy + (oh * py) }' -} - -# Read KWin's real pointer position ("x,y" in compositor/logical coordinates) -# via a one-shot KWin script printed to the journal. This is our feedback -# signal for positioning the cursor. -read_cursor() { - local nonce script plugin line - nonce="pyops-cursor-$$-${RANDOM}-${RANDOM}" - script="$(mktemp --suffix=.js)" - plugin="pyops-reload-factorio-cursor" - trap 'rm -f "$script"; qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript pyops-reload-factorio-cursor >/dev/null 2>&1 || true' RETURN - - printf 'print("%s " + workspace.cursorPos.x + "," + workspace.cursorPos.y);\n' "$nonce" >"$script" - - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript "$plugin" >/dev/null 2>&1 || true - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript "$script" "$plugin" >/dev/null - qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.start >/dev/null - sleep 0.08 - - line="$(journalctl _COMM=kwin_wayland -n 100 -o cat --since "10 seconds ago" 2>/dev/null | grep -F "$nonce" | tail -n 1 || true)" - printf '%s\n' "${line#"$nonce" }" -} - -# Position the pointer at logical x,y. -# -# ydotool's absolute mode is non-functional on this Wayland setup (every input -# maps to the same point; see ydotool issue #250), and relative moves are -# distorted ~2x by libinput pointer acceleration. So: slam to the top-left -# corner for a deterministic origin (huge negative deltas clamp at 0,0), then -# close the loop — nudge by a damped fraction of the remaining delta and -# re-read KWin's real cursor position until we land on target. The feedback -# loop cancels the acceleration without changing any system setting. -move_to() { - local tx="$1" ty="$2" - local i cx cy dx dy sx sy - - ydotool mousemove -x -20000 -y -20000 >/dev/null 2>&1 - sleep 0.05 - - for ((i = 0; i < 40; i++)); do - IFS=, read -r cx cy < <(read_cursor) - [[ -n "${cx:-}" && -n "${cy:-}" ]] || return 1 - dx=$((tx - cx)) - dy=$((ty - cy)) - if (( dx <= 3 && dx >= -3 && dy <= 3 && dy >= -3 )); then - return 0 - fi - sx=$((dx * 5 / 10)) - sy=$((dy * 5 / 10)) - (( sx == 0 && dx > 0 )) && sx=1 - (( sx == 0 && dx < 0 )) && sx=-1 - (( sy == 0 && dy > 0 )) && sy=1 - (( sy == 0 && dy < 0 )) && sy=-1 - ydotool mousemove -x "$sx" -y "$sy" >/dev/null 2>&1 - sleep 0.03 - done - - return 1 -} - -click_point() { - local name="$1" - local point="$2" - local x y - read -r x y < <(point_to_xy "$point") - - if (( dry_run )); then - printf 'click %-11s %s -> %s,%s on %s (%sx%s at %s,%s)\n' \ - "$name" "$point" "$x" "$y" "$output_name" "$output_w" "$output_h" "$output_x" "$output_y" - return - fi - - if ! move_to "$x" "$y"; then - echo "could not position cursor at $x,$y for '$name' click; aborting" >&2 - exit 1 - fi - sleep 0.1 - ydotool click 0xC0 >/dev/null -} - -press_key() { - local name="$1" - local code="$2" - - if (( dry_run )); then - printf 'key %s (%s)\n' "$name" "$code" - return - fi - - ydotool key "${code}:1" "${code}:0" >/dev/null -} - -if (( dry_run )); then - resolve_output_geometry - echo "output: $output_name ${output_w}x${output_h}+${output_x}+${output_y}" - echo "window match: $window_match" -else - focus_factorio - sleep "$before_action_delay" - if ! active_window_matches_factorio; then - cat >&2 < { - const path = decodeURIComponent((req.url ?? "/").split("?")[0]); - try { - let file, - immutable = false; - if (path === "/" || path === "/index.html") { - file = INDEX; - } else { - file = join(ROOT, path); - if (file.endsWith(".png") || file.endsWith(".webp")) immutable = true; - } - const data = await readFile(file); - res.setHeader("Content-Type", MIME[extname(file)] ?? "application/octet-stream"); - res.setHeader( - "Cache-Control", - immutable ? "public, max-age=31536000, immutable" : "no-cache", - ); - res.end(data); - console.log( - `200 ${path} (${(data.length / 1024).toFixed(0)} KB)${immutable ? " [immutable]" : ""}`, - ); - } catch { - res.statusCode = 404; - res.end("not found: " + path); - console.log(`404 ${path}`); - } - }) - .listen(PORT, () => console.log(`serving http://localhost:${PORT}`));