diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..1199790 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Auto-bump the patch version on every push of a branch, so each pushed build +# is a distinct version. Releases stay manual (they are cut by pushing a v* tag, +# which this hook deliberately skips). +# +# git can't add a commit to a push that is already in flight, so this hook: +# 1. bumps package.json / Cargo.toml / tauri.conf.json / Cargo.lock, +# 2. commits the bump onto the current branch, +# 3. re-runs the push (which carries the bump), and +# 4. aborts the original push, now superseded by the re-push. +# +# Enable once with: git config core.hooksPath .githooks +set -euo pipefail + +# Guard against the recursion from our own re-push below. +[ -n "${ELDRUN_BUMPING:-}" ] && exit 0 + +remote="$1" + +repo_root="$(git rev-parse --show-toplevel)" + +# stdin: per pushed ref. +# Bump only when pushing a branch whose version still matches what is already on +# the remote (or a brand-new remote branch). This skips: +# - pure tag / deletion pushes (e.g. `git push origin v0.1.8` to cut a release), +# - pushes where the version was already advanced manually (minor/major bump), +# so the version on the remote strictly increases on each push without ever +# double-bumping. +pkg_version() { git show "$1:package.json" 2>/dev/null | jq -r '.version // empty' 2>/dev/null; } + +bump=0 +while read -r local_ref local_sha _remote_ref remote_sha; do + case "$local_ref" in + refs/tags/*|"") continue ;; # tag or deletion — leave version alone + refs/*) : ;; + *) continue ;; + esac + if [[ "$remote_sha" =~ ^0+$ ]]; then + bump=1 # new branch on the remote — give it a fresh version + elif [ "$(pkg_version "$local_sha")" = "$(pkg_version "$remote_sha")" ]; then + bump=1 # version unchanged since last push — bump it + fi +done + +[ "$bump" -eq 0 ] && exit 0 + +new="$("$repo_root/scripts/bump-version.sh" patch)" + +# Cargo.lock carries the workspace's own version too, so it is part of the bump +# — staged with the rest or the bump commit leaves the tree dirty behind it. +# `--` and the explicit list keep this to the version files: a push must never +# sweep up whatever else happens to be in the working tree. +git -C "$repo_root" add -- \ + package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json Cargo.lock +git -C "$repo_root" commit -m "chore: bump version to v${new}" >/dev/null + +echo "pre-push: bumped version to v${new}; pushing with the bump included…" >&2 +ELDRUN_BUMPING=1 git push "$remote" HEAD +echo "pre-push: pushed v${new}. The 'failed to push' line below is the original" >&2 +echo " pre-bump push being intentionally aborted — the push succeeded." >&2 + +# The re-push above already sent the bump commit; stop the original (stale) push. +exit 1 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 509eeec..dd088a9 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -52,11 +52,9 @@ jobs: - name: Run backend tests run: cargo test --manifest-path src-tauri/Cargo.toml - package-windows: - name: package-windows + test-windows: + name: test-windows runs-on: windows-latest - if: github.event_name == 'push' - needs: test steps: - name: Checkout @@ -79,9 +77,70 @@ jobs: - name: Install frontend dependencies run: npm ci + - name: Build frontend + run: npm run build + + - name: Run backend tests + run: cargo test --manifest-path src-tauri/Cargo.toml + + test-macos: + name: test-macos + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: npm + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + + - name: Install frontend dependencies + run: npm ci + + - name: Build frontend + run: npm run build + - name: Run backend tests run: cargo test --manifest-path src-tauri/Cargo.toml + package-windows: + name: package-windows + runs-on: windows-latest + if: github.event_name == 'push' + needs: test-windows + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: npm + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + + - name: Install frontend dependencies + run: npm ci + - name: Build release bundles run: npm run tauri:build -- --bundles nsis @@ -141,51 +200,181 @@ jobs: uses: actions/upload-artifact@v4 with: name: eldrun-${{ github.sha }}-appimage - path: src-tauri/target/release/bundle/appimage/*.AppImage + # Project-root target/ is a fallback for when CARGO_TARGET_DIR is + # overridden by swatinem/rust-cache. + path: | + src-tauri/target/release/bundle/appimage/*.AppImage + target/release/bundle/appimage/*.AppImage - name: Upload DEB uses: actions/upload-artifact@v4 with: name: eldrun-${{ github.sha }}-deb - path: src-tauri/target/release/bundle/deb/*.deb + # Project-root target/ is a fallback for when CARGO_TARGET_DIR is + # overridden by swatinem/rust-cache. + path: | + src-tauri/target/release/bundle/deb/*.deb + target/release/bundle/deb/*.deb + + package-macos: + name: package-macos + runs-on: macos-latest + if: github.event_name == 'push' + needs: test-macos + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: npm + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-apple-darwin,aarch64-apple-darwin + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + + - name: Install frontend dependencies + run: npm ci + + - name: Build release bundles + # Unsigned alpha .dmg (matches the unsigned Windows NSIS today). macOS + # bundle/window overrides come from src-tauri/tauri.macos.conf.json, + # which Tauri auto-merges on macOS. To ship a signed + notarized build + # later, import an Apple Developer cert into a keychain in a step above + # and set APPLE_SIGNING_IDENTITY + APPLE_ID + APPLE_PASSWORD + + # APPLE_TEAM_ID (or APPLE_API_KEY_ID/APPLE_API_ISSUER/APPLE_API_KEY_PATH) + # from repo secrets here before running the build. + run: npm run tauri:build -- --bundles dmg --target universal-apple-darwin + + - name: Rename universal DMG + run: | + # Only one of these roots exists (rust-cache redirects CARGO_TARGET_DIR + # to the project root), and find exits 1 on the missing one — which the + # step's `bash -e` would treat as fatal before `test -n` ever runs. + dmg="$(find src-tauri/target target -path '*/universal-apple-darwin/release/bundle/dmg/*.dmg' -print -quit 2>/dev/null || true)" + test -n "$dmg" + # Name the published asset by version, like the AppImage/deb/exe + # bundles do — naming it by commit sha made the macOS asset the only + # one a user couldn't match to a release at a glance. Guard the move: + # Tauri may already emit exactly this name, and `mv x x` fails. + version="$(node -p "require('./package.json').version")" + target="$(dirname "$dmg")/Eldrun_${version}_universal.dmg" + [ "$dmg" = "$target" ] || mv "$dmg" "$target" + + - name: Upload DMG + uses: actions/upload-artifact@v4 + with: + name: eldrun-${{ github.sha }}-macos-universal-dmg + path: | + src-tauri/target/universal-apple-darwin/release/bundle/dmg/*universal.dmg + target/universal-apple-darwin/release/bundle/dmg/*universal.dmg release: name: release runs-on: ubuntu-22.04 - if: startsWith(github.ref, 'refs/tags/v0.') && endsWith(github.ref, '.0') - needs: [package, package-windows] + needs: [package, package-windows, package-macos] + if: >- + always() && + startsWith(github.ref, 'refs/tags/v') && + ( + needs.package.result == 'success' || + needs.package-windows.result == 'success' || + needs.package-macos.result == 'success' + ) permissions: contents: write steps: - name: Download AppImage artifact + if: needs.package.result == 'success' uses: actions/download-artifact@v4 with: name: eldrun-${{ github.sha }}-appimage path: release-assets - name: Download DEB artifact + if: needs.package.result == 'success' uses: actions/download-artifact@v4 with: name: eldrun-${{ github.sha }}-deb path: release-assets - name: Download Windows NSIS artifact + if: needs.package-windows.result == 'success' uses: actions/download-artifact@v4 with: name: eldrun-${{ github.sha }}-windows-nsis path: release-assets + - name: Download macOS DMG artifact + if: needs.package-macos.result == 'success' + uses: actions/download-artifact@v4 + with: + name: eldrun-${{ github.sha }}-macos-universal-dmg + path: release-assets + + - name: Build release notes + env: + RELEASE_TAG: ${{ github.ref_name }} + RELEASE_SHA: ${{ github.sha }} + LINUX_RESULT: ${{ needs.package.result }} + WINDOWS_RESULT: ${{ needs.package-windows.result }} + MACOS_RESULT: ${{ needs.package-macos.result }} + run: | + { + printf 'Eldrun %s — alpha / preview build.\n\n' "$RELEASE_TAG" + printf 'Built from commit `%s`.\n\n' "$RELEASE_SHA" + printf 'Artifacts:\n' + + if [ "$LINUX_RESULT" = success ]; then + printf -- '- Linux: `.AppImage` (portable) and `.deb`\n' + else + printf -- '- Linux: unavailable (package job: `%s`)\n' "$LINUX_RESULT" + fi + + if [ "$WINDOWS_RESULT" = success ]; then + printf -- '- Windows: NSIS `.exe` installer\n' + else + printf -- '- Windows: unavailable (package job: `%s`)\n' "$WINDOWS_RESULT" + fi + + if [ "$MACOS_RESULT" = success ]; then + printf -- '- macOS: universal Intel/Apple Silicon `.dmg` (unsigned — right-click → Open, or run ' + printf '`xattr -dr com.apple.quarantine /Applications/Eldrun.app`)\n' + else + printf -- '- macOS: unavailable (package job: `%s`)\n' "$MACOS_RESULT" + fi + + printf '\nThis is an early alpha; expect rough edges.\n' + } > release-body.md + - name: Publish GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} name: ${{ github.ref_name }} - body: | - Eldrun release ${{ github.ref_name }}. - - Built from commit ${{ github.sha }}. + # Publish as a full release so the newest tag becomes GitHub's + # "Latest" — a pre-release never does, which left the releases page + # with no latest build and the README's /releases/latest link stale. + # The notes still say alpha; this flag is about discoverability. + prerelease: false + body_path: release-body.md + fail_on_unmatched_files: false + # Recursive globs: the upload steps list two path patterns, so + # upload-artifact roots each artifact at the workspace dir and the + # bundle ends up nested (e.g. target/release/bundle/appimage/*.AppImage) + # rather than at the artifact root. files: | - release-assets/*.AppImage - release-assets/*.deb - release-assets/*.exe + release-assets/**/*.AppImage + release-assets/**/*.deb + release-assets/**/*.exe + release-assets/**/*.dmg diff --git a/.gitignore b/.gitignore index 28ce37b..5fdac2c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,11 @@ src-tauri/WixTools/ # JSON backup files created by storage layer *.bak.json +.env +.env.local +*.swp +*.swo +.idea/ + +# Machine-list exports (contain real hostnames) +eldrun-machines.json diff --git a/AGENTS.md b/AGENTS.md index 50a92c2..6a6d967 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,29 @@ ## Purpose -ProjectEldrun, or Eldrun, is a Tauri desktop workspace for AI-assisted +ProjectEldrun, or Eldrun, is a Tauri 2 desktop workspace for AI-assisted development. It manages a root control terminal, one terminal per active -project, a bottom project switcher, a right-side project file browser, global -app launching, file opening, time tracking, and optional workspace backend -integration. +project, a bottom project switcher, a right-side project file viewer, global +app launching, file opening, time tracking, and optional workspace/backend +integration in one window. + +The product thesis is project-scoped desktop context: opening a project should +swap the relevant terminals, files, apps, downloads/default-app behavior, and +time tracking together. + +## Load-Bearing Docs + +- Root project context: `CLAUDE.md`. +- Frontend file map: `src/CLAUDE.md`. +- Backend file map: `src-tauri/CLAUDE.md`. +- Architecture/user documentation: `DOCUMENTATION.md`. +- Work plan and grouped TODO conventions: `TODO.md` and `todo/`. +- Remote/multi-host plan: `docs/multi_host_remote_plan.md`. +- Remote lockstep case matrix: `docs/git_lockstep_case_matrix.md`. +- Container plan: `docs/docker_projects_plan.md`. + +Read the relevant map before editing unfamiliar code. The maps intentionally +list only load-bearing files; the tree is still the source of truth. ## Current Architecture @@ -18,8 +36,10 @@ integration. - Terminal UI lives in `src/components/terminal/TerminalView.tsx`; terminal backend commands live in `src-tauri/src/commands/terminal.rs` and `src-tauri/src/terminal/`. -- File browsing UI lives in `src/components/files/`; filesystem commands live - mostly in `src-tauri/src/commands/projects.rs`. +- File browsing/viewing UI lives in `src/components/files/` and is shared by + the right panel, Files tabs, and per-subwindow file sidebars. +- Filesystem commands live mostly in `src-tauri/src/commands/projects.rs` and + `src-tauri/src/commands/fs.rs`. - External app launching and tracked windows live in `src-tauri/src/commands/apps.rs`. - Settings and persisted schema types live in `src/stores/`, `src/types/`, and @@ -33,68 +53,198 @@ integration. - Managed projects normally live under `~/eldrun/projects//`. The root terminal uses `~/eldrun/root/`. - Global state lives in `~/.local/share/eldrun/`, especially `projects.json`, - `settings.json`, `default_apps.json`, `time_log.json`, and - `active_session.json`. -- Project-local state lives in each project's `project.json`; current open-app - metadata is stored in `project.json["open_apps"]`. + `settings.json`, `default_apps.json`, `time_log.json`, + `active_session.json`, and `usage_stats.json`. +- Usage stats are local-only rolling hour/day counters behind the daily recap. + Do not mix them with time (`time_summary.json`), network bytes + (`net_usage.json`), or git stats, which are read from their own sources. +- Project-local state lives in each project's `project.json`. This includes + `open_apps`, `tab_layout`, `tab_groups`, remote specs, runtime/container + settings, and per-project file-viewer settings. - New/imported projects are scaffolded with `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.claude/settings.json`, `.gitignore`, `TODO.md`, `ROADMAP.md`, - `STATUS.md`, and `DOCUMENTATION.md`. + `STATUS.md`, and `README.md` when missing. +- Box agent docs contain generated link blocks between + `` and ``; preserve + user edits outside those blocks. - `TODO.md` uses grouped IDs such as `G1.1`. When adding a TODO, place it in the matching group, create a new group if no current group fits, or merge groups if the TODO depends on distinct areas that should be tracked together. - Avoid unrelated rewrites in docs, generated state, built assets, project metadata, `dist/`, `target/`, and backup files. -## Development Workflow +## Runtime Safety -- Prefer small, focused changes that match the existing React/TypeScript and - Rust/Tauri style. -- Use `rg` for code search. - Do not launch Eldrun from an agent terminal for verification. Opening a - second Eldrun instance can corrupt workspace state; ask the user to run or - restart the existing instance when runtime validation is needed. + second Eldrun instance can corrupt workspace state. - Do not run `npm run tauri`, `npm run dev`, `cargo tauri dev`, or `./start-eldrun-tauri.sh` unless the user explicitly asks and confirms it is safe to launch a new instance. +- Frontend (`src/`) changes hot-reload in the running instance, so do not ask + the user to restart just to see TSX/CSS edits. +- Backend (`src-tauri/`) changes require the user to rebuild/restart the + existing instance for runtime validation. Ask for that only when runtime QA is + needed. +- Runtime launch commands are human-only by default. + +## Development Workflow + +- Use `rtk` before shell commands, per the included RTK instructions. +- Prefer small, focused changes that match the existing React/TypeScript and + Rust/Tauri style. +- Use `rg` for code search. - When changing frontend behavior, prefer local component state and existing Zustand stores over adding new global state. - When changing backend commands, keep Tauri command payload names compatible with the camelCase keys used by the frontend. - Preserve Python-era JSON shapes where the Rust schema already supports them; existing user state should round-trip cleanly. +- Keep service modules `AppHandle`-free and unit-testable where that is the + established boundary. +- Git commit authors for this public-bound repo must use the GitHub noreply + email, never the user's real address. + +## Remote, Sync, And Runtime Model + +- Remote SSH projects are mount-free. Agent/terminal tabs run on the host over + SSH, file browsing/I/O goes over SFTP, and git runs on the host over SSH, all + using pooled ControlMaster/SFTP sessions in `services::remote`. +- Remoteness is explicit: use `services::remote::remote_target_for{,_dir}` and + host-aware variants. Do not infer it from path conventions. +- A remote project's `remote` is the primary host. Extra `compute_hosts` are + worker machines for experiments; each has its own pool entry, connection lamp, + and tab locality (`host:`). +- Worker sync is one-way code fan-out from the mirror to the worker for tracked + files only. It uses git bundle/reset, never `git clean`, and avoids the + bidirectional divergence/local-loss path. +- Shared-filesystem workers are the default in the add-machine UI. They see the + primary folder at their own `remote_path`; do not run git init/reset/fan-out on + them. Tabs simply `cd` into the shared folder. +- Git lockstep owns git-tracked files and moves commits/refs semantically via + bundles. Byte-sync owns everything else and moves raw bytes. Keep the + `drop_tracked` split intact so the two transports never race for one file. +- With lockstep on, a saved edit to a tracked file reaches the peer only after + it is committed. Do not "fix" this back into continuous byte mirroring. +- Byte-sync is opt-in per path from the explicit manifest. It does not read + `.gitignore`; preview and confirm large scopes before pulling big host trees. +- Local-loss warnings are file-backed, not events. Destructive background git or + sync moves must record through `services::local_loss` so losses surface after + relaunch. +- Passwords are never persisted by default. Saved SSH/OpenVPN credentials are + keyed by host/config target, not project id; a blank password can mean "use + saved credential". +- Remote auto-connect and VPN auto-connect must never prompt. Check the + silent-connect predicates before attempting them. +- OpenVPN tunnels are machine-wide. The header `VpnIndicator` owns visibility + and lifecycle across projects; project UI must not imply the tunnel is + project-scoped. +- Never elevate for a connect that cannot succeed silently. `pkexec` prompts + before OpenVPN validates config/credentials. +- Containerized projects use one session-lived Docker container per local + project and bind-mount the project at the identical absolute path. File + viewers/git/usage watchers keep reading host bytes. The toggle is local-only + and hidden/refused on unsupported platforms. + +## Tabs, Agents, And Restore + +- Shell/files tabs restore on relaunch. Claude and Codex agent tabs with a + `sessionId` are resumable and restored; Gemini and Vibe restore behavior is + more limited unless the relevant code explicitly says otherwise. +- Eldrun installs Claude/Codex session hooks and also has hook-free Codex + binding. Codex user hooks may need one-time trust via `/hooks`. +- Agent authority has three axes: project container sandbox, tab location + (local/primary/worker), and optional Plan/Auto agent mode. +- `components/tabs/agentModes.ts` is a capability table. Add an agent there only + if it has an absolute mode flag and a working resume path for the respawn that + mode switching causes. +- Plan/Auto is a launch flag, persisted per tab, and re-applied when args are + rebuilt from layout state. Do not persist raw args as the source of truth. + +## Frontend Notes + +- The right panel and Files (Project) tab share `ProjectFilesView`; keep file + viewer features in the shared component so surfaces do not drift. +- `ProjectFilesPane` owns the tree/sort/source mechanics; panel/tab hosts own + only identity, active state, browsed folder, and chrome slots. +- Remote/SFTP/git probes must be gated when disconnected; synchronous Tauri + commands against a dead session can freeze the window. +- GPU UI reports whole-device memory and optional sensors from `gpustat`, not + only Ollama model memory. Omit missing sensor readings; do not render fake + zeroes. +- File viewer parsers such as YAML and table editing are text-preserving views. + Keep edits surgical so comments, delimiters, quoting, and line endings survive. +- Python Run/Debug opens a terminal tab and asks the backend for interpreter + precedence. Do not duplicate interpreter-ranking logic in the frontend. +- Experimental features should use `useExperimental`; unset flags fall back to + debug mode. + +## Backend Notes + +- `commands/` expose Tauri command handlers; `services/` hold reusable runtime + logic; `schema/` mirrors persisted JSON. +- `services::remote` is the source of truth for SSH/SFTP pooling and host-aware + remote resolution. +- `services::worker_sync` is push-only source-to-worker code sync. Shared-FS + workers must be skipped defensively even if a command reaches the service. +- `services::git_peer` owns lockstep and must preserve the tracked/untracked + boundary with byte-sync. +- `services::openvpn` tracks both headless tunnels and interactive terminal + tunnels armed with Eldrun-owned pid files. +- `services::sandbox` owns Docker runtime lifecycle. Tab close must reap + in-container processes via the existing wrapper/pidfile mechanism. +- Terminal `kill`/`kill_all` must reap the child process subtree, not just the + shell leader. +- Remote GPU snapshots are parsed through the same local `gpustat` parsers so + host readings match local readings field-for-field. ## Verification -Before handing off code changes, run the checks that apply to the files you -changed: +Run checks that apply to the files changed before handing off: ```bash -npm run build +rtk npm run build ``` ```bash -cd src-tauri && cargo test +rtk cargo test --manifest-path src-tauri/Cargo.toml ``` -If `cargo` is unavailable in the environment, state that clearly in the final -handoff. For whitespace checks, also run: +For whitespace checks, also run: ```bash -git diff --check +rtk git diff --check +``` + +For frontend-only risky changes, also consider targeted Vitest/TypeScript +checks if faster than a full build. If `cargo` or another tool is unavailable, +state that clearly in the final handoff. + +Before every push, run the privacy/secret scan on staged changes and stop if it +reports anything real: + +```bash +rtk git add -A +rtk scripts/privacy-check.sh ``` Every push to GitHub should generate a new packaged artifact from -`.github/workflows/ci-cd.yml`; the local equivalent is `npm run package`, -which installs the packaged AppImage outside the checkout so branch switches -do not affect the running app. GitHub Releases are only published for -`v0..0` tags, so patch-only bumps like `0.1.1 -> 0.1.2` do not create -new releases. +`.github/workflows/ci-cd.yml`. The local equivalent is `npm run package`, which +installs the packaged AppImage outside the checkout so branch switches do not +affect the running app. + +Version bumping is automatic on push when `.githooks/` is enabled with +`git config core.hooksPath .githooks`: the pre-push hook patch-bumps +`package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json`, commits +that bump, and re-pushes. To bump minor/major, run +`scripts/bump-version.sh minor|major` and commit before pushing. + +GitHub Releases are cut manually only when a `v*` tag is pushed; ordinary branch +pushes do not publish releases. ## Running Locally -Human-only by default. Agents must not run these commands while an Eldrun -instance may already be active. +Human-only by default. Agents must not run these while an Eldrun instance may +already be active. Preferred Tauri launcher: @@ -107,3 +257,6 @@ Development run: ```bash npm run tauri dev ``` + +Useful keys: `F11` toggles fullscreen; `Super` toggles panels while Eldrun is +focused. diff --git a/CLAUDE.md b/CLAUDE.md index cdcbbaf..a34a38c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,8 +5,6 @@ development. It keeps a root control terminal, one terminal per active project, a bottom project switcher, a right-side file tree overlay, app launching, time tracking, and optional KDE/X11 workspace integration in one window. -Stack: Rust (Tauri 2), React 18, TypeScript, Zustand, xterm.js, Tailwind CSS. - ## Running Do not launch Eldrun from Claude or any other agent terminal for verification. @@ -21,164 +19,8 @@ Runtime launch commands are intentionally omitted from this Claude context. ## File Map -### Frontend (`src/`) - -Only the load-bearing files are listed; the tree is the source of truth. - -**Entry & shell** - -| File | Purpose | -|------|---------| -| `src/App.tsx` | Root component, theme injection, global key handlers. | -| `src/main.tsx` | React entry point. | -| `src/crashReporter.ts` | Captures/forwards WebKitGTK renderer crashes to the backend. | -| `src/types/index.ts` | Shared TypeScript types. | - -**Layout (`src/components/layout/`)** - -| File | Purpose | -|------|---------| -| `AppShell.tsx` | Top-level layout: header, center, right-panel wiring. | -| `HeaderBar.tsx` | Window drag handle + hosts the project switcher in the header. | -| `GlobalAppBar.tsx` | Global toolbar / app launcher (`GLOBAL_APP_ROLES`). | -| `GlobalAppMenu.tsx` | Context menu for a global-app toolbar button. | -| `CenterPanel.tsx` | Tab/subwindow tiling host; keeps all panes mounted across scope switches. | -| `DetachedCenterPanel.tsx` | Center-panel variant rendered inside a detached OS window. | -| `DetachedApp.tsx` | Root component for a popped-out/detached subwindow (#42). | -| `ProjectSwitcher.tsx` | Thin composition root: pill strip + search/dialog/settings wiring. Re-exports scaffold helpers. | -| `ProjectSearch.tsx` *(in `projects/`)* | Inactive-project/box search box + results popover. | -| `ProjectDialog.tsx` *(in `projects/`)* | New/Import project dialog incl. SSH + OpenVPN + scaffold-fill sub-flows. | -| `SettingsPanel.tsx` | Settings dialog + sub-panels (theme/git/layout, global apps, file-type apps, Ollama, shortcuts, help). | -| `RightPanel.tsx` | File-tree overlay panel (git status/history). | -| `VpnPasswordPrompt.tsx` | Modal prompting for an OpenVPN password on activation. | -| `LogoIcon.tsx` | Inline SVG logo. | - -**Projects, header widgets, tabs, terminal, files, embed, common** - -| File | Purpose | -|------|---------| -| `projects/ProjectPill.tsx` | Individual project pill (click/close/drag-reorder/group). | -| `projects/BoxPill.tsx` | Project-box pill (meta-grouping, #13/#41). | -| `projects/ActivityCalendar.tsx` | Per-project activity calendar heatmap. | -| `projects/scaffold.ts` | Pure helpers: name sanitize, SSH-address parse, scaffold/description fill prompts. | -| `header/Clock.tsx` | Header clock. | -| `header/AppTimerDisplay.tsx` | Active-project time-tracking readout. | -| `header/AppResourceDisplay.tsx` | Per-project CPU/resource readout. | -| `header/ConnTypeIcon.tsx` | Local/remote (SSH) connection-type icon. | -| `header/StatusLamp.tsx` | Status indicator lamp. | -| `header/WindowControls.tsx` | Minimize/maximize/close window buttons. | -| `tabs/TabBar.tsx` | Per-subwindow tab strip (add/rename/close, pointer-based DnD). | -| `tabs/Subwindow.tsx` | A single tiled subwindow (tab group). | -| `tabs/commitDrop.ts` / `tabs/commitFileDrop.ts` | Apply a tab/file drag-drop into the layout tree. | -| `tabs/dragGeometry.ts` | Drop-zone/split geometry math for tab drags. | -| `terminal/TerminalView.tsx` | xterm.js terminal wrapper + PTY I/O. | -| `files/FileTree.tsx` | Project file tree with git markers, fs-watch refresh. | -| `files/FileBrowser.tsx` | File browser pane. | -| `files/GitHistory.tsx` | Commit history / commit / push UI. | -| `files/SetDefaultAppDialog.tsx` | Pick the default app for a file type. | -| `embed/EmbedPane.tsx` | Hosts an embedded external app window. | -| `embed/FileViewerPane.tsx` | In-app viewers (PDF, image, markdown, code, TeX/SyncTeX). | -| `common/Dropdown.tsx`, `common/OrbitSpinner.tsx` | Shared primitives. | - -**Stores (`src/stores/`), hooks, lib** - -| File | Purpose | -|------|---------| -| `projects.ts` | Project list, active project, CRUD, `setActive`. | -| `tabs.ts` | Tab/subwindow layout tree per scope; tab persistence policy. | -| `boxes.ts` | Project boxes (meta-grouping) CRUD + membership. | -| `settings.ts` | App settings (theme, default agent, git profile, shortcuts, etc.). | -| `windows.ts` | Embedded app windows. | -| `detached.ts` | Detached/popped-out subwindow state (#42). | -| `drag.ts` | Isolated per-frame drag state (reference for fine-grained selectors). | -| `activity.ts` | PTY-output activity outside React (`lastOutputByPty`). | -| `timer.ts` | Per-project time-tracking state. | -| `linkRouting.ts` | Routing of clicked links/URIs to viewers or external apps. | -| `pdfSync.ts` | Bidirectional PDF/SyncTeX sync state. | -| `editorJump.ts` | Cross-pane jump-to-location requests. | -| `vpnPrompt.ts` | State backing `VpnPasswordPrompt`. | -| `hooks/useKeyboard.ts` | Global keyboard-shortcut hook. | -| `lib/shortcuts.ts` | Shortcut definitions, chord parsing/resolution. | -| `lib/viewers/{fileUtils,markdown,highlight,tex}.ts` | Pure viewer logic (XSS-safe markdown/highlight, TeX, file utils). | - -### Backend (`src-tauri/src/`) - -**Top level** - -| File | Purpose | -|------|---------| -| `main.rs` | Tauri app entry point, plugin registration. | -| `lib.rs` | Command registration (`generate_handler!`), app setup, hook/restore install. | -| `storage.rs` | JSON persistence helpers (read/write state files). | -| `paths.rs` | Canonical Eldrun directory paths. | -| `sysstat.rs` | Per-process CPU sampling via `/proc` (`descendant_pids`). | - -**Commands (`commands/`)** — Tauri command handlers exposed to the frontend. - -| File | Purpose | -|------|---------| -| `projects.rs` | Project CRUD, scaffold/import, time-today (god module; #1). | -| `fs.rs` | File-I/O commands (read/write/mtime, extracted from `projects.rs`; #1 seam). | -| `fs_watch.rs` | Filesystem watch start/stop + change events. | -| `git.rs` | Git status/history/commit/push. | -| `github.rs` | GitHub repo publishing. | -| `terminal.rs` | Terminal/PTY command surface (delegates to `terminal/mod.rs`). | -| `apps.rs` | App launching, `run_script_detached`, `open_file`, external window tracking. | -| `default_apps.rs` | Per-file-type default-app mapping. | -| `downloads.rs` | Per-project download routing. | -| `ssh.rs` | SSH commands for remote projects (`ssh_connect`, `ssh_default_dir`, `ssh_list_dir`, `ensure_project_mounted`). | -| `openvpn.rs` | OpenVPN tunnel connect/store-config commands. | -| `ollama.rs` | Ollama model list/pull/delete + local autocomplete. | -| `tex.rs` | TeX compile + SyncTeX (shell-escape defense). | -| `boxes.rs` | Project-box CRUD. | -| `subwindow.rs` | Detached/popped-out subwindow lifecycle (#42). | -| `timer.rs` | Time-tracking commands. | -| `workspace.rs` | KDE/X11 workspace switch commands. | -| `settings.rs` | Settings read/update. | -| `project_runtime.rs` | Project-switch runtime command wrapper (off-UI-thread). | -| `crash.rs` | Receives frontend renderer crash reports. | -| `debug.rs` | Debug-mode helpers. | - -**Services (`services/`)** — `AppHandle`-free, unit-testable. - -| File | Purpose | -|------|---------| -| `ssh_mount.rs` | sshfs mount lifecycle (mount/unmount, mountpoint derivation, arg validation). | -| `ssh_exec.rs` | Remote command execution over SSH (ControlMaster). | -| `remote_agents.rs` | Remote agent bootstrap/resume for SSH projects. | -| `openvpn.rs` | OpenVPN process lifecycle (askpass file, teardown). | -| `agent_session.rs` | SessionStart hook installer + live-session recording for agent resume. | -| `project_runtime.rs` | Worker-thread project switch + time flush (`flush_project_secs`). | -| `restore_service.rs` | Tab/session restore on relaunch. | -| `terminal_service.rs` | Tab layout save/restore for terminals. | -| `window_service.rs` | Window-state helpers. | - -**Platform (`platform/`)** — `WorkspaceBackend` strategy. - -| File | Purpose | -|------|---------| -| `x11.rs` | X11 workspace / window management via xlib. | -| `wayland_kde.rs` | KDE Wayland backend via KWin scripting + DBus (show/hide stub, #18). | -| `windows.rs` | Windows backend (stub). | -| `null.rs` | No-op platform fallback. | - -**Schema (`schema/`)** — Serde structs mirroring the JSON state files. - -| File | Purpose | -|------|---------| -| `projects.rs` / `project.rs` | `projects.json` entries + per-project `project.json`. | -| `settings.rs` | `settings.json`. | -| `default_apps.rs` | `default_apps.json`. | -| `time_log.rs` | `time_log.json` (unbounded `Vec`; Efficiency #2/#12). | -| `boxes.rs` | Project boxes. | -| `session.rs` | Live/restorable session state. | -| `active_session.rs` | Defined but **not yet wired** (TODO Group F #24). | - -**Terminal (`terminal/`)** - -| File | Purpose | -|------|---------| -| `mod.rs` | PTY lifecycle + agent-session resolvers (`resolve_{claude,codex}_session`). | +Frontend file map: `src/CLAUDE.md`. Backend file map: `src-tauri/CLAUDE.md`. +Both list only the load-bearing files; the tree is the source of truth. ## Persistence @@ -187,28 +29,6 @@ Only the load-bearing files are listed; the tree is the source of truth. - Global Eldrun state lives in `~/.local/share/eldrun/`: `projects.json`, `settings.json`, `default_apps.json`, `time_log.json`, and `active_session.json`. -- Remote (SSH) projects are sshfs-mounted under - `~/.local/share/eldrun/mounts//`; that mountpoint becomes the - project's `directory`. Such projects carry a `remote` spec (`user?`, `host`, - `port?`, `remote_path`) in their `project.json` and mirrored into the - `projects.json` entry's `extra`. Requires `sshfs`/FUSE locally. -- Project-local state lives in each project's `project.json`. This includes the - per-project tab layout (`tab_layout`/`tab_groups`). Shell/files tabs are always - restored on relaunch; agent tabs are normally dropped, **except resumable agent - tabs** — Claude and Codex tabs that carry a `sessionId` are persisted (with - their `sessionId`) and restored, respawning the agent so the prior conversation - comes back (see `isRestorableTab`/`RESUMABLE_AGENTS` in `src/stores/tabs.ts`). - Mechanism (`services/agent_session.rs`, installed at startup): Eldrun installs a - `SessionStart` hook — into `~/.claude/settings.json` (JSON) and - `~/.codex/config.toml` (TOML text-append) — that records each tab's live - `session_id` under `~/.local/share/eldrun/live_sessions/`, keyed by the - `ELDRUN_TAB_UID` env var Eldrun sets on the agent. At spawn, - `terminal::resolve_{claude,codex}_session` reads that to resume the *current* - session, following a `/clear`. For Claude the key is its launch id - (`--session-id`); Codex mints its own id so the key is a separate per-tab uuid - and the backend injects `codex resume `. **Codex caveat:** user-level - Codex hooks need a one-time trust (`/hooks` in Codex) before they run. Gemini - and Vibe are still dropped (TODO 39d). - New/imported projects receive `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.claude/settings.json`, `.gitignore`, `TODO.md`, `ROADMAP.md`, `STATUS.md`, and `README.md` when missing. @@ -216,22 +36,36 @@ Only the load-bearing files are listed; the tree is the source of truth. matching group, create a new group if no current group fits, or merge groups if the TODO depends on distinct areas that should be tracked together. +### Topic docs (read only when the task touches them) + +Each `docs/context/*.md` file below holds the full design rationale for one +subsystem — load-bearing detail on *why* it works the way it does, not +discoverable from the code alone. Don't read these speculatively; open the one +matching the area you're touching. + +- **Usage stats** — local-only rolling counters behind the daily recap; what's counted where and why. `docs/context/usage_stats.md` +- **Remote projects** — SSH/SFTP-native, mount-free: how files/git/terminals work with no sshfs. `docs/context/remote_projects.md` +- **Git lockstep + byte-sync** — the two transports keeping a remote mirror in step, local-loss warnings, lockstep's safe-default rules, byte-sync's opt-in scope and big-folder census. `docs/context/git_sync.md` +- **Remote credentials & host security** — locked-keychain handling, password persistence opt-in, SSH_ASKPASS argv hardening, first-contact host-key confirmation. `docs/context/remote_credentials.md` +- **Remote auto-connect** — when a remote project connects itself on launch/activation, headless vs. non-headless behavior, VPN-needed probing. `docs/context/remote_autoconnect.md` +- **OpenVPN tunnel** — machine-wide (not project-scoped) lifecycle, connect-on-launch, pre-flight silent-connect check, single-polkit-prompt teardown. `docs/context/openvpn.md` +- **Agent session persistence** — how resumable Claude/Codex tabs survive relaunch via the SessionStart hook mechanism. `docs/context/agent_sessions.md` +- **Multi-host remote (compute hosts)** — a remote project's worker machines: push-only code sync, read-only files, pull-outputs, shared-filesystem mode. `docs/context/multi_host_remote.md` +- **tmux session persistence** — shell/script tabs surviving SSH drops and Eldrun crashes; the Sessions view. `docs/context/tmux_sessions.md` +- **Docker project containers** — per-project session container, toggle semantics, lifecycle. `docs/context/docker_containers.md` +- **Agent authority axes** — sandbox / tab location / agentMode (Plan vs Auto) and how they compose. `docs/context/agent_authority.md` +- **Careful mode on HPC hosts** — what the monitor/usage probes stop collecting on a cluster login node and why (usage rules, login-node load), how a host is classified. `docs/context/hpc_careful_mode.md` + ## Dev Workflow 1. Edit files under `src/` (frontend) or `src-tauri/src/` (backend). -2. Type-check frontend: - -```bash -npx tsc --noEmit -``` - -3. Run Rust tests: +2. Run Rust tests: ```bash cargo test --manifest-path src-tauri/Cargo.toml ``` -4. **Privacy check before every push.** This repo is intended to go public, so +3. **Privacy check before every push.** This repo is intended to go public, so before pushing run the privacy/secret scan on the staged changes and stop if it reports anything real: @@ -244,15 +78,24 @@ cargo test --manifest-path src-tauri/Cargo.toml this doc nor the script hardcodes or self-matches the literals it catches). Commits must use the GitHub `noreply` author email, never the real address. -5. Every push to GitHub should produce a fresh packaged artifact from the +4. Every push to GitHub should produce a fresh packaged artifact from the workflow in `.github/workflows/ci-cd.yml`; use `npm run package` locally if you need to install the same release build under `~/.local/share/eldrun/`. - GitHub Releases are only published for `v0..0` tags, so patch-only - bumps like `0.1.1 -> 0.1.2` do not create a release. -6. Do not start Eldrun from Claude. Frontend (`src/`) edits hot-reload in the - running instance — no restart needed. Only ask the user to rebuild/restart - for backend (`src-tauri/`) changes. + **Version bumping is automatic on push.** The tracked `pre-push` hook in + `.githooks/` auto-bumps the patch version across `package.json`, + `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` (via + `scripts/bump-version.sh`), commits it, and re-pushes so every push carries a + distinct version. Enable it once per clone with + `git config core.hooksPath .githooks` (`core.hooksPath` is not itself tracked). + To bump minor/major instead, run `scripts/bump-version.sh minor|major` and + commit before pushing (the hook only patch-bumps when the version is otherwise + unchanged for that push). + + **Releases are cut manually.** A GitHub Release is published only when a `v*` + tag is pushed (e.g. `v0.1.5`) — the `release` job is gated on `refs/tags/v*`, + so ordinary branch pushes never publish. The hook deliberately skips tag + pushes, so to ship a release: `git tag v && git push origin v`. Useful keys: `F11` toggles fullscreen; `Super` toggles panels while Eldrun is focused. diff --git a/Cargo.lock b/Cargo.lock index 7db2a48..ea0d79a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -47,6 +58,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.6.1" @@ -64,9 +84,19 @@ dependencies = [ "parking_lot", "percent-encoding", "windows-sys 0.60.2", + "wl-clipboard-rs", "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -232,6 +262,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atoi_simd" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad17c7c205c2c28b527b9845eeb91cf1b4d008b438f98ce0e628227a822758e" +dependencies = [ + "debug_unsafe", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -244,6 +283,22 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "awaitable" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70af449c9a763cb655c6a1e5338b42d99c67190824ff90658c1e30be844c0775" +dependencies = [ + "awaitable-error", + "cfg-if", +] + +[[package]] +name = "awaitable-error" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b3469636cdf8543cceab175efca534471f36eee12fb8374aba00eb5e7e7f8a" + [[package]] name = "base64" version = "0.21.7" @@ -295,6 +350,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -416,6 +480,23 @@ dependencies = [ "system-deps", ] +[[package]] +name = "calamine" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8822fe6253ca47aa5ad9a3be09f6fe7cd20c6a74e41b0aa42e8f4e3d523508df" +dependencies = [ + "atoi_simd", + "byteorder", + "codepage", + "encoding_rs", + "fast-float2", + "log", + "quick-xml 0.39.4", + "serde", + "zip 7.2.0", +] + [[package]] name = "camino" version = "1.2.2" @@ -458,6 +539,15 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.64" @@ -525,6 +615,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -534,6 +634,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "codepage" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f68d061bc2828ae826206326e61251aca94c1e4a5305cf52d9138639c918b4" +dependencies = [ + "encoding_rs", +] + [[package]] name = "combine" version = "4.6.7" @@ -553,6 +662,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "concurrent_arena" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07f0a549fe58f8477a15f0f1c3aa8ced03a3cdeaa38a661530572f21ea963a0" +dependencies = [ + "arc-swap", + "parking_lot", + "triomphe", +] + [[package]] name = "cookie" version = "0.18.1" @@ -563,6 +683,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -579,6 +709,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + [[package]] name = "core-graphics" version = "0.25.0" @@ -586,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -599,7 +742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -736,6 +879,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "sha2", + "zeroize", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + [[package]] name = "deranged" version = "0.5.8" @@ -745,6 +912,28 @@ 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_destructure2" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -774,6 +963,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -851,7 +1041,7 @@ checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ "bit-set", "cssparser", - "foldhash", + "foldhash 0.2.0", "html5ever", "precomputed-hash", "selectors", @@ -873,6 +1063,28 @@ dependencies = [ "serde", ] +[[package]] +name = "drag" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e90b4a25ace5ce0561534b073943594cbcd21af936e64d09aec444568411f8c" +dependencies = [ + "core-graphics 0.24.0", + "dunce", + "gdk", + "gdkx11", + "gtk", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "raw-window-handle", + "serde", + "thiserror 2.0.18", + "windows 0.52.0", + "windows-core 0.58.0", +] + [[package]] name = "dtoa" version = "1.0.11" @@ -917,27 +1129,42 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "eldrun" -version = "0.1.0" +version = "0.1.40" dependencies = [ "arboard", + "base64 0.22.1", + "bytes", + "calamine", + "dbus-secret-service", + "futures-util", + "gtk", "infer", + "keyring", "libc", "mime_guess", "notify", "opener", + "openssh-sftp-client", + "plist", "png 0.17.16", "portable-pty", + "rusqlite", "serde", "serde_json", + "serde_yaml", + "starship-battery", "tauri", "tauri-build", "tauri-plugin-dialog", + "tauri-plugin-drag", + "tauri-plugin-notification", "tempfile", "tokio", "webkit2gtk", - "windows", + "windows 0.61.3", "xcb", - "zbus", + "zbus 4.4.0", + "zip 2.4.2", ] [[package]] @@ -960,6 +1187,15 @@ 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" @@ -1041,6 +1277,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + [[package]] name = "fastrand" version = "2.4.1" @@ -1089,6 +1343,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -1097,6 +1357,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1105,6 +1366,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1558,11 +1825,41 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5081f264ed7adee96ea4b4778b6bb9da0a7228b084587aa3bd3ff05da7c5a3b" +dependencies = [ + "hashbrown 0.17.1", +] [[package]] name = "heck" @@ -1588,6 +1885,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.38.0" @@ -1889,6 +2204,16 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2012,6 +2337,23 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "linux-keyutils", + "log", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "kqueue" version = "1.2.0" @@ -2038,6 +2380,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2096,6 +2444,27 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags 2.13.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2124,21 +2493,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] -name = "markup5ever" -version = "0.38.0" +name = "mac-notification-sys" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" dependencies = [ + "cc", "log", - "tendril", - "web_atoms", + "objc2", + "objc2-foundation", + "time", + "uuid", ] [[package]] -name = "memchr" -version = "2.8.2" +name = "mach2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[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" @@ -2273,6 +2665,27 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "normpath" version = "1.5.1" @@ -2300,6 +2713,20 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus 5.17.0", +] + [[package]] name = "notify-types" version = "2.1.0" @@ -2309,12 +2736,87 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2364,10 +2866,17 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", "objc2-foundation", + "objc2-quartz-core", ] [[package]] @@ -2387,6 +2896,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -2447,6 +2957,19 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -2560,6 +3083,81 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "openssh-sftp-client" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a575af2d0eaa7c8f96368924994a3ae1d484fd2b19750cc1049c1100a42e82" +dependencies = [ + "bytes", + "derive_destructure2", + "futures-core", + "once_cell", + "openssh-sftp-client-lowlevel", + "openssh-sftp-error", + "pin-project", + "scopeguard", + "tokio", + "tokio-io-utility", + "tokio-util", +] + +[[package]] +name = "openssh-sftp-client-lowlevel" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d1a0e0eeb46100745a2c383c842042e1f04aa57a9c18aa41a16b6d4d58aeb0" +dependencies = [ + "awaitable", + "bytes", + "concurrent_arena", + "derive_destructure2", + "openssh-sftp-error", + "openssh-sftp-protocol", + "pin-project", + "tokio", + "tokio-io-utility", +] + +[[package]] +name = "openssh-sftp-error" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a702f18f0595b4578b21fd120ae7aa45f4298a8b28ddcb2397ace6f5a8251a" +dependencies = [ + "awaitable-error", + "openssh-sftp-protocol-error", + "ssh_format_error", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "openssh-sftp-protocol" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c862e0c56553146306507f55958c11ff554e02c46de287e6976e50d815b350" +dependencies = [ + "bitflags 2.13.0", + "num-derive", + "num-traits", + "openssh-sftp-protocol-error", + "serde", + "ssh_format", + "vec-strings", +] + +[[package]] +name = "openssh-sftp-protocol-error" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b54df62ccfd9a7708a83a9d60c46293837e478f9f4c0829360dcfa60ede8d2" +dependencies = [ + "serde", + "thiserror 2.0.18", + "vec-strings", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2576,6 +3174,16 @@ dependencies = [ "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 = "pango" version = "0.18.3" @@ -2636,6 +3244,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + [[package]] name = "phf" version = "0.13.1" @@ -2689,6 +3308,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2714,13 +3353,13 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.14.0", - "quick-xml 0.39.4", + "quick-xml 0.38.4", "serde", "time", ] @@ -2899,12 +3538,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ + "encoding_rs", "memchr", ] @@ -2936,8 +3585,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2947,7 +3606,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2959,6 +3628,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3092,6 +3770,31 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + +[[package]] +name = "rusqlite" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323" +dependencies = [ + "bitflags 2.13.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3126,6 +3829,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -3193,37 +3902,92 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "selectors" -version = "0.36.1" +name = "secret-service" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" dependencies = [ - "bitflags 2.13.0", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf", - "phf_codegen", - "precomputed-hash", - "rustc-hash", - "servo_arc", - "smallvec", + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.6", + "serde", + "sha2", + "zbus 4.4.0", ] [[package]] -name = "semver" -version = "1.0.28" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "serde", - "serde_core", + "bitflags 2.13.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "serde" -version = "1.0.228" +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 0.10.1", + "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 = [ @@ -3348,6 +4112,19 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial2" version = "0.2.37" @@ -3526,12 +4303,62 @@ dependencies = [ "system-deps", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "ssh_format" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ab31081d1c9097c327ec23550858cb5ffb4af6b866c1ef4d728455f01f3304" +dependencies = [ + "bytes", + "serde", + "ssh_format_error", +] + +[[package]] +name = "ssh_format_error" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be3c6519de7ca611f71ef7e8a56eb57aa1c818fecb5242d0a0f39c83776c210c" +dependencies = [ + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "starship-battery" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0efc2c44c92705be724265a0c758e3b7c120ea63817d2d684bab86fbeced9a" +dependencies = [ + "cfg-if", + "core-foundation 0.10.1", + "lazycell", + "libc", + "mach2", + "nix 0.30.1", + "num-traits", + "plist", + "uom", + "windows-sys 0.61.2", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3568,6 +4395,12 @@ 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" @@ -3641,8 +4474,8 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ "bitflags 2.13.0", "block2", - "core-foundation", - "core-graphics", + "core-foundation 0.10.1", + "core-graphics 0.25.0", "crossbeam-channel", "dbus", "dispatch2", @@ -3667,7 +4500,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -3738,7 +4571,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -3837,6 +4670,21 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-drag" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729ca0ce4b1169869d3405216d3c09a524f41ea5e2eec89f917cd6623f8a70ca" +dependencies = [ + "base64 0.22.1", + "drag", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-plugin-fs" version = "2.5.1" @@ -3861,6 +4709,25 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-notification" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" +dependencies = [ + "log", + "notify-rust", + "rand 0.9.5", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "time", + "url", +] + [[package]] name = "tauri-runtime" version = "2.11.3" @@ -3883,7 +4750,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -3908,7 +4775,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -3961,6 +4828,17 @@ dependencies = [ "toml 1.1.2+spec-1.1.0", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" +dependencies = [ + "thiserror 2.0.18", + "windows 0.61.3", + "windows-version", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -3984,6 +4862,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + [[package]] name = "thiserror" version = "1.0.69" @@ -4110,6 +4994,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tokio-io-utility" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d672654d175710e52c7c41f6aec77c62b3c0954e2a7ebce9049d1e94ed7c263" +dependencies = [ + "bytes", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.7.0" @@ -4352,12 +5246,40 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom", + "petgraph", +] + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -4440,6 +5362,22 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "uom" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd5cfe7d84f6774726717f358a37f5bca8fca273bed4de40604ad129d1107b49" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "url" version = "2.5.8" @@ -4489,6 +5427,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec-strings" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8509489e2a7ee219522238ad45fd370bec6808811ac15ac6b07453804e77659" +dependencies = [ + "serde", + "thin-vec", +] + [[package]] name = "version-compare" version = "0.2.1" @@ -4623,6 +5577,76 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.13.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.4", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.102" @@ -4697,10 +5721,10 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", ] [[package]] @@ -4721,7 +5745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -4777,6 +5801,18 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-implement 0.52.0", + "windows-interface 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -4799,14 +5835,36 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[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-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -4818,8 +5876,8 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", @@ -4836,6 +5894,28 @@ dependencies = [ "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -4847,6 +5927,28 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "windows-interface" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -4880,6 +5982,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4898,6 +6009,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -5214,6 +6335,24 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix", + "thiserror 2.0.18", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "writeable" version = "0.6.3" @@ -5258,7 +6397,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -5370,7 +6509,7 @@ dependencies = [ "hex", "nix 0.29.0", "ordered-stream", - "rand", + "rand 0.8.6", "serde", "serde_repr", "sha1", @@ -5379,9 +6518,44 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28b97f866896a4be7aefd2b5a8e01bb6773d19a775d54ab28b4d094b9a4480e" +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 5.17.0", + "zbus_names 4.3.3", + "zvariant 5.13.0", ] [[package]] @@ -5394,7 +6568,22 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.118", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e05ad887425eecf5e8384dc2406a4a9313eb73468712fc1cdea362eb4fe0469" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zbus_names 4.3.3", + "zvariant 5.13.0", + "zvariant_utils 3.5.0", ] [[package]] @@ -5405,7 +6594,18 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039ca249fee9559680f3a9f05b55e0761fee51af4f6c1e7d8c1f31e549721d2" +dependencies = [ + "serde", + "winnow 1.0.3", + "zvariant 5.13.0", ] [[package]] @@ -5449,6 +6649,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "zerotrie" version = "0.2.4" @@ -5482,12 +6702,61 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.14.0", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "flate2", + "indexmap 2.14.0", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zune-core" version = "0.5.1" @@ -5513,7 +6782,21 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cf057bb00bf5c9ad77abb6147b0ca4818236a1858416e9d988e40d6322fefa7" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.3", + "zvariant_derive 5.13.0", + "zvariant_utils 3.5.0", ] [[package]] @@ -5526,7 +6809,20 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.118", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8118ca6bda77bfc0ab51d660db0c955f2505eef854c9a449435bccb616933b31" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zvariant_utils 3.5.0", ] [[package]] @@ -5539,3 +6835,16 @@ dependencies = [ "quote", "syn 2.0.118", ] + +[[package]] +name = "zvariant_utils" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.118", + "winnow 1.0.3", +] diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index c75c56c..c82cb63 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -234,7 +234,7 @@ Contents: `@dnd-kit/sortable`. Hovering a pill shows a tooltip with the project path, status, and today's active time (from `get_time_today`). Clicking switches to the project; the × button closes it. The pill's menu also exposes **Publish to - GitHub** (see below). + GitHub / GitLab** (see below). - **Box pills** — `BoxPill.tsx` renders a project box as a single project-style pill (`.project-pill.is-box`) with a member-count badge. Dropping a project pill onto a box (same `PILL_DRAG_TYPE` as pill reorder) assigns it to the box; @@ -245,14 +245,20 @@ Contents: - **+ button** — opens an add-project menu with "New project" and "Import project" sub-options. -**Publish to GitHub.** `publishProject` (`stores/projects.ts`) invokes -`github_publish` (`commands/github.rs`), which runs `gh repo create --- --source=. --remote=origin --push` via the system `gh` CLI. For a -work-remote (SSH) project the `gh` call runs over `ssh` on the host where the -repo lives (`BatchMode`, validated argv, single-quoted remote path). On success -it records the new push target — `git_type` becomes `remote-public` or -`remote-private` in both `projects.json` and the project's `project.json` — and -returns `gh`'s stdout (the repo URL). Requires `gh` installed and authenticated +**Publish to GitHub / GitLab.** `publishProject` (`stores/projects.ts`) invokes +`publish_project` (`commands/git_publish.rs`) with a `provider` (`github` / +`gitlab`) and `visibility`. For GitHub it runs `gh repo create +-- --source=. --remote=origin --push`; for GitLab it runs `glab repo +create -- --remoteName origin` followed by an explicit `git +push -u origin HEAD` (since `glab` has no `--source/--push`), authenticating the +push with the effective token via an ephemeral inline git credential helper. For a +work-remote (SSH) project the CLI call runs over `ssh` on the host where the repo +lives (`BatchMode`, validated argv, single-quoted remote path), relying on that +host's own `gh`/`glab` auth. On success it records the new push target — +`git_type` becomes `remote-public` or `remote-private`, and `git_provider` the +chosen provider, in both `projects.json` and the project's `project.json` — and +returns the CLI's stdout (the repo URL). Requires the chosen provider's CLI (`gh` +or `glab`) installed and authenticated, or a token under Settings → Git hosting (locally, or on the remote host for remote projects). Settings dialog covers: default agent command, theme (Dark/Bright/Fancy @@ -598,8 +604,19 @@ session and removed. ### Startup -1. `AppShell` mounts; loads settings and projects from the backend. -2. `getCurrentWindow().setFullscreen(true)` is called after the window is ready. +1. The main window is created hidden (`"visible": false` in `tauri.conf.json`). + The backend's `restore_main_window` (`lib.rs`) reapplies the geometry saved in + `settings.window_state` — monitor, position, size, maximized — then shows it, + so the window never visibly jumps between monitors. If nothing is saved, or the + saved rect no longer fits any connected monitor (an unplugged external display), + it falls back to opening maximized wherever the WM puts it. Which rect is + placeable is decided by `services::window_state::resolve_startup_geometry`. +2. `AppShell` mounts; loads settings and projects from the backend. On macOS only, + `getCurrentWindow().setFullscreen(true)` is called after the window is ready + (Linux must never enter fullscreen: a `_NET_WM_STATE_FULLSCREEN` window is + unmovable under KWin). It then listens for window moves/resizes and writes the + geometry back to `settings.window_state` on a 300 ms debounce, plus once more on + close. 3. Projects marked `current` or `active` appear as project-switcher pills. 4. The project marked `current` is the initial active scope; if none, root. 5. Workspace management (if enabled) allocates desktops for visible projects. @@ -739,15 +756,18 @@ test skips itself when no local Ollama server or model is available. layout on restart rather than respawning as separate OS windows. - Project-box scopes are session-only: a box's tabs are dropped on project switch / restart. Renaming a box does not move its already-created folder. -- `github_publish` requires the `gh` CLI installed and authenticated (on the - remote host for work-remote projects); it does not manage GitHub auth itself. +- `publish_project` requires the chosen provider's CLI — `gh` (GitHub) or `glab` + (GitLab) — installed and authenticated (on the remote host for work-remote + projects); it does not manage provider auth itself beyond an optional token. - Ollama model installation and update depend on network access to the Ollama registry and may take minutes for large models. - `ensure_ollama_running` can start a system service only when the current user has permission to do so; otherwise it falls back to a user `ollama serve` process. -- Open-app restore uses a best-effort relaunch model; window geometry and focus - order are not restored. +- Open-app restore uses a best-effort relaunch model; the geometry and focus + order of *externally launched app* windows are not restored. (Eldrun's own main + window does restore its monitor, position, size, and maximized state — see + Startup below.) - Network status depends on reaching Cloudflare DNS; may show offline on networks that block direct TCP/53. - Download routing browser preference edits assume the browser is not running. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..6f94484 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Eldrun Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 100% rename from LICENSE rename to LICENSE-MIT diff --git a/README.md b/README.md index 1a5ecbd..2ddb891 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,112 @@ +![Eldrun logo](src/assets/logo-white.svg) + # Eldrun -**The problem:** when you develop several projects at once, every project's -windows — browsers, terminals, file managers, docs, agents — pile up on one -desktop. Switching from project A to project B means hunting through dozens of -windows for the handful that belong to where you're going, and losing the rest -in the noise. - -**Eldrun's model:** you don't open applications, you open projects. Selecting a -project swaps the whole desktop to that project's context — its windows come -forward, the previous project's windows are parked out of the way, the -downloads folder and default-app mappings re-route, and time tracking switches. -One project visible at a time, everything else cleanly out of sight. - -Inside each project, Eldrun is an operational cockpit: a root control terminal -for managing the workspace, one or more agent terminals scoped to the project -(Claude, Codex, Gemini, or a local Ollama model), a persistent project bar, a -hover-revealed file panel, and cross-project app controls that stay available as -you move between projects. It is built with Tauri 2 + React + TypeScript, with -optional KDE/X11 workspace integration. +**A project-centric desktop layer that swaps your entire working context — windows, files, apps, Git state, layout, and AI agent terminals — as a single unit when you switch projects, and runs any of those projects on a remote machine or HPC cluster as if it were sitting on your laptop.** + +[![CI](https://github.com/fseiffarth/ProjectEldrun/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/fseiffarth/ProjectEldrun/actions/workflows/ci-cd.yml) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](#license) +[![Release](https://img.shields.io/github/v/release/fseiffarth/ProjectEldrun)](https://github.com/fseiffarth/ProjectEldrun/releases) +![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-blue) +![Tauri](https://img.shields.io/badge/Tauri-2-24C8DB?logo=tauri) + + +> **You don't open applications — you open projects.** +> **And you don't move to the machine your work runs on — the project takes it +> with you.** + +Eldrun stands on **two pillars**. + +**One project = one desktop.** Eldrun is a project-centric desktop layer, not +just an app that launches or embeds other apps: projects own their windows and +desktop context, and selecting a project swaps that whole context — windows, +files, apps, Git state, and layout — as a single unit. The AI agent terminals, +file viewers, and app launcher ride on top, living *inside* a project once its +desktop is restored. + +**One project = any machine.** A project is not tied to the computer in front of +you. Point it at an SSH host — or extend an existing local project onto one — and +its agent tabs, shells, Python runs, and jobs execute *there*, while the file +tree, viewers, and Git views keep working exactly as they do locally. No sshfs or +FUSE mount is involved, a project can span several machines at once, long runs +survive an SSH drop or a laptop lid, and SLURM clusters are driven from the same +cockpit — submit, watch, cancel, and grab an interactive compute node without +memorizing the commands. The goal is that *running a project on a cluster costs +about as much ceremony as running it locally*. See +[Remote machines & HPC clusters](#remote-machines--hpc-clusters-the-second-differentiator). + +Built with **Tauri 2 + React + TypeScript**. Linux (X11 / KDE Wayland) and +Windows both get native workspace, app-launch, default-app, and download +integration today; macOS runs as a shell with a no-op workspace backend (on the +roadmap). + +--- + +## Why Eldrun + +When you juggle several projects at once, every project's windows — browsers, +terminals, file managers, docs, agents — pile onto one desktop. Switching from +project A to project B means digging through dozens of windows for the handful +that belong where you're going, and losing the rest in the noise. + +Eldrun flips the model. **Select a project, and the desktop becomes that +project:** its windows come forward, the previous project's windows park out of +the way, the downloads folder and default-app mappings re-route, and time +tracking switches. One project visible at a time, everything else cleanly out of +sight. + +Inside a project, Eldrun is an operational cockpit — a root control terminal for +the workspace, agent terminals scoped to the project (Claude, Codex, Gemini, or +a local Ollama model), a tiling tab layout, a hover-revealed file panel with +built-in viewers, and cross-project app controls that follow you between +projects. + +**And the same friction exists one layer down: your work rarely runs where you +sit.** The heavy half of a project belongs on a server, a GPU box, or an HPC +cluster, and the usual answer is a second, worse workflow — a bare `ssh` window, +`scp`/`rsync` by hand, `vim` instead of your editor, tmux discipline you have to +remember, and SLURM incantations you look up every time. Eldrun makes the remote +machine a *property of the project* instead of a separate way of working: you +open the project, and its terminals, agents, and jobs are already on the right +host, its files are already in the file tree, and its Git history is already in +sync — with no mount, and no second toolchain to keep in your head. ## Vision -Eldrun is a project-centric desktop layer, not just an app that launches or -embeds other apps. The user-facing model is: - -> Select a project → Eldrun restores that project's working context. +> Select a project → Eldrun restores its complete working context. -The core product is the window/workspace layer: projects own their windows and -desktop context, and switching projects swaps that context as one unit. The -agent terminals, file panel, and app launcher ride on top of that layer — they -are what lives inside a project once its desktop is restored. That context -should eventually include terminals, files, apps, windows, Git state, notes, -AI/task metadata, layout, and workflow state. +A project's context already spans terminals, files, apps, windows, Git state, +layout — **and the machines it runs on**; the direction of travel adds notes, +AI/task metadata, and workflow state, so a project carries everything it needs to +be resumed exactly where you left it, whether that is on this laptop or on a +login node three networks away. -The current implementation is focused on Linux (X11 and KDE Wayland) because -that provides the fastest path to reliable window control. The longer-term -direction is a stable Eldrun core with desktop/compositor backends for Cinnamon -X11, KDE/KWin, Hyprland, GNOME Shell, i3, Sway, and other Wayland environments. +The implementation runs natively on **Linux (X11 and KDE Wayland)** and +**Windows** today — both with real per-project window parking — and the design is +cross-platform by intent. The long-term shape is a stable Eldrun core behind +pluggable compositor/window backends (X11, KDE/KWin, Hyprland, GNOME Shell, i3, +Sway, and other Wayland environments; the Win32 backend on Windows), native macOS +support, and eventually an Eldrun-native compositor for full control of projects, +windows, and layout. See [VISION.md](docs/VISION.md) for the full strategy and platform rationale. +## At a glance + +![Eldrun functionality map](screenshots/eldrun-functionality.svg) + +**①** pick a project and the desktop swaps to it. **②** inside, a tiling tab +layout hosts agent terminals, shells, and native file viewers. **③** the +project-desktop layer (window parking, downloads, default apps, time tracking) +follows the active project automatically, with the right panel (Files · Git · +Search) and the global app toolbar alongside. **④** and the project carries the +machines it runs on: its tabs, jobs, and files can live on an SSH host, a GPU +box, or an HPC cluster without changing how any of the above works. + +And here's how that looks in the running app: + +![Current Eldrun screen](screenshots/eldrun-current.png) + ## How Eldrun compares Agent orchestrators (Vibe Kanban, Conductor, Claude Squad, the Claude Code @@ -53,33 +120,50 @@ desktop per project handle windows but have no project model and no restore; tmux and scripts like `workon` restore terminal layouts but ignore everything outside the terminal. +Nor do any of them cross machines. Remote development tooling (VS Code Remote, +JupyterHub, a hand-rolled `ssh` + `rsync` + tmux setup) attaches *one editor* to +*one host*; the cluster half — workspaces on the parallel filesystem, `sbatch` +and `squeue`, an `srun` shell on a compute node, keeping several machines in +step — stays a terminal exercise you repeat per project. + Eldrun occupies the gap none of them fill: project ownership of *windows and -desktop context*, with agent terminals built in. It is complementary to the +desktop context*, and project ownership of *the machines the work runs on*, with +agent terminals built in on both. It is complementary to the task orchestrators rather than a replacement — you can run one inside an Eldrun project terminal for parallel task delegation while Eldrun handles switching the desktop between projects. -![Current Eldrun screen](screenshots/eldrun-current.png) - ## Stack - **Frontend:** React 18, TypeScript, Vite, Tailwind CSS, Zustand - **Terminal UI:** xterm.js (`@xterm/xterm`, `@xterm/addon-fit`, `@xterm/addon-web-links`) - **Backend:** Rust, Tauri v2 - **PTY:** `portable-pty` crate -- **Workspace:** `zbus` (DBus) and `xcb` (X11) — Linux only +- **Workspace:** `zbus` (DBus) and `xcb` (X11) on Linux; the Win32 API + (`windows` crate — `SW_HIDE`/`SW_SHOW`, `EnumWindows`, virtual-desktop manager, + shell-link/icon resolution) on Windows + +## Download + +Prebuilt packages are published on the +[Releases page](https://github.com/fseiffarth/ProjectEldrun/releases). From the +[latest release](https://github.com/fseiffarth/ProjectEldrun/releases/latest), +grab the `.AppImage` (portable Linux) or `.deb` (Debian/Ubuntu), or the `.exe` +installer on Windows. To build from source instead, follow the requirements +below. ## Requirements -- Linux desktop (X11 or KDE Wayland) +- Linux desktop (X11 or KDE Wayland) **or** Windows 10/11 - Rust toolchain (`rustup`) and Node 18+ -- `sshfs` + FUSE (optional, only for remote/SSH projects) +- Remote/SSH and HPC projects (optional): nothing to install locally beyond + OpenSSH — no `sshfs`, no FUSE. On the host: `tmux` for persistent sessions + (optional), plus `openvpn` locally for VPN-gated hosts ```bash -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +# Install Rust (all platforms): https://rustup.rs -# Tauri system dependencies (Debian / Ubuntu) +# Linux: Tauri system dependencies (Debian / Ubuntu) sudo apt install libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \ libayatana-appindicator3-dev librsvg2-dev @@ -87,88 +171,41 @@ sudo apt install libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \ npm install ``` -## Run +On Windows the Tauri webview uses the system WebView2 runtime (preinstalled on +Windows 11); no GTK/WebKit packages are needed. -```bash -./start-eldrun-tauri.sh -``` +## Run -Or for a development build with hot-reload: +A development build with hot-reload (all platforms): ```bash -npm run tauri dev +npm run tauri:dev ``` -The desktop launchers are `Eldrun.desktop` for the normal packaged app and -`EldrunHotReload.desktop` for hot reload. They already point at this -checkout's scripts, so you can install them as-is: +On Linux you can also use the convenience scripts in `docs/`: +`docs/start-eldrun-tauri.sh` (packaged build) and +`docs/start-eldrun-tauri-hotreload.sh` (hot reload). The desktop launchers +`docs/Eldrun.desktop` and `docs/EldrunHotReload.desktop` carry a +`/path/to/projecteldrun/...` placeholder — point them at your checkout, then +install them: ```bash -cp Eldrun*.desktop ~/.local/share/applications/ +cp docs/Eldrun*.desktop ~/.local/share/applications/ update-desktop-database ~/.local/share/applications/ ``` -## Agent Support - -Eldrun launches agents in xterm.js PTY tabs. The table below describes the -current integration state. - -### CLI agents (xterm.js terminal tabs) - -| Agent | Integrated | Tested | Notes | -| ------------------------------------------ | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Claude** (`claude`) | Yes | Yes | Default agent command. Full tab lifecycle, layout persistence, project-scoped sandbox env. | -| **Codex** (`codex`) | Yes | Yes | Selectable as default agent command in Settings. Same tab lifecycle as Claude. | -| **Gemini** (`gemini`) | Yes | Yes | Selectable as default agent command in Settings. Same tab lifecycle as Claude and Codex. | -| **Vibe** (`vibe`) | Yes | No | Listed as a selectable agent command; same tab lifecycle. | -| **Ollama via Vibe** (`vibe` + local model) | Yes | Partial | Installed Ollama models appear under Local Agents. Each local tab gets an isolated per-model `VIBE_HOME` under `~/.local/share/eldrun/vibe_local/`. | -| **Shell** | Yes | Yes | Plain interactive shell tab in the project directory. | -| Mistral CLI | No | No | Not integrated. Can be used in a plain shell tab. | -| Qwen CLI | No | No | Not integrated. | -| Grok CLI | No | No | Not integrated. | - -The active agent command (`claude`, `codex`, `gemini`, or `vibe`) is set in -Settings. If the configured command is not found in `$PATH`, Eldrun falls back -to the system shell. Project-bound terminals also receive a best-effort project -sandbox: the child process runs in the project directory with project-local XDG -config, cache, data, state, and temp locations under -`/.eldrun/sandbox/`. The root orchestration terminal keeps the normal -workspace environment. - -**Session resume.** Claude and Codex tabs that carry a session id are persisted -across restarts and respawned with their prior conversation. Eldrun installs a -`SessionStart` hook (into `~/.claude/settings.json` and `~/.codex/config.toml`) -that records each tab's live session id keyed by an `ELDRUN_TAB_UID` env var, so -resume follows the live session even across a `/clear`. (Codex hooks need a -one-time `/hooks` trust before they fire; Gemini and Vibe tabs are still -dropped.) - -Local Ollama models are available from the tab `+` menu when Ollama is -installed and reachable. Eldrun can start the Ollama service, list installed -models, and create a `vibe` tab for a selected model. The per-model `VIBE_HOME` -config pins `active_model`, registers the Ollama provider, and disables Vibe -tool calls for local models so local tabs do not mutate global `~/.vibe` -configuration. - -## Platform Support - -| Platform | Status | Notes | -| ------------------------- | ------------------ | -------------------------------------------------------------------------------------------- | -| **Linux — X11** | Yes | Two-desktop workspace parking model (EWMH/xcb). Primary development target. | -| **Linux — KDE Wayland** | Yes | Per-project virtual desktop model via KWin DBus scripting. KDE 5 and KDE 6 supported. | -| **Linux — other Wayland** | Partial | Null backend (no workspace switching, no sticky windows). Terminal and file management work. | -| **Windows** | Experimental shell | Null workspace backend. No native window/default-app/download integrations. | -| **macOS** | Experimental shell | Null workspace backend. No native window/default-app/download integrations. | - ## Main Features -### Project desktop (the differentiator) +### Project desktop (the first differentiator) -- **Workspace management**: X11 two-desktop parking model and KDE Wayland - per-project virtual desktop model; global app windows stay visible across - all project switches. -- **External window tracking**: file opens use `xdg-open`; launched windows are - tracked by PID and shown in the right panel instead of embedded in the UI. +- **Workspace management**: X11 two-desktop parking model, KDE Wayland + per-project virtual desktop model, and a Windows `SW_HIDE`/`SW_SHOW` parking + model (with best-effort virtual-desktop pinning); global app windows stay + visible across all project switches. +- **External window tracking**: file opens use `xdg-open` (Linux) / the shell + open verb (Windows); launched windows are tracked by PID — found via + `EnumWindows` on Windows — and shown in the right panel instead of embedded in + the UI. - **Downloads routing**: `~/eldrun/downloads` symlink always points to the active project's `tmp/downloads/`; Firefox and Chromium preferences are updated automatically. @@ -177,6 +214,55 @@ configuration. - **Time tracking**: Eldrun records active project sessions and shows today's elapsed time on project pills. +### Remote machines & HPC clusters (the second differentiator) + +Eldrun treats remote hosts as first-class: it **manages a fleet of machines** and +**runs your projects on them**, from a single SSH box to a full HPC cluster — +with the same file tree, viewers, Git panel, and agent tabs you use locally, and +without an sshfs/FUSE mount anywhere. + +- **Machine hub**: register SSH hosts once (independent of any project) in the + header's machines indicator — see a live CPU/GPU usage bar per machine, + connect/disconnect, arm silent auto-connect on launch, and drag a machine onto + a project to attach it as a compute host. +- **Run on the remote host**: a project can point at a host — at creation, or by + *extending* an existing local project onto one in place — and run its agent, + shell, and Python tabs *on that host* over `ssh -tt`, with the file tree, Git, + and in-app viewers all reading the remote tree over SFTP. Everything rides one + pooled ControlMaster connection per machine, and VPN-gated hosts bring up an + OpenVPN tunnel first. +- **A local copy that stays in step, on purpose**: the project keeps a local + mirror kept current by two transports that split the tree by Git. **Git + lockstep** moves *tracked* files semantically — commits and refs via + `git bundle`, never `.git` bytes — while an opt-in, per-folder **byte-sync** + moves everything else. So your editor, agents, and Git history see the project + locally, while gigabytes of host-side experiment output stay on the host until + you ask for them; a setup census offers to exclude the giant folders + (`node_modules/`, `.venv/`, `data/`, `checkpoints/`) up front. +- **Many machines per project**: beyond the primary host, add extra *worker* + machines — their code is kept in sync from the project (one-way, tracked files + only) and their experiment outputs are pulled back on demand. A worker on a + **shared filesystem** (e.g. an HPC compute node on a shared home) is used in + place, with nothing copied and no Git run on it. +- **Persistent sessions**: a long run lives in a tmux session per shell tab, so + a job survives an SSH drop, a laptop sleep, a VPN drop, or Eldrun quitting, and + the tab reattaches on relaunch. A **Sessions view** lists every running session + across all connected machines, with per-row attach/rename/kill. +- **Remote system monitor**: a host's CPU, memory, and GPUs (AMD + NVIDIA, + including per-process GPU memory) are sampled over the same SSH connection and + shown alongside the local machine's. +- **HPC / SLURM** *(built, but untested pending real-cluster QA)*: submit a batch + script with `sbatch`, watch its live log, list and cancel your queued jobs, and + open an interactive compute-node shell via `srun` — without memorizing the + commands. A guided **HPC pipeline wizard** walks a cluster newcomer through + login → project → data upload → job → watch. +- **HPC workspaces** *(same QA caveat)*: on clusters that hand out scratch space + on the parallel filesystem via `hpc-workspace`, Eldrun allocates, lists, + extends, and releases workspaces from the app, and can put the project's remote + root *in* one — so the data lands off the quota'd `$HOME` before the first byte + is uploaded. Nothing is site-specific: the host is asked which filesystems and + limits it offers. + ### Project cockpit - **Agent-terminal orchestration**: create Claude, Codex, Gemini, or plain shell @@ -211,80 +297,52 @@ configuration. project or imports an existing directory (keep in place, copy, or move). - **Remote (SSH) projects**: optionally point a project at a remote host. Enter an SSH address (`user@host[:port]`), connect, and browse the remote filesystem - in-app to pick the project root. Eldrun `sshfs`-mounts it locally so the file - tree, terminal cwd, and git work unchanged. Terminal and agent tabs run **on - the remote host** over `ssh -tt` (multiplexed over a ControlMaster socket), - with the agent CLI auto-detected/bootstrapped on the remote and authenticated - with the remote's own login. VPN-gated hosts bring up an OpenVPN tunnel first. - Auth uses your existing SSH setup (keys / agent / `~/.ssh/config`, - `BatchMode`); requires `sshfs`/FUSE on the local machine. -- **Publish to GitHub**: a local (or SSH-remote) git project can be published to - a new GitHub repository from the project pill menu. Choose public or private; - Eldrun runs `gh repo create … --source=. --push` via the system `gh` CLI (over - `ssh` on the host where the bytes live for remote projects), then records the - new push target (`git_type` becomes `remote-public`/`remote-private`). Requires - the GitHub CLI (`gh`) installed and authenticated. + in-app to pick the project root — no mount involved: the file tree and file I/O + go over SFTP, terminal and agent tabs run **on the remote host** over `ssh -tt` + (multiplexed over a ControlMaster socket), and git runs on the host, with the + agent CLI auto-detected/bootstrapped there and authenticated with the remote's + own login. Auth uses your existing SSH setup (keys / agent / `~/.ssh/config`) + or a password you can optionally save to the OS keychain. See + [Remote machines & HPC clusters](#remote-machines--hpc-clusters-the-second-differentiator) + for the fleet, sync, session, and cluster features built on top. +- **Publish to GitHub / GitLab**: a local (or SSH-remote) git project can be + published to a new GitHub or GitLab repository from the project pill menu. + Choose the provider and public/private; Eldrun runs `gh repo create … + --source=. --push` (GitHub) or `glab repo create … --remoteName origin` + followed by `git push` (GitLab) via the system CLI (over `ssh` on the host + where the bytes live for remote projects), then records the new push target + (`git_type` becomes `remote-public`/`remote-private`) and provider. Requires + the chosen provider's CLI — `gh` or `glab` — installed and authenticated, or a + token set under Settings → Git hosting. - **Project switcher**: search, switch, and close projects; a running-task indicator spins on pills with live terminal output (even backgrounded projects); hover over a pill to see the project path, status, today's active time, and live CPU%. -- **Right file panel**: browse, open, create, rename, delete, and reveal project - files, with a breadcrumb trail and per-file git status markers (modified, - untracked, staged, committed-but-unpushed, ignored). A "Git" view shows the - current branch, clickable branch pills for checkout, and a commit list whose - entries open an editable commit-message window (amend HEAD, agent-generated - messages, or checkout). The panel can be pinned open instead of hover-revealed. - Additional views list tracked external windows. -- **In-app file viewers**: drag a file from the tree onto a subwindow's tab bar - to open it in a tab. The built-in viewers, by type, are: - - **Text / code** (`.txt`, `.json`, `.py`, `.rs`, `.ts`, `.svg`, `.bib`, and - many more, plus well-known extensionless files like `Dockerfile`): an - editable code editor with a line-number gutter, syntax highlighting, - Tab/Shift+Tab indent, undo/redo (`Ctrl+Z` / `Ctrl+Shift+Z`), in-editor - find (`Ctrl+F`) and find-and-replace (`Ctrl+R`) with match navigation and a - case toggle, and a save icon (`Ctrl+S`). Unsaved edits are marked in the - gutter so you can see at a glance which lines changed since the last save. - It auto-reloads when the file changes on disk (showing a non-destructive - Reload / Keep-mine banner if you have unsaved edits), and offers opt-in - local autocomplete (see below). - - **Markdown** (`.md`, `.markdown`, `.mdx`): rendered preview with an - Edit/Preview toggle; links to local files read as clickable. - - **LaTeX** (`.tex`): the code editor plus, when a TeX engine is on `PATH`, a - compile action with compiler options (output folder, extra engine flags — - shell-escape is always stripped). A successful compile opens the PDF in its - own tab (reusing/refreshing it on recompile) rather than an inline preview - pane, and `Ctrl`/`Cmd`+Click follows `\input{…}` / `\includegraphics{…}` - references (shown with a dotted underline). Typing inside `\ref{…}` / - `\cite{…}` pops a completion list of the document's `\label` keys and `.bib` - entries. A failed compile lists the parsed errors (file, line, message, - including errors in `\input`-ed child files) and clicking one jumps the - editor to that line. When the engine emits a SyncTeX file, the viewer offers - **bidirectional SyncTeX sync**: `Ctrl`/`Cmd`+Click (or a sync action) in the - editor highlights the matching box in the PDF tab, and clicking in the PDF - jumps the editor back to the source line and word — working even when the - editor and PDF live in separate tiled panes or detached OS windows. - - **Images** (`.png`, `.jpg`, `.gif`, `.webp`, …): zoom (to the cursor) / pan - viewer; the image is also draggable out as an OS drop source. - - **PDF** (`.pdf`): rendered with a themed zoom toolbar. - - Office / spreadsheet formats (`.odt`, `.xlsx`, `.docx`, …) and any other type - open in their external default app for now. Native-viewer behaviour is - configured per file type under **Settings → Native Viewers**: the per-type - autocomplete opt-in, plus a global autosave switch. The text/LaTeX/Markdown - editors also carry an `A−`/`A+` text-size control (or `Ctrl` +/−, `Ctrl`+0 to - reset) — in Markdown it scales the preview too; the chosen size persists per - file type. Each viewer also remembers where you left off: the editor/PDF - scroll position, PDF/image zoom, and image pan are persisted per tab, so - reopening a file — or restarting Eldrun — restores the reader's position - instead of jumping back to the top. +- **Right file panel**: browse, open, create, rename, delete, copy/cut/paste, + and reveal project files, with a breadcrumb trail and per-file git status + markers (modified, untracked, staged, committed-but-unpushed, ignored). A + **Git** view shows the current branch, clickable branch pills for checkout, + and a commit list whose entries open an editable commit-message window (amend + HEAD, agent-generated messages, or checkout). A **Search** view runs a + project-wide literal content search and lists matching lines that jump straight + into the in-app viewer. The panel can be pinned open instead of hover-revealed; + additional views list tracked external windows. - **Local autocomplete (opt-in, private)**: in the editable text/LaTeX/markdown viewers, `Ctrl+Space` requests a single completion from a **local Ollama** - model (`Tab` accepts, `Esc` dismisses). It is OFF by default and per file - type; nothing is sent anywhere unless you enable it, and if Ollama isn't - running it fails silently — no remote calls, ever. + model (`Tab` accepts, `Esc` dismisses). It is OFF by default; each editor tab + has its own **Autocomplete** toggle + length-mode (Sentence/Block/Scope) in the + header that overrides the per-type default, so you can enable it just for the + tab you're in. Nothing is sent anywhere unless you enable it, and if Ollama + isn't running it fails silently — no remote calls, ever. +- **Local grammar check (opt-in, private)**: the same editable viewers can run a + **local Ollama** proofreader after a typing pause, underlining spelling (red), + grammar (blue), and style (green) issues; hover a mark for the explanation and a + one-click fix. Like autocomplete it is OFF by default with a per-tab **Grammar** + toggle in the header, and entirely local — no text leaves the machine. - **Global app toolbar**: cross-project roles (Browser, Mail, Calendar, File Manager, Password Manager, Notes, Screenshot, etc.) with launch-or-raise and - icon resolution. + icon resolution. The Screenshot role launches straight into interactive + region selection when the configured tool supports it. - **Ollama model management**: the Settings Ollama panel shows installed models, running CPU/GPU state, parameter and quantization details, plus catalog install, update, unload, and delete controls. @@ -292,6 +350,91 @@ configuration. pointer hover and disappear when the pointer leaves, keeping the center terminal unobstructed; the right panel can also be pinned permanently open. +### In-app file viewers + +Drag a file from the tree onto a subwindow's tab bar to open it in a tab; the +viewer is chosen by extension. In-progress types open in the external default +app until they land. + +| Viewer | Extensions | Status | Notes | +| ------ | ---------- | ------ | ----- | +| **Text / code** | `.txt` `.toml` `.py` `.rs` `.ts` `.bib` + many more, plus extensionless files like `Dockerfile` | ✅ Shipping | Editable editor: line-number gutter, syntax highlighting, Tab/Shift+Tab indent, undo/redo (`Ctrl+Z`/`Ctrl+Shift+Z`), find (`Ctrl+F`) and find-and-replace (`Ctrl+R`) with match nav + case toggle, save (`Ctrl+S`); unsaved lines marked; non-destructive auto-reload banner; opt-in local autocomplete and grammar check. | +| **Markdown** | `.md` `.markdown` `.mdx` | ✅ Shipping | Rendered preview with an Edit/Preview toggle; links to local files are clickable. | +| **YAML / JSON** | `.yaml` `.yml` `.json` | ✅ Shipping | Editable structure tree with a Tree/Source toggle: retype a value, rename a key, add a key or list item (with a type picker), reorder, delete. Both of YAML's syntaxes are first-class — block (`key:`) and flow (`{a: 1}`, which is exactly JSON, on one line or spread over many) — and each keeps the style it is written in. The tree edits the file's own text, so comments, quoting and layout survive an edit; it withholds the affordance rather than botch a construct it can't rewrite (anchors, merge keys). Source is the full code editor. | +| **LaTeX** | `.tex` | ✅ Shipping | Code editor + compile (when a TeX engine is on `PATH`, shell-escape stripped); follows `\input{…}`/`\includegraphics{…}`, `\ref`/`\cite` completion from `\label` keys and `.bib` entries; parsed compile errors jump to the line; bidirectional SyncTeX sync across tiled or detached panes. | +| **PDF** | `.pdf` | ✅ Shipping | Rendered with a themed zoom toolbar. | +| **Images** | `.png` `.jpg` `.bmp` `.webp` … | ✅ Shipping | Zoom-to-cursor / pan; draggable out as an OS drop source. | +| **Animated GIF** | `.gif` | ✅ Shipping | Frame-level transport on top of the image viewer's zoom/pan: play/pause, frame stepping, scrubber, playback speed, loop toggle, frame/delay readout. Decoded in-app (pure LZW decoder), so it works over SFTP too; a GIF the decoder can't handle degrades to the native animated ``. | +| **Table / CSV** | `.csv` `.tsv` | ✅ Shipping | Read-only grid (RFC 4180-style parse); large files are windowed to keep the webview responsive. | +| **Jupyter notebook** | `.ipynb` | ✅ Shipping | Read-only render of cells top-to-bottom — markdown cells, Python-highlighted code cells, and their classified outputs. | +| **Diff / patch** | `.diff` `.patch` | ✅ Shipping | Color-coded add/del rendering that reads in light and dark themes. | +| **OpenDocument Text** | `.odt` | ✅ Shipping | Read-only: unzips the archive and renders `content.xml` to a safe HTML subset (headings, lists, tables, images). | +| **Spreadsheet** | `.xlsx` `.xls` `.xlsm` | ✅ Shipping | Backend reader (calamine) into the table grid, with a sheet picker. | +| **SQLite** | `.db` `.sqlite` `.sqlite3` | ✅ Shipping | Read-only table browser: table list + paged row grid. | +| **HTML / SVG** | `.html` `.htm` `.svg` | ✅ Shipping | Editable source editor with a sandboxed (no-script) live preview, Preview ⇄ Source toggle. | +| **Audio / video** | `.mp3` `.mp4` `.webm` `.wav` … | ✅ Shipping | Native in-tab `