Compare commits
42 Commits
8aba0eb315
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 47951a615b | |||
| f58b9887c7 | |||
| 1523856ea7 | |||
| 513e1a768f | |||
| f7a99b1176 | |||
| 8e95332552 | |||
| 704255e471 | |||
| 257b2ca2ef | |||
| f13186e667 | |||
| ea08563411 | |||
| 0faa1e48fd | |||
| a6dd567c0a | |||
| 4bc4174d24 | |||
| df828daea9 | |||
| 6b4afd2bc5 | |||
| e7f98dd072 | |||
| bbb06e0814 | |||
| 1fa9ffcf1b | |||
| 92f34cb64d | |||
| e15e128fd3 | |||
| e6226cfc9f | |||
| cf2c25df79 | |||
| 347fc70982 | |||
| 6c765ff7b5 | |||
| 6682040cc0 | |||
| ab1c561d6c | |||
| 446f52ca6d | |||
| 8068471120 | |||
| b743de5f34 | |||
| 23f6562f50 | |||
| a1c95dae4a | |||
| b9d006184a | |||
| 6d44f77671 | |||
| e245600235 | |||
| b127269e89 | |||
| 1345ef799c | |||
| 286513def8 | |||
| 81a663e020 | |||
| 994d5070f0 | |||
| d4d455fa08 | |||
| be1e4aad3f | |||
| 3fe911040d |
@@ -1,21 +1,59 @@
|
|||||||
# ai-control
|
# aICentral
|
||||||
|
|
||||||
Pool and session management for [Claude Code](https://claude.com/claude-code) on macOS — a tray app with a built-in terminal. Linux and Windows integration to follow.
|
**Get Claude Code organized**
|
||||||
|
|
||||||
|
Pool, project, and session management for [Claude Code](https://claude.com/claude-code) — a tray app with a built-in terminal, a draft panel, and a per-project document archive. macOS and Linux; Windows support follows. (Repository and binary name: `ai-control`.)
|
||||||
|
|
||||||
> All code in this project was generated by Claude (Claude Code).
|
> All code in this project was generated by Claude (Claude Code).
|
||||||
|
|
||||||
<img src="docs/projects.png" width="640" alt="Projects">
|
<img src="docs/projects.png" width="640" alt="Projects">
|
||||||
|
|
||||||
## Purpose
|
## What it does
|
||||||
|
|
||||||
Running Claude Code with multiple accounts or credential sets means hitting the right `CLAUDE_CONFIG_DIR` (plus keychain entry) for every session. ai-control turns that into clicks:
|
- **Several logins, one click apart** — keep separate Claude Code logins side by side (subscription accounts or API keys) and give every project the one it should run under. Each project starts with its own login; no re-login, no juggling environment variables. An existing login in `~/.claude` is picked up as a pool of its own, so nothing has to be set up twice.
|
||||||
|
- **Command history** — every shell command the assistant hands you lands beside the terminal as a copyable tile, with the session's full command history one click away.
|
||||||
|
- **Text panel** — Markdown drafts appear next to the terminal instead of scrolling past in the chat: readable, selectable, editable, and copyable in one go.
|
||||||
|
- **Archive with full-text search** — keep drafts as Markdown files per project, curated with folder, description, and tags; browse them as a wiki and search their full text.
|
||||||
|
- **What it costs** — tokens and estimated cost per login and project over the last 7 or 30 days, read straight from the session transcripts (retries deduplicated).
|
||||||
|
- **One click, one session** — the tray popup lists every project with its icon and a green dot when it runs; a click starts it or brings the running terminal to the front.
|
||||||
|
|
||||||
- **Pools** — named credential sets (OAuth login or API key) under `~/.config/ai-control/pools/<pool>`. Each pool is a self-contained Claude config directory with its own keychain entry.
|
## How it works
|
||||||
- **Projects** — arbitrary directories, mapped through a registry (`~/.config/ai-control/projects.json`, name → path; paths under home are stored as `~/…` and therefore stable across machines). Each project is assigned a pool, plus terminal settings (theme, icon, window title).
|
|
||||||
- **Sessions** — a built-in terminal per project (xterm.js + PTY) that launches Claude Code with the pool's config directory as `CLAUDE_CONFIG_DIR`. Every terminal runs as its own process with its own Dock icon and Cmd-Tab entry.
|
Running Claude Code with multiple accounts or credential sets means hitting the right `CLAUDE_CONFIG_DIR` (plus keychain entry) for every session. aICentral turns that into clicks:
|
||||||
- **Tray** — the app itself is a pure menu-bar app without a Dock entry. The tray menu lists all projects with icon and status dot (green = running); clicking starts the project or brings the running terminal to the front.
|
|
||||||
|
- **Pools** — named credential sets (OAuth login or API key) under `~/.config/ai-control/pools/<pool>`. Each pool is a self-contained Claude config directory with its own keychain entry. A pool can also *point at* an existing config directory instead: if `~/.claude` is there, it is offered as a pool, keeps its login, and is left otherwise untouched — the app adds panel access there and never deletes anything in it.
|
||||||
|
- **Projects** — arbitrary directories carrying their own identity in `<project>/.ai-control/config.json` (UUID, display name, terminal settings, archive home — this file syncs with the project). The machine-local registry `~/.config/ai-control/projects.json` maps that UUID to the directory and the assigned pool; paths under home are stored as `~/…` and therefore stable across machines.
|
||||||
|
- **Sessions** — a built-in terminal per project (xterm.js + PTY) that launches Claude Code with the pool's config directory as `CLAUDE_CONFIG_DIR`. Every terminal runs as its own process — on macOS with its own Dock icon and Cmd-Tab entry, on Linux as its own window with its own app id.
|
||||||
|
- **Tray** — the app itself is a pure tray app. Clicking the tray icon opens a popup listing all projects with icon and status dot (green = running); clicking a project starts it or brings the running terminal to the front.
|
||||||
|
- **Text panel** — a workspace beside the terminal with four tabs: command tiles, the current draft, an archive wiki, and archive search. Claude writes into it through a bundled MCP server instead of flooding the chat.
|
||||||
|
- **Archive** — drafts can be archived as Markdown files into a per-project archive home, curated with folder, description, and tags at archiving time; browsable as a wiki and searchable via full-text search.
|
||||||
- **Session watcher** — detects the end of a session by the terminal process disappearing and then (opt-in) syncs the Git repository containing the project: add → commit → pull --rebase → push.
|
- **Session watcher** — detects the end of a session by the terminal process disappearing and then (opt-in) syncs the Git repository containing the project: add → commit → pull --rebase → push.
|
||||||
|
|
||||||
|
## Text panel and MCP tools
|
||||||
|
|
||||||
|
The app ships an MCP stdio server (`ai-control --mcp-panel`, server key `text-panel`) and provisions it into every pool, including the tool permission — no per-pool setup. Six tools:
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `write_panel` | Place a Markdown draft in the panel instead of printing it as chat prose; accepts inline text or a file path (the server reads the file itself). |
|
||||||
|
| `write_commands` | List shell commands for the user as copyable tiles (command + optional note), appended to the session's command history. |
|
||||||
|
| `show_commands` | Show the command history (tile view). |
|
||||||
|
| `archive_panel` | Save the current draft as a Markdown file into the project's archive home; takes `folder`, `description`, `tags` for the frontmatter. |
|
||||||
|
| `show_archive` | Render the archive overview as a wiki page — documents grouped by folder, with descriptions and clickable tag links; with `tag`, that tag's page. |
|
||||||
|
| `search_archive` | FTS5 full-text search over the archive (words, "phrases", prefix*); hits appear as tiles in the panel. |
|
||||||
|
|
||||||
|
The panel docks into the terminal window and can be detached into its own window. The header tab bar switches between **Commands** (command tiles), the current **document**, **Wiki**, and **Search** (live search, `#tag` filtering); without a configured archive home the archive tabs stay hidden. The draft's title is taken from its first heading and is editable, as is the content. Spell checking follows `spellcheckLang` (per-text override in the panel).
|
||||||
|
|
||||||
|
The archive lives in the project's `archiveHome` (set in `.ai-control/config.json`); documents are plain Markdown files with frontmatter — no database, the search index is built in-memory per query.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
| Command tiles | Archive search |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
The interface speaks English and German across all windows; the language follows the browser locale and can be switched in the app.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
| Pools | Usage |
|
| Pools | Usage |
|
||||||
@@ -26,87 +64,177 @@ Running Claude Code with multiple accounts or credential sets means hitting the
|
|||||||
|---|---|
|
|---|---|
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
Prebuilt DMGs are attached to the releases. Open the DMG and drag `ai-control.app` onto the `Applications` shortcut — or drop it into your personal `~/Applications` instead, which works just as well and asks for no administrator password. On first launch from anywhere else (the DMG, your Downloads folder), the app offers to move itself to `~/Applications` and restarts from there.
|
||||||
|
|
||||||
|
The bundles are **not signed or notarized** — an Apple Developer ID costs €99/year, which this project doesn't have. If you'd like to change that: [Buy me a coffee](https://buymeacoffee.com/marcusH). macOS quarantines the unsigned download; clear it with whichever path you installed to:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xattr -d com.apple.quarantine /Applications/ai-control.app # or
|
||||||
|
xattr -d com.apple.quarantine ~/Applications/ai-control.app
|
||||||
|
```
|
||||||
|
|
||||||
|
Without the terminal: double-click the app, dismiss the warning, then open **System Settings → Privacy & Security**. Near the bottom of that page macOS now offers *Open Anyway* for the app it just blocked — click it and confirm. The warning is gone for good afterwards.
|
||||||
|
|
||||||
|
Or avoid the topic entirely and build from source (below) — self-built apps aren't quarantined.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
Releases carry a `.deb` and an `.rpm`. Both install three desktop-integration pieces alongside the app: the **GNOME Shell extension** `ai-control-popup@local` (tray button + popup on GNOME, `/usr/share/gnome-shell/extensions/`), the **Cinnamon extension** `ai-control-popup@local` (popup placement on Wayland, initial focus on X11, `/usr/share/cinnamon/extensions/`), and the **KWin script** `ai-control-popup` (popup placement on KDE Wayland, `/usr/share/kwin/scripts/`).
|
||||||
|
|
||||||
|
Tray integration by desktop: GNOME via the bundled shell extension, KDE Wayland via StatusNotifierItem plus the bundled KWin script for popup placement, KDE X11/XFCE/Cinnamon via StatusNotifierItem directly. On Cinnamon the bundled extension additionally places the popup at the tray icon on **Wayland** (a Wayland client cannot position its own window) and gives it initial focus on **X11** (Muffin's focus-stealing prevention would otherwise drop it).
|
||||||
|
|
||||||
|
The app **enables the matching piece automatically** on each start, per user and without prompting: GNOME via `gnome-extensions enable`, Cinnamon via the `org.cinnamon enabled-extensions` gsettings key, KDE via `kwriteconfig6` + KWin reconfigure. Cinnamon and KDE pick the change up immediately. GNOME Shell only scans for newly installed extensions at session start, so right after the very first package install the enable call can come too early — the tray button then appears after the next login; from then on it is always active.
|
||||||
|
|
||||||
|
| Tray popup on GNOME | Tray popup on Cinnamon |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Installer tests
|
||||||
|
|
||||||
|
The installers are hand-tested per release. Current state: Fedora 44 via rpm on GNOME, KDE Plasma, Cinnamon, and XFCE (VM test fleet); Ubuntu 26.04 (GNOME), Kubuntu (KDE Plasma, Wayland) and Linux Mint (Cinnamon) via deb in earlier rounds. The two open findings from that round are resolved: popup placement on Cinnamon Wayland now comes from the bundled Cinnamon extension, and click-outside-to-close verified fine on XFCE and Cinnamon. macOS re-test of the current build is in progress.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Follows.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
One binary, three process roles:
|
||||||
|
|
||||||
|
- **Main app** (`ai-control`) — tray, main window, management, session watcher.
|
||||||
|
- **Terminal process** (`ai-control --terminal <project>`) — one window with its own PTY; launches Claude Code in the project directory with the assigned pool's config directory. On macOS it carries its own Dock icon and Cmd-Tab entry, on Linux its own app id and `.desktop` file.
|
||||||
|
- **MCP server** (`ai-control --mcp-panel`) — stdio server started by Claude Code itself; no GUI.
|
||||||
|
|
||||||
|
Running projects are recognised by their terminal processes (`pgrep` for `--terminal <project>`) — there is no state file that could go stale.
|
||||||
|
|
||||||
|
**One look, every platform.** Windows are frameless and draw their own header as the title bar, styled from theme variables: macOS keeps the traffic lights as an overlay, Linux gets its own window buttons. The tray popup is the same HTML window everywhere — what differs is only how the click arrives and who places the window.
|
||||||
|
|
||||||
|
**Placement follows a contract.** `show_popup` is a single code path; where the popup lands is decided by the anchor the tray integration hands it:
|
||||||
|
|
||||||
|
| Anchor | Where it comes from | Placement |
|
||||||
|
|---|---|---|
|
||||||
|
| `IconRect` | native tray (macOS, Windows) | popup's right edge at the tray icon — below the menu bar, above the taskbar |
|
||||||
|
| `Click` | StatusNotifierItem (KDE, XFCE, Cinnamon) | at the activate coordinates when the host supplies usable ones, otherwise at the pointer, clamped into the monitor |
|
||||||
|
| `Managed` | GNOME shell extension, KWin script, Cinnamon extension | the compositor places the window itself |
|
||||||
|
|
||||||
|
GNOME has no tray at all, so the bundled shell extension provides the icon and relays the click over D-Bus. On Wayland a window cannot position itself, which is why KDE and Cinnamon get a small script/extension for that job.
|
||||||
|
|
||||||
|
**Linux, honestly:** desktop × session type (X11/Wayland) × tray host is a wide matrix, and no release can test all of it. What was tested is listed under [Installer tests](#installer-tests). On an untested combination the popup works, but its first placement may sit somewhere other than at the tray icon.
|
||||||
|
|
||||||
|
## Security model
|
||||||
|
|
||||||
|
- **CSP** — the WebViews run under `default-src 'self'`; scripts, styles, and connections are limited to the app itself and the Tauri IPC endpoint (dev mode additionally allows the Vite dev server and inline styles for hot reload). `style-src` carries no `unsafe-inline`: the window styles live in their own CSS files rather than in the HTML heads, so the policy holds without an exception.
|
||||||
|
- **Per-window capabilities, deny-by-default** — four capability files, one per window group: `main` (management, broadest command set), `popup` (list/start projects only), `term-*` (PTY plus docked panel), `panel-*` (panel/archive channels only — no PTY, no management). A backend command is callable only from windows whose capability lists it.
|
||||||
|
- **Pools separate configuration, not access** — keychain entries are deliberately readable without a prompt; any process running as your user can read any pool key. No isolation promise is made or implied.
|
||||||
|
|
||||||
|
## Project deletion
|
||||||
|
|
||||||
|
Deleting a project is scoped in three stages, each preceded by a preview of the affected artifacts (integration files, archive permission, todo hook, panel files, archive documents, working directories):
|
||||||
|
|
||||||
|
1. **Integration only** — removes ai-control's traces: `.ai-control/`, registry entry, panel/session files, archive permission, todo hook, desktop file. The project itself stays untouched.
|
||||||
|
2. **+ Archive** — additionally deletes the archive home with its documents.
|
||||||
|
3. **Full** — additionally deletes the project directory (working directories only with an explicit extra flag).
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
```
|
```
|
||||||
├── index.html main UI (project/pool management)
|
├── index.html main UI (project/pool management)
|
||||||
├── terminal.html terminal window (xterm.js)
|
├── terminal.html terminal window (xterm.js) + docked text panel
|
||||||
|
├── panel.html detached text panel window
|
||||||
|
├── popup.html tray popup
|
||||||
├── src/ frontend: Vue 3 + TypeScript
|
├── src/ frontend: Vue 3 + TypeScript
|
||||||
|
│ (incl. wiki-view, search-view, archive-form,
|
||||||
|
│ commands-view + vitest tests)
|
||||||
├── src-tauri/
|
├── src-tauri/
|
||||||
│ ├── src/lib.rs Tauri commands: projects, pools, keychain,
|
│ ├── src/lib.rs entry point, process roles
|
||||||
│ │ OAuth login, tray menu, session watcher
|
│ ├── src/app.rs Tauri wiring: tray, main/popup windows, watcher
|
||||||
│ ├── src/terminal.rs PTY sessions (portable-pty), terminal windows,
|
│ ├── src/mcp.rs MCP stdio server (6 tools, see above)
|
||||||
│ │ Dock icon, focusing running terminals
|
│ ├── src/terminal.rs PTY sessions (portable-pty), terminal windows
|
||||||
|
│ ├── src/domain/ registry, project config, settings, watcher,
|
||||||
|
│ │ deletion scopes
|
||||||
|
│ ├── src/platform/ macOS / Linux / Windows specifics
|
||||||
|
│ ├── capabilities/ per-window ACL (main/popup/terminal/panel)
|
||||||
│ └── icons/ app and tray icons
|
│ └── icons/ app and tray icons
|
||||||
|
├── gnome-shell-extension/ tray/popup integration for GNOME
|
||||||
|
├── cinnamon-extension/ popup placement for Cinnamon Wayland
|
||||||
|
├── kwin-script/ popup placement for KDE Wayland
|
||||||
├── dev.sh development mode (tauri dev)
|
├── dev.sh development mode (tauri dev)
|
||||||
└── build.sh release build (.app bundle)
|
├── build.sh release build for the current OS (see below)
|
||||||
|
├── deploy-linux.sh install the freshest local bundle (rpm/deb)
|
||||||
|
└── deploy-macos.sh install the fresh .app into ~/Applications
|
||||||
```
|
```
|
||||||
|
|
||||||
Two process roles from one binary:
|
The three process roles that binary takes are described under [Architecture](#architecture).
|
||||||
|
|
||||||
- **Main app** (`ai-control`) — tray, main window, management, watcher.
|
|
||||||
- **Terminal process** (`ai-control --terminal <project>`) — one window with its own PTY; launches Claude Code in the project directory with the assigned pool's `CLAUDE_CONFIG_DIR`.
|
|
||||||
|
|
||||||
Running projects are detected through their terminal processes (`pgrep` for `--terminal <project>`), without a state file.
|
|
||||||
|
|
||||||
## Tooling
|
## Tooling
|
||||||
|
|
||||||
| Area | Stack |
|
| Area | Stack |
|
||||||
|-----------|-------|
|
|-----------|-------|
|
||||||
| Shell | Tauri 2 (Rust) |
|
| Shell | Tauri 2 (Rust) |
|
||||||
| Frontend | Vue 3, TypeScript, Vite |
|
| Frontend | Vue 3, TypeScript, Vite; vitest (happy-dom) for the panel views |
|
||||||
| Terminal | xterm.js, portable-pty |
|
| Terminal | xterm.js, portable-pty |
|
||||||
| macOS | objc2 / objc2-app-kit (Dock icon, window focus, tray) |
|
| macOS | objc2 / objc2-app-kit (Dock icon, window focus, tray) |
|
||||||
| Secrets | keyring (macOS Keychain) |
|
| Linux | GTK 3 / WebKitGTK, ksni (StatusNotifierItem), zbus (D-Bus) |
|
||||||
|
| Secrets | keyring (macOS Keychain / Secret Service) |
|
||||||
## Installation
|
|
||||||
|
|
||||||
Prebuilt DMGs are attached to the releases. The bundles are **not signed or notarized** — an Apple Developer ID costs €99/year, which this project doesn't have. If you'd like to change that: [Buy me a coffee](https://buymeacoffee.com/marcusH). macOS quarantines the unsigned download; after copying `ai-control.app` to `~/Applications`, clear it with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
xattr -d com.apple.quarantine ~/Applications/ai-control.app
|
|
||||||
```
|
|
||||||
|
|
||||||
Or avoid the topic entirely and build from source (below) — self-built apps aren't quarantined.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./dev.sh # tauri dev with hot reload
|
./dev.sh # tauri dev with hot reload
|
||||||
./build.sh # release build; bundle lands in src-tauri/target/release/bundle/macos.noindex/
|
./build.sh # release build for the current OS
|
||||||
|
./deploy-macos.sh # …then install it (pick the script for your OS)
|
||||||
```
|
```
|
||||||
|
|
||||||
`build.sh` moves the bundle into a `.noindex` directory and unregisters it from Launch Services so Spotlight only finds the installed copy. Install by copying `ai-control.app` to `~/Applications`.
|
`build.sh` picks the bundles by platform: macOS `.app` + DMG, Linux deb + rpm. Installing is the job of the matching deploy script:
|
||||||
|
|
||||||
Prerequisites: Rust (stable), Node.js/npm, macOS. `dev.sh`/`build.sh` expect `CARGO_HOME`/`RUSTUP_HOME` under `~/tools/` — adjust the two exports in the scripts for a standard installation.
|
- **`deploy-macos.sh`** — installs the fresh bundle into `~/Applications` (replacing the previous copy) and registers it with Launch Services. The build output is then moved into `bundle/macos.noindex/` and unregistered, so Spotlight and the launcher only ever find the installed copy. It refuses to run while `ai-control` is still running.
|
||||||
|
- **`deploy-linux.sh`** — installs the freshest local bundle via `rpm`/`dpkg`.
|
||||||
|
|
||||||
|
Build dependencies on Linux: `webkit2gtk-4.1`, `libgtk-3`, `libayatana-appindicator3`, `librsvg2`, `patchelf`.
|
||||||
|
|
||||||
|
Prerequisites: Rust (stable), Node.js/npm. `dev.sh`/`build.sh` expect `CARGO_HOME`/`RUSTUP_HOME` under `~/tools/` — adjust the two exports in the scripts for a standard installation.
|
||||||
|
|
||||||
## Configuration layout
|
## Configuration layout
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.config/ai-control/
|
~/.config/ai-control/
|
||||||
├── settings.json app settings (see below)
|
├── settings.json app settings (see below)
|
||||||
├── projects.json project registry: name → directory
|
├── projects.json registry: project UUID → directory
|
||||||
|
│ + pool assignment (machine-local)
|
||||||
└── pools/<pool>/ one Claude config directory per pool
|
└── pools/<pool>/ one Claude config directory per pool
|
||||||
└── pool.json name + credential type
|
└── pool.json name + credential type
|
||||||
|
|
||||||
<project directory>/ any path, mapped via the registry
|
<project directory>/ any path, mapped via the registry
|
||||||
├── .claude/ Claude Code project configuration
|
├── .claude/ Claude Code project configuration
|
||||||
└── ai-control.json pool assignment + terminal settings
|
└── .ai-control/ syncs with the project
|
||||||
|
├── config.json project id (UUID), display name,
|
||||||
|
│ terminal settings (theme, icon, title),
|
||||||
|
│ archive home (archiveHome)
|
||||||
|
└── icon.png project icon
|
||||||
```
|
```
|
||||||
|
|
||||||
### settings.json
|
### settings.json
|
||||||
|
|
||||||
| Key | Default | Meaning |
|
| Key | Default | Meaning |
|
||||||
|--------------------|----------|---------|
|
|--------------------|----------|---------|
|
||||||
| `claudeCommand` | `claude` | Command launched in the project terminal (via `zsh -ic`, so your `.zshrc` PATH applies). |
|
| `claudeCommand` | `claude` | Command launched in the project terminal (via your login shell, `$SHELL -lc`, so your profile PATH applies). |
|
||||||
| `syncOnSessionEnd` | `false` | After a session ends, commit and push the Git repository containing the project. |
|
| `syncOnSessionEnd` | `false` | After a session ends, commit and push the Git repository containing the project. |
|
||||||
| `poolSyncDir` | unset | Directory to sync pool runtime data into (session transcripts, todos, prompt history — what `/resume` needs). When set, a pool's runtime files are replaced by symlinks into `<poolSyncDir>/<pool>`, so sessions travel with however that directory is synced (git, Syncthing, …). Unset: everything stays local in the pool directory. |
|
| `poolSyncDir` | unset | Directory to sync pool runtime data into (session transcripts, todos, prompt history — what `/resume` needs). When set, a pool's runtime files are replaced by symlinks into `<poolSyncDir>/<pool>`, so sessions travel with however that directory is synced (git, Syncthing, …). Unset: everything stays local in the pool directory. |
|
||||||
|
| `terminalFontSize` | `13` | Font size of the built-in terminal (also adjustable from the terminal window). |
|
||||||
|
| `spellcheckLang` | `de` | Spell-check language of the text panel (per-text override in the panel). |
|
||||||
|
|
||||||
## Claude and Anthropic licensing
|
## Claude and Anthropic
|
||||||
|
|
||||||
ai-control is pool management only — it does not change, extend, or circumvent Anthropic's licensing in any way. Using Claude Code through ai-control is subject to the same Anthropic terms as running it directly.
|
Two things to be clear about:
|
||||||
|
|
||||||
For that reason the app stores no OAuth tokens itself: with an OAuth pool, the first session of a project on that pool goes through the regular Anthropic login (`/login`, browser, against your subscription), run by Claude Code itself. Credentials live where Claude Code puts them — in the macOS keychain, one entry per pool.
|
- **No connection to Anthropic.** aICentral is an independent open-source project — not affiliated with, sponsored by, endorsed by, or in any way associated with Anthropic. Claude and Claude Code are trademarks of Anthropic and are named here solely to describe what this app works with.
|
||||||
|
- **No effect on your licence or your usage.** aICentral organises config directories; it does not touch, extend, replace, or circumvent Anthropic's terms, and it grants no access of its own. Whatever you may do with Claude Code, you may do through aICentral — no more, no less. Every request runs under your own login against your own subscription or API key, exactly as it would without this app.
|
||||||
|
|
||||||
|
The app stores no OAuth tokens itself: with an OAuth pool, the first session of a project on that pool goes through the regular Anthropic login (`/login`, browser, against your subscription), run by Claude Code itself. Credentials live where Claude Code puts them — in the macOS Keychain or the freedesktop Secret Service, one entry per pool.
|
||||||
|
|
||||||
API keys go into the keychain as well (one entry per pool). Only when no keychain/keyring is available does the app offer — after an explicit confirmation — to store the key as a file in the pool directory (mode 0600). We strongly advise against that fallback: an API key on disk is readable by every process running as your user. Use the keychain.
|
API keys go into the keychain as well (one entry per pool). Only when no keychain/keyring is available does the app offer — after an explicit confirmation — to store the key as a file in the pool directory (mode 0600). We strongly advise against that fallback: an API key on disk is readable by every process running as your user. Use the keychain.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# Gemeinsamer Release-Build für macOS/Linux (Windows: unter Git-Bash läuft
|
||||||
|
# auch dieses Script). Baut nur — ausrollen macht das jeweilige
|
||||||
|
# deploy-<os>-Script.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -8,22 +11,20 @@ export RUSTUP_HOME="$HOME/tools/.rustup"
|
|||||||
export PATH="$CARGO_HOME/bin:$PATH"
|
export PATH="$CARGO_HOME/bin:$PATH"
|
||||||
|
|
||||||
cd "$DIR"
|
cd "$DIR"
|
||||||
# CI=true: DMG ohne Finder-AppleScript (Fenster-Layout) bauen — das Script
|
case "$(uname -s)" in
|
||||||
# wäre aus Nicht-GUI-Shells TCC-blockiert.
|
Darwin)
|
||||||
CI=true npm run tauri build -- --bundles app,dmg "$@"
|
# CI=true: DMG ohne Finder-AppleScript (Fenster-Layout) bauen — das Script
|
||||||
|
# wäre aus Nicht-GUI-Shells TCC-blockiert.
|
||||||
# Dev-Bundle in ein .noindex-Verzeichnis schieben und aus Launch Services
|
CI=true npm run tauri build -- --bundles app,dmg "$@"
|
||||||
# austragen — Spotlight indiziert .noindex-Ordner nicht, damit erscheint im
|
;;
|
||||||
# Starter/Spotlight nur die installierte Kopie unter ~/Applications.
|
Linux)
|
||||||
BUNDLE="$DIR/src-tauri/target/release/bundle/macos"
|
npm run tauri build -- --bundles deb,rpm "$@"
|
||||||
NOINDEX="$DIR/src-tauri/target/release/bundle/macos.noindex"
|
;;
|
||||||
LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
|
MINGW* | MSYS*)
|
||||||
# lsregister -u meldet -10814, wenn das Bundle nicht registriert ist —
|
npm run tauri build -- --bundles nsis "$@"
|
||||||
# erwartetes Ergebnis, kein Fehler.
|
;;
|
||||||
"$LSREGISTER" -u "$BUNDLE/ai-control.app" || true
|
*)
|
||||||
rm -rf "$NOINDEX/ai-control.app"
|
echo "unbekanntes OS: $(uname -s)" >&2
|
||||||
mkdir -p "$NOINDEX"
|
exit 1
|
||||||
mv "$BUNDLE/ai-control.app" "$NOINDEX/"
|
;;
|
||||||
"$LSREGISTER" -u "$NOINDEX/ai-control.app" || true
|
esac
|
||||||
|
|
||||||
echo "DMG: $(ls "$DIR"/src-tauri/target/release/bundle/dmg/*.dmg)"
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/* Cinnamon-Extension (CJS, Muffin/Meta-0)
|
||||||
|
* ai-control-popup@local
|
||||||
|
*
|
||||||
|
* Das Tray-Icon kommt unter Cinnamon über SNI (xapp), die App zeigt das
|
||||||
|
* Popup selbst. Die Extension ergänzt, was der Client nicht kann:
|
||||||
|
* - Wayland: set_position aus dem Client ist wirkungslos — die Extension
|
||||||
|
* schiebt das frisch gemappte Popup an den Mauszeiger (der beim Klick auf
|
||||||
|
* dem Icon steht), mit derselben Klemm-Logik wie position_popup in app.rs.
|
||||||
|
* - X11: Muffins Focus-Stealing-Prevention verwirft das present() der App —
|
||||||
|
* ohne initialen Fokus schließt das Blur-Verstecken nie. Die Extension
|
||||||
|
* fokussiert das Popup beim Map aus dem Compositor heraus (platziert wird
|
||||||
|
* client-seitig über die SNI-Activate-Koordinaten).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
|
|
||||||
|
const WIN_TITLE = 'ai-control-popup';
|
||||||
|
|
||||||
|
let mapId = 0;
|
||||||
|
|
||||||
|
function _place(win) {
|
||||||
|
const [px, py] = global.get_pointer();
|
||||||
|
const rect = win.get_frame_rect();
|
||||||
|
// Work-Area statt Monitor: die Panel-Struts bleiben frei, das Popup sitzt
|
||||||
|
// über der Leiste statt darauf.
|
||||||
|
const wa = win.get_work_area_current_monitor();
|
||||||
|
const maxX = wa.x + wa.width - rect.width;
|
||||||
|
const maxY = wa.y + wa.height - rect.height;
|
||||||
|
const x = Math.min(Math.max(px - rect.width, wa.x), Math.max(maxX, wa.x));
|
||||||
|
const y = Math.min(Math.max(py, wa.y), Math.max(maxY, wa.y));
|
||||||
|
win.move_frame(true, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beim Mappen unsichtbar schalten und vor dem nächsten Redraw positionieren —
|
||||||
|
// der erste sichtbare Frame sitzt schon am Zielort (Muster wie in der
|
||||||
|
// GNOME-Extension, dort über global.compositor.get_laters()).
|
||||||
|
function _placeBeforeRedraw(actor, win) {
|
||||||
|
actor.remove_all_transitions();
|
||||||
|
actor.opacity = 0;
|
||||||
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
||||||
|
const rect = win.get_frame_rect();
|
||||||
|
// Noch keine echte Größe -> nächster Redraw, bis positionierbar.
|
||||||
|
if (!rect || rect.width <= 1) return true;
|
||||||
|
_place(win);
|
||||||
|
actor.opacity = 255;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(_metadata) {}
|
||||||
|
|
||||||
|
function enable() {
|
||||||
|
mapId = global.window_manager.connect('map', (_wm, actor) => {
|
||||||
|
const win = actor.meta_window;
|
||||||
|
if (!win || win.get_title() !== WIN_TITLE) return;
|
||||||
|
if (Meta.is_wayland_compositor()) {
|
||||||
|
_placeBeforeRedraw(actor, win);
|
||||||
|
} else {
|
||||||
|
win.activate(global.get_current_time());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable() {
|
||||||
|
if (mapId) {
|
||||||
|
global.window_manager.disconnect(mapId);
|
||||||
|
mapId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"uuid": "ai-control-popup@local",
|
||||||
|
"name": "ai-control popup placement",
|
||||||
|
"description": "ai-control-Tray-Popup: platziert es unter Wayland am Mauszeiger und gibt ihm unter X11 den initialen Fokus (Focus-Stealing-Prevention).",
|
||||||
|
"cinnamon-version": ["6.0", "6.2", "6.4", "6.6"],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Linux-Deploy nach build.sh: jüngstes Paket-Bundle installieren.
|
||||||
|
# dnf-Systeme: rpm -Uvh --force (installiert auch dieselbe Version neu),
|
||||||
|
# apt-Systeme: dpkg -i. Braucht sudo.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BUNDLE="$DIR/src-tauri/target/release/bundle"
|
||||||
|
|
||||||
|
if command -v dnf >/dev/null; then
|
||||||
|
PKG="$(ls -t "$BUNDLE"/rpm/ai-control-*.rpm | head -1)"
|
||||||
|
sudo rpm -Uvh --force "$PKG"
|
||||||
|
elif command -v dpkg >/dev/null; then
|
||||||
|
PKG="$(ls -t "$BUNDLE"/deb/ai-control_*.deb | head -1)"
|
||||||
|
sudo dpkg -i "$PKG"
|
||||||
|
else
|
||||||
|
echo "weder dnf noch dpkg gefunden" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "installiert: $PKG"
|
||||||
|
echo "Haupt-App übers Tray beenden und neu starten (Migration läuft beim Start)."
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# macOS-Deploy nach build.sh: das frisch gebaute Bundle nach ~/Applications
|
||||||
|
# installieren. Das Dev-Bundle wandert danach aus Launch Services heraus in ein
|
||||||
|
# .noindex-Verzeichnis — Spotlight indiziert .noindex nicht, damit erscheint im
|
||||||
|
# Starter/Spotlight nur die installierte Kopie.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
BUNDLE="$DIR/src-tauri/target/release/bundle/macos"
|
||||||
|
NOINDEX="$DIR/src-tauri/target/release/bundle/macos.noindex"
|
||||||
|
TARGET="$HOME/Applications"
|
||||||
|
LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
|
||||||
|
|
||||||
|
if [[ ! -d "$BUNDLE/ai-control.app" ]]; then
|
||||||
|
echo "kein frisches Bundle unter $BUNDLE — erst ./build.sh laufen lassen" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Eine laufende Instanz hält Dateien im Bundle offen; das Ersetzen ergäbe eine
|
||||||
|
# halb überschriebene App.
|
||||||
|
if pgrep -qx ai-control; then
|
||||||
|
echo "ai-control läuft — erst beenden, dann erneut deployen" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Installieren: alte Kopie ganz weg (ditto führt sonst Reste alter Builds
|
||||||
|
# fort), dann das neue Bundle hin und in Launch Services eintragen.
|
||||||
|
mkdir -p "$TARGET"
|
||||||
|
"$LSREGISTER" -u "$TARGET/ai-control.app" || true
|
||||||
|
rm -rf "$TARGET/ai-control.app"
|
||||||
|
ditto "$BUNDLE/ai-control.app" "$TARGET/ai-control.app"
|
||||||
|
"$LSREGISTER" -f "$TARGET/ai-control.app"
|
||||||
|
|
||||||
|
# lsregister -u meldet -10814, wenn das Bundle nicht registriert ist —
|
||||||
|
# erwartetes Ergebnis, kein Fehler.
|
||||||
|
"$LSREGISTER" -u "$BUNDLE/ai-control.app" || true
|
||||||
|
rm -rf "$NOINDEX/ai-control.app"
|
||||||
|
mkdir -p "$NOINDEX"
|
||||||
|
mv "$BUNDLE/ai-control.app" "$NOINDEX/"
|
||||||
|
"$LSREGISTER" -u "$NOINDEX/ai-control.app" || true
|
||||||
|
|
||||||
|
echo "installiert: $TARGET/ai-control.app"
|
||||||
|
echo "DMG: $(ls "$DIR"/src-tauri/target/release/bundle/dmg/*.dmg)"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Windows-Deploy nach dem Build (--bundles nsis): jüngsten Installer starten.
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$installer = Get-ChildItem "$dir\src-tauri\target\release\bundle\nsis\ai-control_*-setup.exe" |
|
||||||
|
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $installer) {
|
||||||
|
Write-Error "kein Installer gefunden — erst build.sh (Git-Bash) mit nsis-Bundle laufen lassen"
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Process -Wait $installer.FullName
|
||||||
|
Write-Output "installiert: $($installer.Name)"
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ai-control</title>
|
<title>aICentral</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ function place(w) {
|
|||||||
if (y < area.y) y = area.y;
|
if (y < area.y) y = area.y;
|
||||||
if (y + height > area.y + area.height) y = area.y + area.height - height;
|
if (y + height > area.y + area.height) y = area.y + area.height - height;
|
||||||
|
|
||||||
w.frameGeometry = Qt.rect(x, y, width, height);
|
// Reine QJSEngine, kein QML: `Qt.rect` gibt es hier nicht; die Engine
|
||||||
|
// konvertiert ein {x,y,width,height}-Objekt selbst nach QRectF.
|
||||||
|
w.frameGeometry = { x: x, y: y, width: width, height: height };
|
||||||
}
|
}
|
||||||
|
|
||||||
// windowAdded feuert beim (erneuten) Mappen des Popups, windowActivated fängt
|
// windowAdded feuert beim (erneuten) Mappen des Popups, windowActivated fängt
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"License": "MIT",
|
"License": "MIT",
|
||||||
"EnabledByDefault": false
|
"EnabledByDefault": false
|
||||||
},
|
},
|
||||||
|
"KPackageStructure": "KWin/Script",
|
||||||
"X-Plasma-API": "javascript",
|
"X-Plasma-API": "javascript",
|
||||||
"X-Plasma-MainScript": "code/main.js"
|
"X-Plasma-MainScript": "code/main.js"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+468
@@ -12,6 +12,7 @@
|
|||||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
"@tauri-apps/api": "^2.11.1",
|
"@tauri-apps/api": "^2.11.1",
|
||||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||||
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||||
"@tauri-apps/plugin-opener": "^2.5.4",
|
"@tauri-apps/plugin-opener": "^2.5.4",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
@@ -27,8 +28,10 @@
|
|||||||
"@types/node": "^24.13.2",
|
"@types/node": "^24.13.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"@vitejs/plugin-vue": "^6.0.7",
|
||||||
"@vue/tsconfig": "^0.9.1",
|
"@vue/tsconfig": "^0.9.1",
|
||||||
|
"happy-dom": "^20.11.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"vite": "^8.1.1",
|
"vite": "^8.1.1",
|
||||||
|
"vitest": "^4.1.10",
|
||||||
"vue-tsc": "^3.3.5"
|
"vue-tsc": "^3.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -481,6 +484,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@tauri-apps/api": {
|
"node_modules/@tauri-apps/api": {
|
||||||
"version": "2.11.1",
|
"version": "2.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz",
|
||||||
@@ -717,6 +727,15 @@
|
|||||||
"@tauri-apps/api": "^2.8.0"
|
"@tauri-apps/api": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-clipboard-manager": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tauri-apps/plugin-dialog": {
|
"node_modules/@tauri-apps/plugin-dialog": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz",
|
||||||
@@ -746,6 +765,31 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/chai": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/deep-eql": "*",
|
||||||
|
"assertion-error": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/deep-eql": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/estree": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.13.2",
|
"version": "24.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||||
@@ -756,6 +800,23 @@
|
|||||||
"undici-types": "~7.18.0"
|
"undici-types": "~7.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/whatwg-mimetype": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "6.0.7",
|
"version": "6.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
|
||||||
@@ -773,6 +834,129 @@
|
|||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/expect": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
|
"@types/chai": "^5.2.2",
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"chai": "^6.2.2",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/mocker": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"magic-string": "^0.30.21"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"msw": "^2.4.9",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"msw": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/pretty-format": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/runner": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/snapshot": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/spy": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/utils": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"convert-source-map": "^2.0.0",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@volar/language-core": {
|
"node_modules/@volar/language-core": {
|
||||||
"version": "2.4.28",
|
"version": "2.4.28",
|
||||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
||||||
@@ -984,6 +1168,46 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/assertion-error": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-image-size": {
|
||||||
|
"version": "0.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz",
|
||||||
|
"integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chai": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/convert-source-map": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
@@ -1012,12 +1236,29 @@
|
|||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-module-lexer": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/expect-type": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -1087,6 +1328,25 @@
|
|||||||
"node": ">8.0.0"
|
"node": ">8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/happy-dom": {
|
||||||
|
"version": "20.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.0.tgz",
|
||||||
|
"integrity": "sha512-XogN4asPd1a56di9prVG6bZxteNcXsZxxKmAvcEfc5Px5Ca2hMyMgk8wvqK2K1V8zXg40j9VANXsDaJYh9DeNA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": ">=20.0.0",
|
||||||
|
"@types/whatwg-mimetype": "^3.0.2",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"buffer-image-size": "^0.6.4",
|
||||||
|
"entities": "^7.0.1",
|
||||||
|
"whatwg-mimetype": "^3.0.0",
|
||||||
|
"ws": "^8.21.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.32.0",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||||
@@ -1406,6 +1666,20 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/obug": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/sxzz",
|
||||||
|
"https://opencollective.com/debug"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/opentype.js": {
|
"node_modules/opentype.js": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz",
|
||||||
@@ -1425,6 +1699,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1515,6 +1796,13 @@
|
|||||||
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/siginfo": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -1524,12 +1812,43 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stackback": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/std-env": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tiny-inflate": {
|
"node_modules/tiny-inflate": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tinybench": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tinyexec": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
@@ -1547,6 +1866,16 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyrainbow": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@@ -1654,6 +1983,96 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/expect": "4.1.10",
|
||||||
|
"@vitest/mocker": "4.1.10",
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"@vitest/runner": "4.1.10",
|
||||||
|
"@vitest/snapshot": "4.1.10",
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"es-module-lexer": "^2.0.0",
|
||||||
|
"expect-type": "^1.3.0",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"obug": "^2.1.1",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"std-env": "^4.0.0-rc.1",
|
||||||
|
"tinybench": "^2.9.0",
|
||||||
|
"tinyexec": "^1.0.2",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
|
"tinyrainbow": "^3.1.0",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
|
"why-is-node-running": "^2.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vitest": "vitest.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@edge-runtime/vm": "*",
|
||||||
|
"@opentelemetry/api": "^1.9.0",
|
||||||
|
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||||
|
"@vitest/browser-playwright": "4.1.10",
|
||||||
|
"@vitest/browser-preview": "4.1.10",
|
||||||
|
"@vitest/browser-webdriverio": "4.1.10",
|
||||||
|
"@vitest/coverage-istanbul": "4.1.10",
|
||||||
|
"@vitest/coverage-v8": "4.1.10",
|
||||||
|
"@vitest/ui": "4.1.10",
|
||||||
|
"happy-dom": "*",
|
||||||
|
"jsdom": "*",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@edge-runtime/vm": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@opentelemetry/api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-playwright": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-preview": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-webdriverio": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/coverage-istanbul": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/coverage-v8": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/ui": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"happy-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jsdom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vscode-uri": {
|
"node_modules/vscode-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||||
@@ -1720,6 +2139,55 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/why-is-node-running": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"siginfo": "^2.0.0",
|
||||||
|
"stackback": "0.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"why-is-node-running": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
||||||
|
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
|||||||
+5
-1
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "ai-control",
|
"name": "ai-control",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc -b && vite build",
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"test": "vitest run",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
"@tauri-apps/api": "^2.11.1",
|
"@tauri-apps/api": "^2.11.1",
|
||||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||||
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||||
"@tauri-apps/plugin-opener": "^2.5.4",
|
"@tauri-apps/plugin-opener": "^2.5.4",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
@@ -29,8 +31,10 @@
|
|||||||
"@types/node": "^24.13.2",
|
"@types/node": "^24.13.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"@vitejs/plugin-vue": "^6.0.7",
|
||||||
"@vue/tsconfig": "^0.9.1",
|
"@vue/tsconfig": "^0.9.1",
|
||||||
|
"happy-dom": "^20.11.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"vite": "^8.1.1",
|
"vite": "^8.1.1",
|
||||||
|
"vitest": "^4.1.10",
|
||||||
"vue-tsc": "^3.3.5"
|
"vue-tsc": "^3.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-224
@@ -2,236 +2,41 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Entwurf</title>
|
<title data-i18n="panel.windowPanel">Panel</title>
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
background: #1e1e2e;
|
|
||||||
color: #cdd6f4;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.panel-head {
|
|
||||||
flex: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
padding: 7px 8px 9px 14px;
|
|
||||||
background: #181825;
|
|
||||||
border-bottom: 1px solid #313244;
|
|
||||||
font:
|
|
||||||
500 12px/1 -apple-system,
|
|
||||||
system-ui,
|
|
||||||
sans-serif;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
.panel-titlerow {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.panel-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
.panel-title {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: #a6adc8;
|
|
||||||
}
|
|
||||||
.panel-title[contenteditable="true"] {
|
|
||||||
overflow: visible;
|
|
||||||
text-overflow: clip;
|
|
||||||
outline: none;
|
|
||||||
color: inherit;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
.panel-btn {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #a6adc8;
|
|
||||||
cursor: default;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 9px;
|
|
||||||
font: inherit;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.panel-btn:hover {
|
|
||||||
background: #313244;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
.panel-btn.copied {
|
|
||||||
color: #a6e3a1;
|
|
||||||
}
|
|
||||||
.panel-btn.error {
|
|
||||||
color: #f38ba8;
|
|
||||||
}
|
|
||||||
.panel-btn svg {
|
|
||||||
display: block;
|
|
||||||
stroke: currentColor;
|
|
||||||
stroke-width: 1.3;
|
|
||||||
fill: none;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
}
|
|
||||||
.panel-btn.active {
|
|
||||||
background: #313244;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
.panel-btn.changed {
|
|
||||||
color: #f9e2af;
|
|
||||||
}
|
|
||||||
.panel-editor {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
resize: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
padding: 16px 20px;
|
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
font-family: "JetBrains Mono", Menlo, monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.55;
|
|
||||||
}
|
|
||||||
.panel-editor[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.panel-lang {
|
|
||||||
margin-right: auto;
|
|
||||||
background: #313244;
|
|
||||||
color: #a6adc8;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 3px 4px;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 11px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
#panel-content {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 16px 20px;
|
|
||||||
user-select: text;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
cursor: text;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.55;
|
|
||||||
}
|
|
||||||
#panel-content::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
#panel-content::-webkit-scrollbar-thumb {
|
|
||||||
background: #313244;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
#panel-content.raw {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
font-family: "JetBrains Mono", Menlo, monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
#panel-content.md h1,
|
|
||||||
#panel-content.md h2,
|
|
||||||
#panel-content.md h3 {
|
|
||||||
line-height: 1.25;
|
|
||||||
margin: 1em 0 0.4em;
|
|
||||||
}
|
|
||||||
#panel-content.md h1 {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
#panel-content.md h2 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
#panel-content.md h3 {
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
#panel-content.md :first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
#panel-content.md p,
|
|
||||||
#panel-content.md ul,
|
|
||||||
#panel-content.md ol,
|
|
||||||
#panel-content.md pre,
|
|
||||||
#panel-content.md blockquote {
|
|
||||||
margin: 0 0 0.7em;
|
|
||||||
}
|
|
||||||
#panel-content.md code {
|
|
||||||
font-family: "JetBrains Mono", Menlo, monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
background: #313244;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 1px 5px;
|
|
||||||
}
|
|
||||||
#panel-content.md pre {
|
|
||||||
background: #11111b;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
#panel-content.md pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#panel-content.md blockquote {
|
|
||||||
border-left: 3px solid #313244;
|
|
||||||
padding-left: 12px;
|
|
||||||
color: #a6adc8;
|
|
||||||
}
|
|
||||||
#panel-content.md a {
|
|
||||||
color: #89b4fa;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="panel-topbar" data-tauri-drag-region>
|
||||||
|
<!-- Tabs kommen aus der Modul-Registry (panel-wiring). -->
|
||||||
|
<div class="panel-tabs" id="panel-tabs"></div>
|
||||||
|
<button class="winbtn" id="panel-dock" data-i18n-title="panel.dock">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5" /><path d="M9.5 2.5v11" /><path d="M4 8h3.4M6 6.2 7.8 8 6 9.8" /></svg>
|
||||||
|
</button>
|
||||||
|
<button class="winbtn osctl" id="win-min" data-i18n-aria="panel.minimize">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="2" y1="6.5" x2="10" y2="6.5" /></svg>
|
||||||
|
</button>
|
||||||
|
<button class="winbtn osctl" id="win-max" data-i18n-aria="panel.maximize">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="2.5" y="2.5" width="7" height="7" rx="1" /></svg>
|
||||||
|
</button>
|
||||||
|
<button class="winbtn close" id="panel-close" data-i18n-title="panel.closePanel">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M3 3l6 6M9 3l-6 6" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Modul-Container (<mode>-content) reihen sich hinter der Kopfzeile ein. -->
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div class="panel-titlerow" data-tauri-drag-region>
|
<div class="panel-titlerow" data-tauri-drag-region>
|
||||||
<span class="panel-title">Entwurf</span>
|
<span class="panel-title"></span>
|
||||||
<button class="panel-btn panel-edit" id="panel-title-edit" title="Titel bearbeiten">✎</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-actions">
|
|
||||||
<select class="panel-lang" id="panel-lang" title="Sprache der Rechtschreibprüfung">
|
|
||||||
<option value="de">DE</option>
|
|
||||||
<option value="en">EN</option>
|
|
||||||
<option value="en-GB">EN-GB</option>
|
|
||||||
<option value="fr">FR</option>
|
|
||||||
<option value="es">ES</option>
|
|
||||||
<option value="it">IT</option>
|
|
||||||
<option value="nl">NL</option>
|
|
||||||
</select>
|
|
||||||
<button class="panel-btn" id="panel-mode" title="Rohtext / gerendert">MD</button>
|
|
||||||
<button class="panel-btn" id="panel-content-edit" title="Entwurf bearbeiten (Cmd/Ctrl+Enter speichert, Esc verwirft)">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 16 16"><path d="M10.8 2.6 13.4 5.2 6 12.6l-3.1.5.5-3.1z" /><path d="M9.7 3.7 12.3 6.3" /></svg>
|
|
||||||
</button>
|
|
||||||
<button class="panel-btn" id="panel-copy" title="In die Zwischenablage kopieren">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 16 16"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5" /><path d="M10.5 3.2V3A1.5 1.5 0 0 0 9 1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h.2" /></svg>
|
|
||||||
</button>
|
|
||||||
<button class="panel-btn" id="panel-archive" title="In den Archiv-Ordner speichern">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1.5" y="2.5" width="13" height="3.5" rx="1" /><path d="M2.8 6v6.5a1 1 0 0 0 1 1h8.4a1 1 0 0 0 1-1V6" /><path d="M6.3 9h3.4" /></svg>
|
|
||||||
</button>
|
|
||||||
<button class="panel-btn" id="panel-dock" title="Wieder andocken">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5" /><path d="M9.5 2.5v11" /><path d="M4 8h3.4M6 6.2 7.8 8 6 9.8" /></svg>
|
|
||||||
</button>
|
|
||||||
<button class="panel-btn" id="panel-close" title="Panel schließen">
|
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M3 3l6 6M9 3l-6 6" /></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel-content" class="md"></div>
|
<div id="resize-grips" aria-hidden="true">
|
||||||
|
<div class="grip n" data-dir="North"></div>
|
||||||
|
<div class="grip s" data-dir="South"></div>
|
||||||
|
<div class="grip w" data-dir="West"></div>
|
||||||
|
<div class="grip e" data-dir="East"></div>
|
||||||
|
<div class="grip nw" data-dir="NorthWest"></div>
|
||||||
|
<div class="grip ne" data-dir="NorthEast"></div>
|
||||||
|
<div class="grip sw" data-dir="SouthWest"></div>
|
||||||
|
<div class="grip se" data-dir="SouthEast"></div>
|
||||||
|
</div>
|
||||||
<script type="module" src="/src/panel.ts"></script>
|
<script type="module" src="/src/panel.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-8
@@ -2,14 +2,7 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Projekte</title>
|
<title data-i18n="panel.windowProjects">Projekte</title>
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
Generated
+334
-5
@@ -30,10 +30,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ai-control"
|
name = "ai-control"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"glib",
|
"glib",
|
||||||
|
"gtk",
|
||||||
"keyring",
|
"keyring",
|
||||||
"ksni",
|
"ksni",
|
||||||
"log",
|
"log",
|
||||||
@@ -41,6 +42,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
|
"pulldown-cmark",
|
||||||
"rfd 0.15.4",
|
"rfd 0.15.4",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -48,6 +50,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-autostart",
|
"tauri-plugin-autostart",
|
||||||
|
"tauri-plugin-clipboard-manager",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -110,6 +113,27 @@ version = "1.0.103"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arboard"
|
||||||
|
version = "3.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||||
|
dependencies = [
|
||||||
|
"clipboard-win",
|
||||||
|
"image",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"wl-clipboard-rs",
|
||||||
|
"x11rb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@@ -659,6 +683,15 @@ dependencies = [
|
|||||||
"vec_map",
|
"vec_map",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -771,6 +804,12 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -1030,7 +1069,7 @@ checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"foldhash",
|
"foldhash 0.2.0",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
"selectors",
|
"selectors",
|
||||||
@@ -1178,6 +1217,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
@@ -1205,6 +1250,12 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -1250,6 +1301,12 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
@@ -1266,6 +1323,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1507,6 +1570,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -1689,6 +1762,17 @@ dependencies = [
|
|||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1698,6 +1782,15 @@ dependencies = [
|
|||||||
"ahash",
|
"ahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
@@ -1983,6 +2076,7 @@ dependencies = [
|
|||||||
"moxcms",
|
"moxcms",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
|
"tiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2387,6 +2481,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2673,6 +2776,16 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"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]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@@ -2733,6 +2846,17 @@ version = "2.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
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]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -2811,13 +2935,13 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist"
|
name = "plist"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
|
checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
"quick-xml",
|
"quick-xml 0.41.0",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
@@ -2995,12 +3119,36 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulldown-cmark"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"memchr",
|
||||||
|
"pulldown-cmark-escape",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulldown-cmark-escape"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pxfm"
|
name = "pxfm"
|
||||||
version = "0.1.29"
|
version = "0.1.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.39.4"
|
version = "0.39.4"
|
||||||
@@ -3010,6 +3158,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.41.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.46"
|
version = "1.0.46"
|
||||||
@@ -4076,6 +4233,21 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-clipboard-manager"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf"
|
||||||
|
dependencies = [
|
||||||
|
"arboard",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
@@ -4312,6 +4484,20 @@ dependencies = [
|
|||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||||
|
dependencies = [
|
||||||
|
"fax",
|
||||||
|
"flate2",
|
||||||
|
"half",
|
||||||
|
"quick-error",
|
||||||
|
"weezl",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.53"
|
version = "0.3.53"
|
||||||
@@ -4614,6 +4800,17 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -4684,6 +4881,12 @@ dependencies = [
|
|||||||
"unic-common",
|
"unic-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -4903,6 +5106,76 @@ dependencies = [
|
|||||||
"web-sys",
|
"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]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.103"
|
version = "0.3.103"
|
||||||
@@ -5005,6 +5278,12 @@ dependencies = [
|
|||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -5479,6 +5758,24 @@ version = "0.57.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
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]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
@@ -5559,6 +5856,23 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.8.28"
|
version = "0.8.28"
|
||||||
@@ -5749,6 +6063,21 @@ version = "1.0.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.12.0"
|
version = "5.12.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ai-control"
|
name = "ai-control"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
description = "Pool- und Session-Verwaltung für Claude Code"
|
description = "Pool- und Session-Verwaltung für Claude Code"
|
||||||
authors = ["marcus.hinz"]
|
authors = ["marcus.hinz"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -29,19 +29,26 @@ tauri = { version = "2.11.3", features = ["macos-private-api", "tray-icon", "ima
|
|||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
tauri-plugin-autostart = "2"
|
tauri-plugin-autostart = "2"
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
|
tauri-plugin-clipboard-manager = "2"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
portable-pty = "0.9"
|
portable-pty = "0.9"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
# Markdown→HTML beim Archivieren in Trilium (Notizen sind HTML).
|
||||||
|
pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] }
|
||||||
keyring = { version = "3", features = ["apple-native", "sync-secret-service", "windows-native"] }
|
keyring = { version = "3", features = ["apple-native", "sync-secret-service", "windows-native"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
zbus = "5"
|
zbus = "5"
|
||||||
glib = "0.18"
|
glib = "0.18"
|
||||||
|
gtk = "0.18"
|
||||||
rfd = { version = "0.15", default-features = false, features = ["gtk3"] }
|
rfd = { version = "0.15", default-features = false, features = ["gtk3"] }
|
||||||
ksni = "0.1"
|
ksni = "0.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
# Der Installations-Dialog läuft vor dem Tauri-Setup, also ohne AppHandle und
|
||||||
|
# damit ohne das Dialog-Plugin. Unter macOS braucht rfd kein Backend-Feature.
|
||||||
|
rfd = { version = "0.15", default-features = false }
|
||||||
objc2 = "0.6"
|
objc2 = "0.6"
|
||||||
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder", "NSRunningApplication"] }
|
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder", "NSRunningApplication"] }
|
||||||
objc2-foundation = { version = "0.3", features = ["NSString"] }
|
objc2-foundation = { version = "0.3", features = ["NSString"] }
|
||||||
|
|||||||
+65
-1
@@ -1,3 +1,67 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
// App-ACL-Manifest: erzeugt pro Command allow-/deny-Permissions. Sobald das
|
||||||
|
// Manifest existiert, gilt deny-by-default — jedes Command muss in einer
|
||||||
|
// Capability einem Fenster zugeteilt sein (capabilities/*.json), sonst
|
||||||
|
// lehnt Tauri den Aufruf ab. Neue Commands hier UND in der passenden
|
||||||
|
// Capability eintragen.
|
||||||
|
tauri_build::try_build(
|
||||||
|
tauri_build::Attributes::new().app_manifest(tauri_build::AppManifest::new().commands(&[
|
||||||
|
"list_projects",
|
||||||
|
"create_project_full",
|
||||||
|
"add_project",
|
||||||
|
"delete_preview",
|
||||||
|
"delete_project_scoped",
|
||||||
|
"project_work_dirs",
|
||||||
|
"set_project_dir",
|
||||||
|
"add_work_dir",
|
||||||
|
"remove_work_dir",
|
||||||
|
"list_pools",
|
||||||
|
"create_oauth_pool",
|
||||||
|
"create_apikey_pool",
|
||||||
|
"create_reference_pool",
|
||||||
|
"default_config_dir",
|
||||||
|
"rename_pool",
|
||||||
|
"delete_pool",
|
||||||
|
"assign_pool",
|
||||||
|
"unassign_pool",
|
||||||
|
"set_terminal_config",
|
||||||
|
"project_icon",
|
||||||
|
"pool_label",
|
||||||
|
"usage_stats",
|
||||||
|
"stop_project",
|
||||||
|
"restart_project",
|
||||||
|
"start_or_focus_cmd",
|
||||||
|
"open_main_window",
|
||||||
|
"quit_app",
|
||||||
|
"sync_setting",
|
||||||
|
"set_sync_setting",
|
||||||
|
"terminal_font_size",
|
||||||
|
"set_terminal_font_size",
|
||||||
|
"link_pool_runtime",
|
||||||
|
"oauth_login",
|
||||||
|
"keychain_status",
|
||||||
|
"set_apikey",
|
||||||
|
"trilium_status",
|
||||||
|
"trilium_setup",
|
||||||
|
"trilium_connect",
|
||||||
|
"trilium_open",
|
||||||
|
"trilium_disable",
|
||||||
|
"open_terminal",
|
||||||
|
"term_start",
|
||||||
|
"term_log",
|
||||||
|
"term_write",
|
||||||
|
"term_resize",
|
||||||
|
"buffer_read",
|
||||||
|
"enabled_modules",
|
||||||
|
"module_registry",
|
||||||
|
"set_module",
|
||||||
|
"commands_delete",
|
||||||
|
"todos_delete",
|
||||||
|
"todos_add",
|
||||||
|
"todos_update",
|
||||||
|
"search_run",
|
||||||
|
"open_panel_window",
|
||||||
|
])),
|
||||||
|
)
|
||||||
|
.expect("tauri-build");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "enables the default permissions",
|
|
||||||
"windows": [
|
|
||||||
"main",
|
|
||||||
"popup",
|
|
||||||
"term-*",
|
|
||||||
"panel-*"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"core:window:allow-start-dragging",
|
|
||||||
"core:window:allow-set-size",
|
|
||||||
"core:window:allow-minimize",
|
|
||||||
"core:window:allow-toggle-maximize",
|
|
||||||
"core:window:allow-close",
|
|
||||||
"core:window:allow-hide",
|
|
||||||
"core:window:allow-start-resize-dragging",
|
|
||||||
"autostart:default",
|
|
||||||
"dialog:default"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "main",
|
||||||
|
"description": "Hauptfenster: komplette Projekt-/Pool-Verwaltung.",
|
||||||
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-position",
|
||||||
|
"core:window:allow-outer-position",
|
||||||
|
"core:window:allow-inner-size",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-hide",
|
||||||
|
"core:window:allow-start-resize-dragging",
|
||||||
|
"autostart:default",
|
||||||
|
"dialog:default",
|
||||||
|
"clipboard-manager:allow-write-text",
|
||||||
|
"allow-list-projects",
|
||||||
|
"allow-create-project-full",
|
||||||
|
"allow-add-project",
|
||||||
|
"allow-project-work-dirs",
|
||||||
|
"allow-set-project-dir",
|
||||||
|
"allow-add-work-dir",
|
||||||
|
"allow-remove-work-dir",
|
||||||
|
"allow-list-pools",
|
||||||
|
"allow-create-oauth-pool",
|
||||||
|
"allow-create-apikey-pool",
|
||||||
|
"allow-create-reference-pool",
|
||||||
|
"allow-default-config-dir",
|
||||||
|
"allow-rename-pool",
|
||||||
|
"allow-delete-pool",
|
||||||
|
"allow-assign-pool",
|
||||||
|
"allow-unassign-pool",
|
||||||
|
"allow-set-terminal-config",
|
||||||
|
"allow-project-icon",
|
||||||
|
"allow-usage-stats",
|
||||||
|
"allow-stop-project",
|
||||||
|
"allow-restart-project",
|
||||||
|
"allow-open-terminal",
|
||||||
|
"allow-sync-setting",
|
||||||
|
"allow-set-sync-setting",
|
||||||
|
"allow-link-pool-runtime",
|
||||||
|
"allow-oauth-login",
|
||||||
|
"allow-keychain-status",
|
||||||
|
"allow-set-apikey",
|
||||||
|
"allow-delete-preview",
|
||||||
|
"allow-delete-project-scoped",
|
||||||
|
"allow-module-registry",
|
||||||
|
"allow-set-module",
|
||||||
|
"allow-trilium-status",
|
||||||
|
"allow-trilium-setup",
|
||||||
|
"allow-trilium-connect",
|
||||||
|
"allow-trilium-open",
|
||||||
|
"allow-trilium-disable"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "panel",
|
||||||
|
"description": "Abgelöstes Panel-Fenster: nur Panel-/Archiv-Kanäle, keine PTY, keine Verwaltung.",
|
||||||
|
"windows": [
|
||||||
|
"panel-*"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-position",
|
||||||
|
"core:window:allow-outer-position",
|
||||||
|
"core:window:allow-inner-size",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-hide",
|
||||||
|
"core:window:allow-start-resize-dragging",
|
||||||
|
"autostart:default",
|
||||||
|
"dialog:default",
|
||||||
|
"clipboard-manager:allow-write-text",
|
||||||
|
"allow-buffer-read",
|
||||||
|
"allow-commands-delete",
|
||||||
|
"allow-enabled-modules",
|
||||||
|
"allow-list-projects",
|
||||||
|
"allow-trilium-open",
|
||||||
|
"allow-search-run",
|
||||||
|
"allow-term-log",
|
||||||
|
"allow-todos-add",
|
||||||
|
"allow-todos-delete",
|
||||||
|
"allow-todos-update"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "popup",
|
||||||
|
"description": "Tray-Popup: Projektliste anzeigen und starten.",
|
||||||
|
"windows": [
|
||||||
|
"popup"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-position",
|
||||||
|
"core:window:allow-outer-position",
|
||||||
|
"core:window:allow-inner-size",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-hide",
|
||||||
|
"core:window:allow-start-resize-dragging",
|
||||||
|
"autostart:default",
|
||||||
|
"dialog:default",
|
||||||
|
"clipboard-manager:allow-write-text",
|
||||||
|
"allow-list-projects",
|
||||||
|
"allow-project-icon",
|
||||||
|
"allow-start-or-focus-cmd",
|
||||||
|
"allow-open-main-window",
|
||||||
|
"allow-quit-app"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "terminal",
|
||||||
|
"description": "Terminal-Fenster: PTY und angedocktes Panel.",
|
||||||
|
"windows": [
|
||||||
|
"term-*"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-position",
|
||||||
|
"core:window:allow-outer-position",
|
||||||
|
"core:window:allow-inner-size",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-hide",
|
||||||
|
"core:window:allow-start-resize-dragging",
|
||||||
|
"autostart:default",
|
||||||
|
"dialog:default",
|
||||||
|
"clipboard-manager:allow-write-text",
|
||||||
|
"allow-buffer-read",
|
||||||
|
"allow-commands-delete",
|
||||||
|
"allow-enabled-modules",
|
||||||
|
"allow-list-projects",
|
||||||
|
"allow-open-panel-window",
|
||||||
|
"allow-trilium-open",
|
||||||
|
"allow-pool-label",
|
||||||
|
"allow-project-icon",
|
||||||
|
"allow-search-run",
|
||||||
|
"allow-set-terminal-font-size",
|
||||||
|
"allow-term-log",
|
||||||
|
"allow-term-resize",
|
||||||
|
"allow-term-start",
|
||||||
|
"allow-term-write",
|
||||||
|
"allow-terminal-font-size",
|
||||||
|
"allow-todos-add",
|
||||||
|
"allow-todos-delete",
|
||||||
|
"allow-todos-update"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-add-project"
|
||||||
|
description = "Enables the add_project command without any pre-configured scope."
|
||||||
|
commands.allow = ["add_project"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-add-project"
|
||||||
|
description = "Denies the add_project command without any pre-configured scope."
|
||||||
|
commands.deny = ["add_project"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-add-work-dir"
|
||||||
|
description = "Enables the add_work_dir command without any pre-configured scope."
|
||||||
|
commands.allow = ["add_work_dir"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-add-work-dir"
|
||||||
|
description = "Denies the add_work_dir command without any pre-configured scope."
|
||||||
|
commands.deny = ["add_work_dir"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-create-doc"
|
||||||
|
description = "Enables the archive_create_doc command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_create_doc"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-create-doc"
|
||||||
|
description = "Denies the archive_create_doc command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_create_doc"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-create-folder"
|
||||||
|
description = "Enables the archive_create_folder command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_create_folder"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-create-folder"
|
||||||
|
description = "Denies the archive_create_folder command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_create_folder"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-delete"
|
||||||
|
description = "Enables the archive_delete command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_delete"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-delete"
|
||||||
|
description = "Denies the archive_delete command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_delete"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-move"
|
||||||
|
description = "Enables the archive_move command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_move"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-move"
|
||||||
|
description = "Denies the archive_move command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_move"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-move-folder"
|
||||||
|
description = "Enables the archive_move_folder command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_move_folder"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-move-folder"
|
||||||
|
description = "Denies the archive_move_folder command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_move_folder"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-archive-rename"
|
||||||
|
description = "Enables the archive_rename command without any pre-configured scope."
|
||||||
|
commands.allow = ["archive_rename"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-archive-rename"
|
||||||
|
description = "Denies the archive_rename command without any pre-configured scope."
|
||||||
|
commands.deny = ["archive_rename"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-assign-pool"
|
||||||
|
description = "Enables the assign_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["assign_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-assign-pool"
|
||||||
|
description = "Denies the assign_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["assign_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-buffer-read"
|
||||||
|
description = "Enables the buffer_read command without any pre-configured scope."
|
||||||
|
commands.allow = ["buffer_read"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-buffer-read"
|
||||||
|
description = "Denies the buffer_read command without any pre-configured scope."
|
||||||
|
commands.deny = ["buffer_read"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-commands-delete"
|
||||||
|
description = "Enables the commands_delete command without any pre-configured scope."
|
||||||
|
commands.allow = ["commands_delete"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-commands-delete"
|
||||||
|
description = "Denies the commands_delete command without any pre-configured scope."
|
||||||
|
commands.deny = ["commands_delete"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-commands-read"
|
||||||
|
description = "Enables the commands_read command without any pre-configured scope."
|
||||||
|
commands.allow = ["commands_read"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-commands-read"
|
||||||
|
description = "Denies the commands_read command without any pre-configured scope."
|
||||||
|
commands.deny = ["commands_read"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-create-apikey-pool"
|
||||||
|
description = "Enables the create_apikey_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["create_apikey_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-create-apikey-pool"
|
||||||
|
description = "Denies the create_apikey_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["create_apikey_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-create-oauth-pool"
|
||||||
|
description = "Enables the create_oauth_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["create_oauth_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-create-oauth-pool"
|
||||||
|
description = "Denies the create_oauth_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["create_oauth_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-create-project-full"
|
||||||
|
description = "Enables the create_project_full command without any pre-configured scope."
|
||||||
|
commands.allow = ["create_project_full"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-create-project-full"
|
||||||
|
description = "Denies the create_project_full command without any pre-configured scope."
|
||||||
|
commands.deny = ["create_project_full"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-create-reference-pool"
|
||||||
|
description = "Enables the create_reference_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["create_reference_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-create-reference-pool"
|
||||||
|
description = "Denies the create_reference_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["create_reference_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-default-config-dir"
|
||||||
|
description = "Enables the default_config_dir command without any pre-configured scope."
|
||||||
|
commands.allow = ["default_config_dir"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-default-config-dir"
|
||||||
|
description = "Denies the default_config_dir command without any pre-configured scope."
|
||||||
|
commands.deny = ["default_config_dir"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-delete-pool"
|
||||||
|
description = "Enables the delete_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["delete_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-delete-pool"
|
||||||
|
description = "Denies the delete_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["delete_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-delete-preview"
|
||||||
|
description = "Enables the delete_preview command without any pre-configured scope."
|
||||||
|
commands.allow = ["delete_preview"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-delete-preview"
|
||||||
|
description = "Denies the delete_preview command without any pre-configured scope."
|
||||||
|
commands.deny = ["delete_preview"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-delete-project-scoped"
|
||||||
|
description = "Enables the delete_project_scoped command without any pre-configured scope."
|
||||||
|
commands.allow = ["delete_project_scoped"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-delete-project-scoped"
|
||||||
|
description = "Denies the delete_project_scoped command without any pre-configured scope."
|
||||||
|
commands.deny = ["delete_project_scoped"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-enabled-modules"
|
||||||
|
description = "Enables the enabled_modules command without any pre-configured scope."
|
||||||
|
commands.allow = ["enabled_modules"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-enabled-modules"
|
||||||
|
description = "Denies the enabled_modules command without any pre-configured scope."
|
||||||
|
commands.deny = ["enabled_modules"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-keychain-status"
|
||||||
|
description = "Enables the keychain_status command without any pre-configured scope."
|
||||||
|
commands.allow = ["keychain_status"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-keychain-status"
|
||||||
|
description = "Denies the keychain_status command without any pre-configured scope."
|
||||||
|
commands.deny = ["keychain_status"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-link-pool-runtime"
|
||||||
|
description = "Enables the link_pool_runtime command without any pre-configured scope."
|
||||||
|
commands.allow = ["link_pool_runtime"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-link-pool-runtime"
|
||||||
|
description = "Denies the link_pool_runtime command without any pre-configured scope."
|
||||||
|
commands.deny = ["link_pool_runtime"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-list-pools"
|
||||||
|
description = "Enables the list_pools command without any pre-configured scope."
|
||||||
|
commands.allow = ["list_pools"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-list-pools"
|
||||||
|
description = "Denies the list_pools command without any pre-configured scope."
|
||||||
|
commands.deny = ["list_pools"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-list-projects"
|
||||||
|
description = "Enables the list_projects command without any pre-configured scope."
|
||||||
|
commands.allow = ["list_projects"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-list-projects"
|
||||||
|
description = "Denies the list_projects command without any pre-configured scope."
|
||||||
|
commands.deny = ["list_projects"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-module-registry"
|
||||||
|
description = "Enables the module_registry command without any pre-configured scope."
|
||||||
|
commands.allow = ["module_registry"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-module-registry"
|
||||||
|
description = "Denies the module_registry command without any pre-configured scope."
|
||||||
|
commands.deny = ["module_registry"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-oauth-login"
|
||||||
|
description = "Enables the oauth_login command without any pre-configured scope."
|
||||||
|
commands.allow = ["oauth_login"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-oauth-login"
|
||||||
|
description = "Denies the oauth_login command without any pre-configured scope."
|
||||||
|
commands.deny = ["oauth_login"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-open-main-window"
|
||||||
|
description = "Enables the open_main_window command without any pre-configured scope."
|
||||||
|
commands.allow = ["open_main_window"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-open-main-window"
|
||||||
|
description = "Denies the open_main_window command without any pre-configured scope."
|
||||||
|
commands.deny = ["open_main_window"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-open-panel-window"
|
||||||
|
description = "Enables the open_panel_window command without any pre-configured scope."
|
||||||
|
commands.allow = ["open_panel_window"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-open-panel-window"
|
||||||
|
description = "Denies the open_panel_window command without any pre-configured scope."
|
||||||
|
commands.deny = ["open_panel_window"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-open-terminal"
|
||||||
|
description = "Enables the open_terminal command without any pre-configured scope."
|
||||||
|
commands.allow = ["open_terminal"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-open-terminal"
|
||||||
|
description = "Denies the open_terminal command without any pre-configured scope."
|
||||||
|
commands.deny = ["open_terminal"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-panel-read"
|
||||||
|
description = "Enables the panel_read command without any pre-configured scope."
|
||||||
|
commands.allow = ["panel_read"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-panel-read"
|
||||||
|
description = "Denies the panel_read command without any pre-configured scope."
|
||||||
|
commands.deny = ["panel_read"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-pool-label"
|
||||||
|
description = "Enables the pool_label command without any pre-configured scope."
|
||||||
|
commands.allow = ["pool_label"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-pool-label"
|
||||||
|
description = "Denies the pool_label command without any pre-configured scope."
|
||||||
|
commands.deny = ["pool_label"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-project-icon"
|
||||||
|
description = "Enables the project_icon command without any pre-configured scope."
|
||||||
|
commands.allow = ["project_icon"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-project-icon"
|
||||||
|
description = "Denies the project_icon command without any pre-configured scope."
|
||||||
|
commands.deny = ["project_icon"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-project-work-dirs"
|
||||||
|
description = "Enables the project_work_dirs command without any pre-configured scope."
|
||||||
|
commands.allow = ["project_work_dirs"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-project-work-dirs"
|
||||||
|
description = "Denies the project_work_dirs command without any pre-configured scope."
|
||||||
|
commands.deny = ["project_work_dirs"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-quit-app"
|
||||||
|
description = "Enables the quit_app command without any pre-configured scope."
|
||||||
|
commands.allow = ["quit_app"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-quit-app"
|
||||||
|
description = "Denies the quit_app command without any pre-configured scope."
|
||||||
|
commands.deny = ["quit_app"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-remove-work-dir"
|
||||||
|
description = "Enables the remove_work_dir command without any pre-configured scope."
|
||||||
|
commands.allow = ["remove_work_dir"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-remove-work-dir"
|
||||||
|
description = "Denies the remove_work_dir command without any pre-configured scope."
|
||||||
|
commands.deny = ["remove_work_dir"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-rename-pool"
|
||||||
|
description = "Enables the rename_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["rename_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-rename-pool"
|
||||||
|
description = "Denies the rename_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["rename_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-restart-project"
|
||||||
|
description = "Enables the restart_project command without any pre-configured scope."
|
||||||
|
commands.allow = ["restart_project"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-restart-project"
|
||||||
|
description = "Denies the restart_project command without any pre-configured scope."
|
||||||
|
commands.deny = ["restart_project"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-search-read"
|
||||||
|
description = "Enables the search_read command without any pre-configured scope."
|
||||||
|
commands.allow = ["search_read"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-search-read"
|
||||||
|
description = "Denies the search_read command without any pre-configured scope."
|
||||||
|
commands.deny = ["search_read"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-search-run"
|
||||||
|
description = "Enables the search_run command without any pre-configured scope."
|
||||||
|
commands.allow = ["search_run"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-search-run"
|
||||||
|
description = "Denies the search_run command without any pre-configured scope."
|
||||||
|
commands.deny = ["search_run"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-apikey"
|
||||||
|
description = "Enables the set_apikey command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_apikey"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-apikey"
|
||||||
|
description = "Denies the set_apikey command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_apikey"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-module"
|
||||||
|
description = "Enables the set_module command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_module"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-module"
|
||||||
|
description = "Denies the set_module command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_module"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-project-dir"
|
||||||
|
description = "Enables the set_project_dir command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_project_dir"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-project-dir"
|
||||||
|
description = "Denies the set_project_dir command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_project_dir"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-sync-setting"
|
||||||
|
description = "Enables the set_sync_setting command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_sync_setting"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-sync-setting"
|
||||||
|
description = "Denies the set_sync_setting command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_sync_setting"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-terminal-config"
|
||||||
|
description = "Enables the set_terminal_config command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_terminal_config"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-terminal-config"
|
||||||
|
description = "Denies the set_terminal_config command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_terminal_config"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-terminal-font-size"
|
||||||
|
description = "Enables the set_terminal_font_size command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_terminal_font_size"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-terminal-font-size"
|
||||||
|
description = "Denies the set_terminal_font_size command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_terminal_font_size"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-set-todo"
|
||||||
|
description = "Enables the set_todo command without any pre-configured scope."
|
||||||
|
commands.allow = ["set_todo"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-set-todo"
|
||||||
|
description = "Denies the set_todo command without any pre-configured scope."
|
||||||
|
commands.deny = ["set_todo"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-start-or-focus-cmd"
|
||||||
|
description = "Enables the start_or_focus_cmd command without any pre-configured scope."
|
||||||
|
commands.allow = ["start_or_focus_cmd"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-start-or-focus-cmd"
|
||||||
|
description = "Denies the start_or_focus_cmd command without any pre-configured scope."
|
||||||
|
commands.deny = ["start_or_focus_cmd"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-stop-project"
|
||||||
|
description = "Enables the stop_project command without any pre-configured scope."
|
||||||
|
commands.allow = ["stop_project"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-stop-project"
|
||||||
|
description = "Denies the stop_project command without any pre-configured scope."
|
||||||
|
commands.deny = ["stop_project"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-sync-setting"
|
||||||
|
description = "Enables the sync_setting command without any pre-configured scope."
|
||||||
|
commands.allow = ["sync_setting"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-sync-setting"
|
||||||
|
description = "Denies the sync_setting command without any pre-configured scope."
|
||||||
|
commands.deny = ["sync_setting"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-term-log"
|
||||||
|
description = "Enables the term_log command without any pre-configured scope."
|
||||||
|
commands.allow = ["term_log"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-term-log"
|
||||||
|
description = "Denies the term_log command without any pre-configured scope."
|
||||||
|
commands.deny = ["term_log"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-term-resize"
|
||||||
|
description = "Enables the term_resize command without any pre-configured scope."
|
||||||
|
commands.allow = ["term_resize"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-term-resize"
|
||||||
|
description = "Denies the term_resize command without any pre-configured scope."
|
||||||
|
commands.deny = ["term_resize"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-term-start"
|
||||||
|
description = "Enables the term_start command without any pre-configured scope."
|
||||||
|
commands.allow = ["term_start"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-term-start"
|
||||||
|
description = "Denies the term_start command without any pre-configured scope."
|
||||||
|
commands.deny = ["term_start"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-term-write"
|
||||||
|
description = "Enables the term_write command without any pre-configured scope."
|
||||||
|
commands.allow = ["term_write"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-term-write"
|
||||||
|
description = "Denies the term_write command without any pre-configured scope."
|
||||||
|
commands.deny = ["term_write"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-terminal-font-size"
|
||||||
|
description = "Enables the terminal_font_size command without any pre-configured scope."
|
||||||
|
commands.allow = ["terminal_font_size"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-terminal-font-size"
|
||||||
|
description = "Denies the terminal_font_size command without any pre-configured scope."
|
||||||
|
commands.deny = ["terminal_font_size"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-todo-state"
|
||||||
|
description = "Enables the todo_state command without any pre-configured scope."
|
||||||
|
commands.allow = ["todo_state"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-todo-state"
|
||||||
|
description = "Denies the todo_state command without any pre-configured scope."
|
||||||
|
commands.deny = ["todo_state"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-todos-add"
|
||||||
|
description = "Enables the todos_add command without any pre-configured scope."
|
||||||
|
commands.allow = ["todos_add"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-todos-add"
|
||||||
|
description = "Denies the todos_add command without any pre-configured scope."
|
||||||
|
commands.deny = ["todos_add"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-todos-delete"
|
||||||
|
description = "Enables the todos_delete command without any pre-configured scope."
|
||||||
|
commands.allow = ["todos_delete"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-todos-delete"
|
||||||
|
description = "Denies the todos_delete command without any pre-configured scope."
|
||||||
|
commands.deny = ["todos_delete"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-todos-update"
|
||||||
|
description = "Enables the todos_update command without any pre-configured scope."
|
||||||
|
commands.allow = ["todos_update"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-todos-update"
|
||||||
|
description = "Denies the todos_update command without any pre-configured scope."
|
||||||
|
commands.deny = ["todos_update"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-trilium-connect"
|
||||||
|
description = "Enables the trilium_connect command without any pre-configured scope."
|
||||||
|
commands.allow = ["trilium_connect"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-trilium-connect"
|
||||||
|
description = "Denies the trilium_connect command without any pre-configured scope."
|
||||||
|
commands.deny = ["trilium_connect"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-trilium-disable"
|
||||||
|
description = "Enables the trilium_disable command without any pre-configured scope."
|
||||||
|
commands.allow = ["trilium_disable"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-trilium-disable"
|
||||||
|
description = "Denies the trilium_disable command without any pre-configured scope."
|
||||||
|
commands.deny = ["trilium_disable"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-trilium-open"
|
||||||
|
description = "Enables the trilium_open command without any pre-configured scope."
|
||||||
|
commands.allow = ["trilium_open"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-trilium-open"
|
||||||
|
description = "Denies the trilium_open command without any pre-configured scope."
|
||||||
|
commands.deny = ["trilium_open"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-trilium-setup"
|
||||||
|
description = "Enables the trilium_setup command without any pre-configured scope."
|
||||||
|
commands.allow = ["trilium_setup"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-trilium-setup"
|
||||||
|
description = "Denies the trilium_setup command without any pre-configured scope."
|
||||||
|
commands.deny = ["trilium_setup"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-trilium-status"
|
||||||
|
description = "Enables the trilium_status command without any pre-configured scope."
|
||||||
|
commands.allow = ["trilium_status"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-trilium-status"
|
||||||
|
description = "Denies the trilium_status command without any pre-configured scope."
|
||||||
|
commands.deny = ["trilium_status"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-unassign-pool"
|
||||||
|
description = "Enables the unassign_pool command without any pre-configured scope."
|
||||||
|
commands.allow = ["unassign_pool"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-unassign-pool"
|
||||||
|
description = "Denies the unassign_pool command without any pre-configured scope."
|
||||||
|
commands.deny = ["unassign_pool"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-usage-stats"
|
||||||
|
description = "Enables the usage_stats command without any pre-configured scope."
|
||||||
|
commands.allow = ["usage_stats"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-usage-stats"
|
||||||
|
description = "Denies the usage_stats command without any pre-configured scope."
|
||||||
|
commands.deny = ["usage_stats"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-wiki-read"
|
||||||
|
description = "Enables the wiki_read command without any pre-configured scope."
|
||||||
|
commands.allow = ["wiki_read"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-wiki-read"
|
||||||
|
description = "Denies the wiki_read command without any pre-configured scope."
|
||||||
|
commands.deny = ["wiki_read"]
|
||||||
@@ -1,25 +1,35 @@
|
|||||||
---
|
---
|
||||||
name: panel
|
name: panel
|
||||||
description: Beim Entwerfen längerer Texte (ADR, E-Mail, Dokument, Spezifikation, Commit-Message, Textbaustein) das MCP-Tool write_panel nutzen, statt den Text als Fließtext in den Chat zu schreiben — dann ist er im ai-control-Panel mit Maus selektier- und kopierbar. Auch auf ausdrückliche Bitte nutzen ("ins Panel", "ins Panel schreiben").
|
description: Beim Entwerfen längerer Texte (ADR, E-Mail, Dokument, Spezifikation, Commit-Message, Textbaustein) das MCP-Tool write_panel nutzen, statt den Text als Fließtext in den Chat zu schreiben; für eine bestehende Datei write_panel mit path aufrufen. Befehle, die der Nutzer ausführen soll, IMMER über write_commands als kopierbare Kacheln ausgeben statt als Codeblock im Chat. Will der Nutzer die Befehlsliste sehen ("zeig die Befehle", "zeige die Befehlsliste"), show_commands aufrufen. Auch auf ausdrückliche Bitte nutzen ("ins Panel", "ins Panel schreiben").
|
||||||
---
|
---
|
||||||
|
|
||||||
# Panel
|
# Panel
|
||||||
|
|
||||||
Claude Code läuft im TUI; sichtbarer Text lässt sich dort nicht sauber selektieren und kopieren. Die ai-control-App zeigt Text in einem andockbaren Panel neben dem Terminal an — selektierbar, mit Copy-Button. Der Kanal dahin ist das MCP-Tool `write_panel` (Server `aicontrol`).
|
Claude Code läuft im TUI; sichtbarer Text lässt sich dort nicht sauber selektieren und kopieren. Die ai-control-App zeigt Inhalte in einem andockbaren Panel neben dem Terminal an — selektierbar, mit Copy-Buttons. Die Kanäle dahin sind die MCP-Tools `write_panel` und `write_commands` (Server `text-panel`).
|
||||||
|
|
||||||
## Wann
|
## Entwürfe — `write_panel`
|
||||||
|
|
||||||
- Automatisch, sobald ein längerer Fließtext oder ein Dokument entworfen wird (ADR, E-Mail, Spezifikation, Commit-Message, Textbaustein).
|
- Automatisch, sobald ein längerer Fließtext oder ein Dokument entworfen wird (ADR, E-Mail, Spezifikation, Commit-Message, Textbaustein); außerdem auf ausdrückliche Bitte, etwas ins Panel zu legen.
|
||||||
- Auf ausdrückliche Bitte, etwas ins Panel zu legen.
|
- Neuer Text: vollständigen Entwurf als Argument `text` (Markdown-Rohtext, das Panel rendert ihn).
|
||||||
|
- Bestehende Datei: **immer** `path` statt `text` übergeben — der Server liest die Datei selbst von der Platte, der Inhalt wird nicht abgetippt.
|
||||||
|
- Den Text nicht zusätzlich als Fließtext in den Chat schreiben — im Chat nur kurz bestätigen (z. B. „Entwurf im Panel.").
|
||||||
|
- Nicht für normale Chat-Antworten, kurze Bestätigungen oder Code im laufenden Dialog.
|
||||||
|
|
||||||
Nicht für normale Chat-Antworten, kurze Bestätigungen oder Code im laufenden Dialog.
|
## Befehle — `write_commands`
|
||||||
|
|
||||||
## Vorgehen
|
- IMMER nutzen, wenn ein oder mehrere Shell-Befehle für den Nutzer bestimmt sind (er soll sie ausführen) — statt sie als Codeblock in den Chat zu schreiben.
|
||||||
|
- Argument `commands`: Array in Ausführungsreihenfolge, je Eintrag `cmd` (exakt ausführbar) und optional `note` (Kurznotiz).
|
||||||
|
- Das Panel zeigt sie als Kacheln mit Copy-Button, angehängt an die Befehls-History der Session (flüchtig, startet mit jeder Session leer); im Chat nur kurz einordnen, den Befehl nicht doppelt ausgeben.
|
||||||
|
- Nicht für Befehle, die Claude selbst ausführt, oder für Code, der nur erklärt wird.
|
||||||
|
|
||||||
1. `write_panel` mit dem vollständigen Entwurf als Argument `text` aufrufen (Markdown-Rohtext, das Panel rendert ihn). Das Panel blendet sich beim Schreiben automatisch ein.
|
## Befehlsliste zeigen — `show_commands`
|
||||||
2. Den Text **nicht** zusätzlich als Fließtext in den Chat schreiben — im Chat nur kurz bestätigen (z. B. „Entwurf im Panel.").
|
|
||||||
3. Ist das Tool nicht verfügbar (Terminal außerhalb von ai-control), den Text normal im Chat ausgeben.
|
- Nutzen, wenn der Nutzer die bisherige Befehlsliste sehen will („zeig die Befehle", „zeige die Befehlsliste"): schaltet das Panel auf die Kachel-Ansicht der History, ohne etwas anzuhängen. Keine Argumente.
|
||||||
|
|
||||||
## Panel leer öffnen
|
## Panel leer öffnen
|
||||||
|
|
||||||
Soll das Panel ohne konkreten Inhalt geöffnet werden („mach das Panel auf", „Panel öffnen"), `write_panel` mit dem neutralen Platzhalter `Panel` aufrufen — leerer Text blendet das Panel nicht ein. Keinen erklärenden, werbenden oder ausgedachten Einleitungstext erfinden.
|
Soll das Panel ohne konkreten Inhalt geöffnet werden („mach das Panel auf", „Panel öffnen"), `write_panel` mit dem neutralen Platzhalter `Panel` aufrufen — leerer Text blendet das Panel nicht ein. Keinen erklärenden, werbenden oder ausgedachten Einleitungstext erfinden.
|
||||||
|
|
||||||
|
## Fallback
|
||||||
|
|
||||||
|
Sind die Tools nicht verfügbar (Terminal außerhalb von ai-control), Text bzw. Befehle normal im Chat ausgeben.
|
||||||
|
|||||||
+121
-84
@@ -4,13 +4,14 @@
|
|||||||
|
|
||||||
use crate::domain::paths::Paths;
|
use crate::domain::paths::Paths;
|
||||||
use crate::domain::pool::provision_pools_for_panel;
|
use crate::domain::pool::provision_pools_for_panel;
|
||||||
use crate::domain::project::terminal_config;
|
use crate::domain::project::project_config;
|
||||||
use crate::domain::watcher::spawn_session_watcher;
|
use crate::domain::watcher::spawn_session_watcher;
|
||||||
use crate::platform::Anchor;
|
use crate::platform::Anchor;
|
||||||
use crate::{commands, terminal};
|
use crate::{commands, terminal};
|
||||||
|
|
||||||
/// Sperrt das Auto-Hide des Popups bei Fokusverlust für einen Moment nach dem
|
/// Sperrt das Auto-Hide des Popups bei Fokusverlust nach dem Anzeigen, bis das
|
||||||
/// Anzeigen (KDE-Erststart-Race). Als Tauri-State geführt.
|
/// Popup einmal Focused(true) gemeldet hat (KDE feuert nach show/set_focus ein
|
||||||
|
/// spurioses Focused(false)). Als Tauri-State geführt.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) struct PopupBlurGuard(pub(crate) std::sync::Arc<std::sync::atomic::AtomicBool>);
|
pub(crate) struct PopupBlurGuard(pub(crate) std::sync::Arc<std::sync::atomic::AtomicBool>);
|
||||||
|
|
||||||
@@ -60,29 +61,28 @@ pub(crate) fn show_popup(app: &tauri::AppHandle, anchor: Anchor) {
|
|||||||
};
|
};
|
||||||
let _ = w.set_position(tauri::PhysicalPosition::new(x, y));
|
let _ = w.set_position(tauri::PhysicalPosition::new(x, y));
|
||||||
}
|
}
|
||||||
// SNI (KDE/XFCE/Cinnamon): Activate liefert keine brauchbaren
|
// SNI (KDE/XFCE/Cinnamon): Activate-Koordinaten, wenn der Host welche
|
||||||
// Koordinaten, aber der Zeiger sitzt beim Klick auf dem Icon.
|
// mitgibt (Cinnamon); sonst Zeigerposition — der Zeiger sitzt beim
|
||||||
Anchor::Cursor => {
|
// Klick auf dem Icon. Unter Wayland liefert cursor_position (0,0).
|
||||||
|
Anchor::Click { x, y } => {
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
let _ = (x, y);
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
|
// Gelöst wird die Sperre vom ersten Focused(true) im Event-Handler.
|
||||||
if let Some(g) = win.try_state::<PopupBlurGuard>() {
|
if let Some(g) = win.try_state::<PopupBlurGuard>() {
|
||||||
g.0.store(true, std::sync::atomic::Ordering::SeqCst);
|
g.0.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
match win.cursor_position() {
|
if (x, y) != (0, 0) {
|
||||||
Ok(p) => position_popup(&w, p.x as i32, p.y as i32),
|
position_popup(&w, x, y);
|
||||||
Err(_) => {
|
} else {
|
||||||
let _ = w.center();
|
match win.cursor_position() {
|
||||||
|
Ok(p) => position_popup(&w, p.x as i32, p.y as i32),
|
||||||
|
Err(_) => {
|
||||||
|
let _ = w.center();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Guard nach kurzer Zeit lösen — danach schließt Fokusverlust wieder
|
|
||||||
// normal.
|
|
||||||
let g2 = win.clone();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(350));
|
|
||||||
if let Some(g) = g2.try_state::<PopupBlurGuard>() {
|
|
||||||
g.0.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// GNOME-Extension/KWin: der Compositor positioniert selbst.
|
// GNOME-Extension/KWin: der Compositor positioniert selbst.
|
||||||
@@ -125,6 +125,10 @@ pub(crate) fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||||
|
|
||||||
|
// Alt-Layout migrieren: ai-control.json → .ai-control/config.json,
|
||||||
|
// Pool-Zuordnung in die Registry, Icons in den Projekt-Config-Ordner.
|
||||||
|
crate::domain::project::migrate_layout_in(&Paths::real())?;
|
||||||
|
|
||||||
// Session-Watcher: synct bei Session-Ende (Prozess verschwindet).
|
// Session-Watcher: synct bei Session-Ende (Prozess verschwindet).
|
||||||
spawn_session_watcher();
|
spawn_session_watcher();
|
||||||
|
|
||||||
@@ -139,7 +143,7 @@ pub(crate) fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
// Fenster im Code statt in tauri.conf.json, damit der Terminal-Prozess
|
// Fenster im Code statt in tauri.conf.json, damit der Terminal-Prozess
|
||||||
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
||||||
let main_win = tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
let main_win = tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
||||||
.title("ai-control")
|
.title("aICentral")
|
||||||
.inner_size(800.0, 600.0)
|
.inner_size(800.0, 600.0)
|
||||||
// Fenster-Icon (_NET_WM_ICON) — deckt X11-DEs wie XFCE direkt ab,
|
// Fenster-Icon (_NET_WM_ICON) — deckt X11-DEs wie XFCE direkt ab,
|
||||||
// unabhängig von der .desktop-Zuordnung; auf Windows das Titel-/Taskbar-Icon.
|
// unabhängig von der .desktop-Zuordnung; auf Windows das Titel-/Taskbar-Icon.
|
||||||
@@ -176,6 +180,25 @@ pub(crate) fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
app.manage(PopupBlurGuard(std::sync::Arc::new(
|
app.manage(PopupBlurGuard(std::sync::Arc::new(
|
||||||
std::sync::atomic::AtomicBool::new(false),
|
std::sync::atomic::AtomicBool::new(false),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
// Muffin (Cinnamon) mappt das Popup ohne Fokus: das set_focus aus
|
||||||
|
// show_popup läuft, bevor der WM das Fenster gemappt hat, und wird
|
||||||
|
// von der Focus-Stealing-Prevention verworfen — ohne initialen Fokus
|
||||||
|
// greift das Blur-Schließen nie. Deshalb nach jedem Map erneut
|
||||||
|
// präsentieren: present() besorgt sich unter X11 den echten
|
||||||
|
// Server-Timestamp, damit gewährt der WM den Fokus. Nur für den
|
||||||
|
// SNI-Pfad unter X11 — auf GNOME zeigt die Extension das Popup, dort
|
||||||
|
// hat das present() beim Map den Klick-Weg gebrochen (Ubuntu 17.07).
|
||||||
|
use gtk::prelude::*;
|
||||||
|
let gtk_win = app.get_webview_window("popup").unwrap().gtk_window()?;
|
||||||
|
if gtk_win.display().type_().name().starts_with("GdkX11")
|
||||||
|
&& !crate::platform::is_gnome()
|
||||||
|
{
|
||||||
|
gtk_win.connect_map_event(|w, _| {
|
||||||
|
w.present();
|
||||||
|
glib::Propagation::Proceed
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Der gesamte Tray-Kontrakt: Icon zeigen, Klick als Anchor melden —
|
// Der gesamte Tray-Kontrakt: Icon zeigen, Klick als Anchor melden —
|
||||||
@@ -197,62 +220,93 @@ pub(crate) fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
window.hide().unwrap();
|
window.hide().unwrap();
|
||||||
}
|
}
|
||||||
// Popup schließt bei Fokusverlust.
|
// Popup schließt bei Fokusverlust.
|
||||||
if let tauri::WindowEvent::Focused(false) = event {
|
if let tauri::WindowEvent::Focused(focused) = event {
|
||||||
if window.label() == "popup" {
|
if window.label() == "popup" {
|
||||||
// KDE feuert direkt nach show/set_focus ein Focused(false) — ohne
|
// KDE feuert direkt nach show/set_focus ein spurioses Focused(false),
|
||||||
// diese kurze Sperre verschwände das Popup sofort wieder (erst der
|
// vor dem echten Fokus. Die Sperre steht ab dem Anzeigen und fällt mit
|
||||||
// zweite Klick hielte). GNOME/macOS setzen die Sperre nie.
|
// dem ersten Focused(true) — das spuriose Blur läuft ins Leere.
|
||||||
|
// GNOME/macOS setzen die Sperre nie.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
if let Some(g) = window.try_state::<PopupBlurGuard>() {
|
if let Some(g) = window.try_state::<PopupBlurGuard>() {
|
||||||
if g.0.load(std::sync::atomic::Ordering::SeqCst) {
|
if *focused {
|
||||||
|
g.0.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
} else if g.0.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = window.hide();
|
if !focused {
|
||||||
|
let _ = window.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(invoke_handlers())
|
||||||
commands::list_projects,
|
}
|
||||||
commands::create_project_full,
|
|
||||||
commands::add_project,
|
/// Eine Kommando-Liste für beide Prozesse (Hauptfenster und Terminal).
|
||||||
commands::remove_project,
|
/// Zwei getrennte Listen hatten den Settings-Dialog gebrochen, weil die
|
||||||
commands::delete_project,
|
/// Archiv-Kommandos nur im Terminal-Prozess registriert waren.
|
||||||
commands::project_work_dirs,
|
fn invoke_handlers() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
|
||||||
commands::set_project_dir,
|
tauri::generate_handler![
|
||||||
commands::add_work_dir,
|
commands::list_projects,
|
||||||
commands::remove_work_dir,
|
commands::create_project_full,
|
||||||
commands::list_pools,
|
commands::add_project,
|
||||||
commands::create_oauth_pool,
|
commands::delete_preview,
|
||||||
commands::create_apikey_pool,
|
commands::delete_project_scoped,
|
||||||
commands::rename_pool,
|
commands::project_work_dirs,
|
||||||
commands::delete_pool,
|
commands::set_project_dir,
|
||||||
commands::assign_pool,
|
commands::add_work_dir,
|
||||||
commands::unassign_pool,
|
commands::remove_work_dir,
|
||||||
commands::set_terminal_config,
|
commands::list_pools,
|
||||||
commands::project_icon,
|
commands::create_oauth_pool,
|
||||||
commands::pool_label,
|
commands::create_reference_pool,
|
||||||
commands::todo_state,
|
commands::default_config_dir,
|
||||||
commands::set_todo,
|
commands::create_apikey_pool,
|
||||||
commands::usage_stats,
|
commands::rename_pool,
|
||||||
commands::stop_project,
|
commands::delete_pool,
|
||||||
commands::restart_project,
|
commands::assign_pool,
|
||||||
commands::start_or_focus_cmd,
|
commands::unassign_pool,
|
||||||
commands::open_main_window,
|
commands::set_terminal_config,
|
||||||
commands::quit_app,
|
commands::project_icon,
|
||||||
commands::sync_setting,
|
commands::pool_label,
|
||||||
commands::set_sync_setting,
|
commands::usage_stats,
|
||||||
commands::terminal_font_size,
|
commands::stop_project,
|
||||||
commands::set_terminal_font_size,
|
commands::restart_project,
|
||||||
commands::link_pool_runtime,
|
commands::start_or_focus_cmd,
|
||||||
commands::oauth_login,
|
commands::open_main_window,
|
||||||
commands::keychain_status,
|
commands::quit_app,
|
||||||
commands::set_apikey,
|
commands::sync_setting,
|
||||||
terminal::open_terminal
|
commands::set_sync_setting,
|
||||||
])
|
commands::terminal_font_size,
|
||||||
|
commands::set_terminal_font_size,
|
||||||
|
commands::link_pool_runtime,
|
||||||
|
commands::oauth_login,
|
||||||
|
commands::keychain_status,
|
||||||
|
commands::set_apikey,
|
||||||
|
commands::trilium_status,
|
||||||
|
commands::trilium_setup,
|
||||||
|
commands::trilium_connect,
|
||||||
|
commands::trilium_open,
|
||||||
|
commands::trilium_disable,
|
||||||
|
commands::enabled_modules,
|
||||||
|
commands::module_registry,
|
||||||
|
commands::set_module,
|
||||||
|
terminal::open_terminal,
|
||||||
|
terminal::term_start,
|
||||||
|
terminal::term_log,
|
||||||
|
terminal::term_write,
|
||||||
|
terminal::term_resize,
|
||||||
|
terminal::buffer_read,
|
||||||
|
terminal::commands_delete,
|
||||||
|
terminal::todos_delete,
|
||||||
|
terminal::todos_add,
|
||||||
|
terminal::todos_update,
|
||||||
|
terminal::search_run,
|
||||||
|
terminal::open_panel_window
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminal-Prozess: ein Fenster mit eigener PTY; Activation-Policy bleibt
|
/// Terminal-Prozess: ein Fenster mit eigener PTY; Activation-Policy bleibt
|
||||||
@@ -260,9 +314,10 @@ pub(crate) fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
pub(crate) fn terminal_builder(project: String) -> tauri::Builder<tauri::Wry> {
|
pub(crate) fn terminal_builder(project: String) -> tauri::Builder<tauri::Wry> {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.manage(terminal::Terminals::default())
|
.manage(terminal::Terminals::default())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let cfg = terminal_config(&project)?;
|
let cfg = project_config(&project)?;
|
||||||
terminal::build_window(app.handle(), &project, &cfg)?;
|
terminal::build_window(app.handle(), &project, &cfg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -279,23 +334,5 @@ pub(crate) fn terminal_builder(project: String) -> tauri::Builder<tauri::Wry> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(invoke_handlers())
|
||||||
terminal::term_start,
|
|
||||||
terminal::term_log,
|
|
||||||
terminal::term_write,
|
|
||||||
terminal::term_resize,
|
|
||||||
terminal::panel_read,
|
|
||||||
terminal::panel_set,
|
|
||||||
terminal::open_panel_window,
|
|
||||||
commands::panel_archive_dir_cmd,
|
|
||||||
commands::panel_archive_cmd,
|
|
||||||
commands::reveal_path_cmd,
|
|
||||||
// Header im Terminal-Prozess: Projektliste, Icon und Pool-Name.
|
|
||||||
commands::list_projects,
|
|
||||||
commands::project_icon,
|
|
||||||
commands::pool_label,
|
|
||||||
commands::terminal_font_size,
|
|
||||||
commands::set_terminal_font_size,
|
|
||||||
commands::spellcheck_lang
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|||||||
+199
-61
@@ -3,22 +3,21 @@
|
|||||||
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::domain::archive::{archive_panel_content, project_archive_dir};
|
|
||||||
use crate::domain::credentials::keychain_service;
|
use crate::domain::credentials::keychain_service;
|
||||||
use crate::domain::paths::Paths;
|
use crate::domain::paths::Paths;
|
||||||
use crate::domain::pool::{
|
use crate::domain::pool::{
|
||||||
create_apikey_pool_in, create_oauth_pool_in, delete_pool_in, ensure_oauth_pool,
|
create_apikey_pool_in, create_oauth_pool_in, create_reference_pool_in, delete_pool_in,
|
||||||
link_pool_runtime_in, list_pools_in, read_pool, rename_pool_in, set_apikey_in, PoolInfo,
|
ensure_oauth_pool, link_pool_runtime_in, list_pools_in, offered_default_dir, read_pool,
|
||||||
|
rename_pool_in, set_apikey_in, PoolInfo,
|
||||||
};
|
};
|
||||||
use crate::domain::project::{
|
use crate::domain::project::{
|
||||||
add_project_in, add_work_dir_in, assign_pool_in, create_project_full_in, delete_project_in,
|
add_project_in, add_work_dir_in, assign_pool_in, create_project_full_in, delete_preview_in,
|
||||||
is_running, kill_terminals, list_projects_in, project_work_dirs_in, read_project_config_in,
|
delete_project_scoped_in, display_name_in, is_running, kill_terminals, list_projects_in,
|
||||||
remove_work_dir_in, resolve_icon_path, running_projects_using_pool, set_project_dir_in,
|
project_work_dirs_in, read_project_config_in, remove_work_dir_in, resolve_icon_path,
|
||||||
set_terminal_config_in, unassign_pool_in, Project, TerminalConfig,
|
running_projects_using_pool, set_project_dir_in, set_terminal_config_in, unassign_pool_in,
|
||||||
|
DeletePreview, Project, TerminalConfig,
|
||||||
};
|
};
|
||||||
use crate::domain::registry::unregister_project;
|
|
||||||
use crate::domain::settings;
|
use crate::domain::settings;
|
||||||
use crate::domain::todo::{set_todo_in, todo_state_in};
|
|
||||||
use crate::domain::usage::{usage_stats_in, UsageRow};
|
use crate::domain::usage::{usage_stats_in, UsageRow};
|
||||||
use crate::platform::KeychainStore;
|
use crate::platform::KeychainStore;
|
||||||
use crate::terminal;
|
use crate::terminal;
|
||||||
@@ -38,8 +37,7 @@ pub(crate) fn create_project_full(
|
|||||||
work_dir: Option<String>,
|
work_dir: Option<String>,
|
||||||
create_work_dir: bool,
|
create_work_dir: bool,
|
||||||
terminal: TerminalConfig,
|
terminal: TerminalConfig,
|
||||||
todo: bool,
|
) -> Result<String, String> {
|
||||||
) -> Result<(), String> {
|
|
||||||
create_project_full_in(
|
create_project_full_in(
|
||||||
&Paths::real(),
|
&Paths::real(),
|
||||||
&name,
|
&name,
|
||||||
@@ -48,7 +46,6 @@ pub(crate) fn create_project_full(
|
|||||||
work_dir.as_deref(),
|
work_dir.as_deref(),
|
||||||
create_work_dir,
|
create_work_dir,
|
||||||
terminal,
|
terminal,
|
||||||
todo,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,21 +55,19 @@ pub(crate) fn add_project(path: String) -> Result<(), String> {
|
|||||||
add_project_in(&Paths::real(), &path)
|
add_project_in(&Paths::real(), &path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Projekt aus der Registry nehmen; der Ordner bleibt unangetastet.
|
/// Umbau-Sperre: eine laufende Session des Projekts zuerst beenden.
|
||||||
#[tauri::command]
|
fn ensure_not_running(project: &str) -> Result<(), String> {
|
||||||
pub(crate) fn remove_project(name: String) -> Result<(), String> {
|
if is_running(project) {
|
||||||
if is_running(&name) {
|
let name = display_name_in(&Paths::real(), project)?;
|
||||||
return Err(format!("{name} läuft noch — erst beenden"));
|
return Err(format!("{name} läuft noch — erst beenden"));
|
||||||
}
|
}
|
||||||
unregister_project(&Paths::real(), &name)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
|
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn set_project_dir(project: String, dir: String) -> Result<(), String> {
|
pub(crate) fn set_project_dir(project: String, dir: String) -> Result<(), String> {
|
||||||
if is_running(&project) {
|
ensure_not_running(&project)?;
|
||||||
return Err(format!("{project} läuft noch — erst beenden"));
|
|
||||||
}
|
|
||||||
set_project_dir_in(&Paths::real(), &project, &dir)
|
set_project_dir_in(&Paths::real(), &project, &dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +81,23 @@ pub(crate) fn remove_work_dir(project: String, dir: String) -> Result<(), String
|
|||||||
remove_work_dir_in(&Paths::real(), &project, &dir)
|
remove_work_dir_in(&Paths::real(), &project, &dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Artefakt-Vorschau für den Lösch-Dialog.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn delete_project(name: String, delete_work_dirs: bool) -> Result<(), String> {
|
pub(crate) fn delete_preview(project: String) -> Result<DeletePreview, String> {
|
||||||
if is_running(&name) {
|
delete_preview_in(&Paths::real(), &project)
|
||||||
return Err(format!("{name} läuft noch — erst beenden"));
|
}
|
||||||
}
|
|
||||||
delete_project_in(&Paths::real(), &name, delete_work_dirs)
|
/// Löschen in drei Stufen: "integration" (nur ai-control-Spuren),
|
||||||
|
/// "archive" (zusätzlich Archiv), "full" (zusätzlich Projektordner,
|
||||||
|
/// Arbeitsordner per Flag).
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn delete_project_scoped(
|
||||||
|
project: String,
|
||||||
|
scope: String,
|
||||||
|
delete_work_dirs: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
ensure_not_running(&project)?;
|
||||||
|
delete_project_scoped_in(&Paths::real(), &project, &scope, delete_work_dirs)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -99,16 +105,6 @@ pub(crate) fn project_work_dirs(project: String) -> Result<Vec<String>, String>
|
|||||||
project_work_dirs_in(&Paths::real(), &project)
|
project_work_dirs_in(&Paths::real(), &project)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub(crate) fn todo_state(project: String) -> Result<bool, String> {
|
|
||||||
todo_state_in(&Paths::real(), &project)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub(crate) fn set_todo(project: String, enabled: bool) -> Result<(), String> {
|
|
||||||
set_todo_in(&Paths::real(), &project, enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn set_terminal_config(
|
pub(crate) fn set_terminal_config(
|
||||||
project: String,
|
project: String,
|
||||||
@@ -116,7 +112,11 @@ pub(crate) fn set_terminal_config(
|
|||||||
icon: Option<String>,
|
icon: Option<String>,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
set_terminal_config_in(&Paths::real(), &project, TerminalConfig { theme, icon, title })
|
set_terminal_config_in(
|
||||||
|
&Paths::real(),
|
||||||
|
&project,
|
||||||
|
TerminalConfig { theme, icon, title, ..Default::default() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Icon eines Projekts als data-URL für die Übersicht (PNG oder SVG).
|
/// Icon eines Projekts als data-URL für die Übersicht (PNG oder SVG).
|
||||||
@@ -127,7 +127,7 @@ pub(crate) fn project_icon(project: String) -> Result<Option<String>, String> {
|
|||||||
let Some(icon) = read_project_config_in(&paths, &project)?.terminal.icon else {
|
let Some(icon) = read_project_config_in(&paths, &project)?.terminal.icon else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let path = resolve_icon_path(&paths, &icon);
|
let path = resolve_icon_path(&paths, &project, &icon)?;
|
||||||
let mime = match path.extension().and_then(|e| e.to_str()) {
|
let mime = match path.extension().and_then(|e| e.to_str()) {
|
||||||
Some(e) if e.eq_ignore_ascii_case("svg") => "image/svg+xml",
|
Some(e) if e.eq_ignore_ascii_case("svg") => "image/svg+xml",
|
||||||
_ => "image/png",
|
_ => "image/png",
|
||||||
@@ -162,6 +162,11 @@ pub(crate) fn rename_pool(pool: String, name: String) -> Result<(), String> {
|
|||||||
rename_pool_in(&Paths::real(), &pool, &name)
|
rename_pool_in(&Paths::real(), &pool, &name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Anzeige-Namen der Projekte (für Fehlermeldungen mit Projektliste).
|
||||||
|
fn display_names(paths: &Paths, ids: &[String]) -> Result<Vec<String>, String> {
|
||||||
|
ids.iter().map(|id| display_name_in(paths, id)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn delete_pool(pool: String) -> Result<(), String> {
|
pub(crate) fn delete_pool(pool: String) -> Result<(), String> {
|
||||||
let paths = Paths::real();
|
let paths = Paths::real();
|
||||||
@@ -169,7 +174,7 @@ pub(crate) fn delete_pool(pool: String) -> Result<(), String> {
|
|||||||
if !running.is_empty() {
|
if !running.is_empty() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Pool wird noch benutzt — läuft: {}",
|
"Pool wird noch benutzt — läuft: {}",
|
||||||
running.join(", ")
|
display_names(&paths, &running)?.join(", ")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
delete_pool_in(&paths, &KeychainStore, &pool)
|
delete_pool_in(&paths, &KeychainStore, &pool)
|
||||||
@@ -198,7 +203,21 @@ pub(crate) fn set_apikey(pool: String, key: String, allow_file: bool) -> Result<
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn keychain_status(pool: String) -> Result<bool, String> {
|
pub(crate) fn keychain_status(pool: String) -> Result<bool, String> {
|
||||||
crate::platform::oauth_keychain_exists(&keychain_service(&Paths::real(), &pool))
|
crate::platform::oauth_keychain_exists(&keychain_service(&Paths::real(), &pool)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claudes Default-Verzeichnis, sofern vorhanden und noch nicht als Pool
|
||||||
|
/// eingebunden — die Pool-Anlage bietet es dann als fertigen Pool an.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn default_config_dir() -> Option<String> {
|
||||||
|
offered_default_dir(&Paths::real())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pool, der auf ein bestehendes Config-Verzeichnis verweist (statt eines neu
|
||||||
|
/// angelegten). Der vorhandene Login dort bleibt gültig.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn create_reference_pool(name: String, dir: String) -> Result<String, String> {
|
||||||
|
create_reference_pool_in(&Paths::real(), &name, &dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setzt einen oauth-Pool zurück: löscht den suffixierten Keychain-Eintrag,
|
/// Setzt einen oauth-Pool zurück: löscht den suffixierten Keychain-Eintrag,
|
||||||
@@ -208,16 +227,23 @@ pub(crate) fn keychain_status(pool: String) -> Result<bool, String> {
|
|||||||
pub(crate) fn oauth_login(pool: String) -> Result<(), String> {
|
pub(crate) fn oauth_login(pool: String) -> Result<(), String> {
|
||||||
let paths = Paths::real();
|
let paths = Paths::real();
|
||||||
ensure_oauth_pool(&paths, &pool)?;
|
ensure_oauth_pool(&paths, &pool)?;
|
||||||
|
// Bei einem verwiesenen Pool hinge daran der Login des fremden
|
||||||
|
// Verzeichnisses — den löscht die App nicht.
|
||||||
|
if let Some(dir) = read_pool(&paths, &pool)?.dir {
|
||||||
|
return Err(format!(
|
||||||
|
"Neuanmeldung geht bei einem verwiesenen Pool nicht ({dir}) — dort meldet claude selbst an"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let running = running_projects_using_pool(&paths, &pool)?;
|
let running = running_projects_using_pool(&paths, &pool)?;
|
||||||
if !running.is_empty() {
|
if !running.is_empty() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Neuanmeldung nur bei ungenutztem Pool möglich — läuft: {}",
|
"Neuanmeldung nur bei ungenutztem Pool möglich — läuft: {}",
|
||||||
running.join(", ")
|
display_names(&paths, &running)?.join(", ")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let service = keychain_service(&paths, &pool);
|
let service = keychain_service(&paths, &pool)?;
|
||||||
if crate::platform::oauth_keychain_exists(&service)? {
|
if crate::platform::oauth_keychain_exists(&service)? {
|
||||||
crate::platform::oauth_keychain_delete(&service)?;
|
crate::platform::oauth_keychain_delete(&service)?;
|
||||||
}
|
}
|
||||||
@@ -240,7 +266,8 @@ pub(crate) fn link_pool_runtime(pool: String) -> Result<(), String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn stop_project(project: String) -> Result<(), String> {
|
pub(crate) fn stop_project(project: String) -> Result<(), String> {
|
||||||
if crate::platform::terminal_pids(&project).is_empty() {
|
if crate::platform::terminal_pids(&project).is_empty() {
|
||||||
return Err(format!("{project} läuft nicht"));
|
let name = display_name_in(&Paths::real(), &project)?;
|
||||||
|
return Err(format!("{name} läuft nicht"));
|
||||||
}
|
}
|
||||||
kill_terminals(&project)
|
kill_terminals(&project)
|
||||||
}
|
}
|
||||||
@@ -253,7 +280,8 @@ pub(crate) fn restart_project(app: tauri::AppHandle, project: String) -> Result<
|
|||||||
let deadline = Instant::now() + Duration::from_secs(30);
|
let deadline = Instant::now() + Duration::from_secs(30);
|
||||||
while is_running(&project) {
|
while is_running(&project) {
|
||||||
if Instant::now() > deadline {
|
if Instant::now() > deadline {
|
||||||
return Err(format!("{project} hat sich nach 30 s nicht beendet"));
|
let name = display_name_in(&Paths::real(), &project)?;
|
||||||
|
return Err(format!("{name} hat sich nach 30 s nicht beendet"));
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(250));
|
std::thread::sleep(Duration::from_millis(250));
|
||||||
}
|
}
|
||||||
@@ -319,11 +347,6 @@ pub(crate) fn set_terminal_font_size(size: u32) -> Result<(), String> {
|
|||||||
settings::set_terminal_font_size_in(&Paths::real(), size)
|
settings::set_terminal_font_size_in(&Paths::real(), size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub(crate) fn spellcheck_lang() -> String {
|
|
||||||
settings::spellcheck_lang(&Paths::real())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Verbrauch ----------
|
// ---------- Verbrauch ----------
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -331,24 +354,139 @@ pub(crate) fn usage_stats(days: u32) -> Result<Vec<UsageRow>, String> {
|
|||||||
usage_stats_in(&Paths::real(), days)
|
usage_stats_in(&Paths::real(), days)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Panel-Archiv ----------
|
/// Modul-Registry für den Settings-Dialog (Checkbox-Gruppe „Module").
|
||||||
|
|
||||||
/// Konfigurierter Archiv-Ordner des Projekts (für den Panel-Button: entscheidet,
|
|
||||||
/// ob der Ordner-Dialog nötig ist).
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn panel_archive_dir_cmd(project: String) -> Option<String> {
|
pub(crate) fn module_registry(project: String) -> Result<Vec<crate::domain::modules::ModuleInfo>, String> {
|
||||||
project_archive_dir(&project).map(|p| p.display().to_string())
|
crate::domain::modules::module_infos_in(&Paths::real(), &project)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Archiviert den aktuellen Panel-Entwurf. `dir` (aus dem Ordner-Dialog) setzt
|
/// Schaltet ein Modul im Projekt an/ab (Settings-Dialog, schreibt sofort).
|
||||||
/// den Archiv-Ordner zugleich; ohne `dir` muss er konfiguriert sein.
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn panel_archive_cmd(project: String, dir: Option<String>) -> Result<String, String> {
|
pub(crate) fn set_module(project: String, module: String, enabled: bool) -> Result<(), String> {
|
||||||
archive_panel_content(&project, dir.as_deref()).map(|p| p.display().to_string())
|
crate::domain::modules::set_module_in(&Paths::real(), &project, &module, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zeigt einen Pfad im Dateimanager (nach dem Archivieren „im Finder zeigen").
|
/// Aktive Module fürs Frontend (Registry-Enablement der Projekt-Config).
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn reveal_path_cmd(path: String) {
|
pub(crate) fn enabled_modules(project: String) -> Result<Vec<String>, String> {
|
||||||
crate::platform::reveal_path(std::path::Path::new(&path));
|
Ok(
|
||||||
|
crate::domain::modules::active_in(&Paths::real(), &project)?
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.id.to_string())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Trilium ----------
|
||||||
|
|
||||||
|
/// Wizard-Status: Runtime da, Instanz erreichbar, Token gültig. Reine
|
||||||
|
/// Checks, keine Schreiber.
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub(crate) struct TriliumStatus {
|
||||||
|
version: &'static str,
|
||||||
|
port: u16,
|
||||||
|
data_dir: String,
|
||||||
|
runtime_ready: bool,
|
||||||
|
instance_running: bool,
|
||||||
|
token_ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trilium_status_for(project: &str) -> TriliumStatus {
|
||||||
|
let paths = Paths::real();
|
||||||
|
let instance_running = crate::domain::trilium::instance_running(project);
|
||||||
|
TriliumStatus {
|
||||||
|
version: crate::domain::trilium::VERSION,
|
||||||
|
port: crate::domain::trilium::port_for(project),
|
||||||
|
data_dir: crate::domain::paths::contract_home(
|
||||||
|
&paths,
|
||||||
|
&crate::domain::trilium::data_dir(&paths, project),
|
||||||
|
),
|
||||||
|
runtime_ready: crate::domain::trilium::runtime_ready(&paths),
|
||||||
|
instance_running,
|
||||||
|
token_ok: instance_running && crate::domain::trilium::token_ok(project),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn trilium_status(project: String) -> TriliumStatus {
|
||||||
|
trilium_status_for(&project)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Richtet die Projekt-Instanz ein: Runtime laden (gepinnt, Checksum),
|
||||||
|
/// Instanz starten. Schreibt KEINE Config — das Modul-Flag setzt erst
|
||||||
|
/// trilium_connect nach erfolgreichem Token-Login.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn trilium_setup(project: String) -> Result<TriliumStatus, String> {
|
||||||
|
crate::domain::trilium::ensure_instance(&Paths::real(), &project)?;
|
||||||
|
Ok(trilium_status_for(&project))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Richtet die Instanz mit dem Passwort aus dem Wizard vollständig ein:
|
||||||
|
/// Erstlauf legt das Dokument an und setzt das Passwort, dann ETAPI-Token
|
||||||
|
/// holen, im Keyring ablegen und ERST DANN das Modul in der Projekt-Config
|
||||||
|
/// freischalten. Das Passwort wird nicht gespeichert.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn trilium_connect(
|
||||||
|
project: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<TriliumStatus, String> {
|
||||||
|
let paths = Paths::real();
|
||||||
|
crate::domain::trilium::ensure_instance(&paths, &project)?;
|
||||||
|
if !crate::domain::trilium::initialized(&project)? {
|
||||||
|
crate::domain::trilium::setup_new_document(&project)?;
|
||||||
|
crate::domain::trilium::set_password(&project, &password)?;
|
||||||
|
}
|
||||||
|
let token = crate::domain::trilium::login(&project, &password)?;
|
||||||
|
crate::domain::trilium::store_token(&project, &token)?;
|
||||||
|
if !crate::domain::trilium::token_ok(&project) {
|
||||||
|
crate::domain::trilium::delete_token(&project);
|
||||||
|
return Err("Token angenommen, aber ETAPI-Zugriff scheitert".into());
|
||||||
|
}
|
||||||
|
crate::domain::modules::set_module_in(&paths, &project, "trilium", true)?;
|
||||||
|
Ok(trilium_status_for(&project))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Öffnet das Trilium-Fenster des Projekts (startet die Instanz bei Bedarf);
|
||||||
|
/// `note_id` springt zur Notiz. Async wie open_panel_window (Fenster-Erzeugung
|
||||||
|
/// aus synchronen Commands klemmt auf dem GTK-Mainloop).
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn trilium_open(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
project: String,
|
||||||
|
note_id: Option<String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use tauri::Manager;
|
||||||
|
crate::domain::trilium::ensure_instance(&Paths::real(), &project)?;
|
||||||
|
let mut url = crate::domain::trilium::base_url(&project);
|
||||||
|
if let Some(id) = ¬e_id {
|
||||||
|
url.push_str(&format!("/#root/{id}"));
|
||||||
|
}
|
||||||
|
let label = format!("trilium-{project}");
|
||||||
|
if let Some(win) = app.get_webview_window(&label) {
|
||||||
|
if note_id.is_some() {
|
||||||
|
win
|
||||||
|
.eval(&format!("window.location.href = {}", serde_json::json!(url)))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
win.set_focus().map_err(|e| e.to_string())?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let parsed: tauri::Url = url.parse().map_err(|e| format!("{url}: {e}"))?;
|
||||||
|
tauri::WebviewWindowBuilder::new(&app, &label, tauri::WebviewUrl::External(parsed))
|
||||||
|
.title(format!("Trilium — {}", crate::domain::project::display_name_in(&Paths::real(), &project)?))
|
||||||
|
.inner_size(1100.0, 780.0)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wählt das Modul ab und stoppt die Projekt-Instanz. Daten und Token
|
||||||
|
/// bleiben — erneutes Aktivieren verbindet sich wieder.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) async fn trilium_disable(project: String) -> Result<(), String> {
|
||||||
|
crate::domain::modules::set_module_in(&Paths::real(), &project, "trilium", false)?;
|
||||||
|
crate::domain::trilium::stop_instance(&project);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
//! Archiv: Panel-Entwürfe als Markdown-Dateien mit Frontmatter im
|
|
||||||
//! konfigurierten Archiv-Ordner des Projekts persistieren.
|
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
use crate::domain::paths::{contract_home, expand_home, panel_file, Paths};
|
|
||||||
use crate::domain::project::{
|
|
||||||
read_project_config_in, settings_path, write_project_config_in,
|
|
||||||
};
|
|
||||||
use crate::domain::registry::project_dir;
|
|
||||||
|
|
||||||
/// Konfigurierter Archiv-Ordner des Projekts (ai-control.json: archiveDir),
|
|
||||||
/// Home-expandiert; None, wenn nicht gesetzt.
|
|
||||||
pub(crate) fn project_archive_dir(project: &str) -> Option<PathBuf> {
|
|
||||||
let paths = Paths::real();
|
|
||||||
let dir = read_project_config_in(&paths, project).ok()?.archive_dir?;
|
|
||||||
Some(expand_home(&paths, &dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setzt den Archiv-Ordner: ai-control.json (~-relativ) und ein Eintrag in
|
|
||||||
/// permissions.additionalDirectories + Edit der Projekt-settings.json, damit
|
|
||||||
/// claude das Archiv später lesen/scannen darf.
|
|
||||||
pub(crate) fn set_project_archive_dir(project: &str, dir: &str) -> Result<(), String> {
|
|
||||||
let paths = Paths::real();
|
|
||||||
let expanded = expand_home(&paths, dir);
|
|
||||||
if !expanded.is_absolute() {
|
|
||||||
return Err(format!("Archiv-Ordner muss ein absoluter Pfad sein: {}", expanded.display()));
|
|
||||||
}
|
|
||||||
// Zu weit gefasst? Ein Vorfahre des Home (inkl. Home selbst und Root) würde
|
|
||||||
// claude über additionalDirectories weiten Zugriff geben — das lehnen wir ab.
|
|
||||||
if paths.home.starts_with(&expanded) {
|
|
||||||
return Err(format!(
|
|
||||||
"Archiv-Ordner zu weit gefasst: {}. Bitte einen spezifischen Unterordner wählen.",
|
|
||||||
expanded.display()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
fs::create_dir_all(&expanded).map_err(|e| format!("{}: {e}", expanded.display()))?;
|
|
||||||
let contracted = contract_home(&paths, &expanded);
|
|
||||||
let mut cfg = read_project_config_in(&paths, project)?;
|
|
||||||
cfg.archive_dir = Some(contracted.clone());
|
|
||||||
write_project_config_in(&paths, project, &cfg)?;
|
|
||||||
add_archive_permission(&paths, project, &contracted)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trägt den Archiv-Ordner idempotent in additionalDirectories + Edit-Allow ein.
|
|
||||||
fn add_archive_permission(paths: &Paths, project: &str, dir: &str) -> Result<(), String> {
|
|
||||||
let sp = settings_path(&project_dir(paths, project)?);
|
|
||||||
let mut v: serde_json::Value = if sp.is_file() {
|
|
||||||
serde_json::from_str(&fs::read_to_string(&sp).map_err(|e| format!("{}: {e}", sp.display()))?)
|
|
||||||
.map_err(|e| format!("{}: {e}", sp.display()))?
|
|
||||||
} else {
|
|
||||||
serde_json::json!({})
|
|
||||||
};
|
|
||||||
let root = v.as_object_mut().ok_or("settings.json ist kein Objekt")?;
|
|
||||||
let perms = root
|
|
||||||
.entry("permissions")
|
|
||||||
.or_insert_with(|| serde_json::json!({}))
|
|
||||||
.as_object_mut()
|
|
||||||
.ok_or("permissions ist kein Objekt")?;
|
|
||||||
let dirs = perms
|
|
||||||
.entry("additionalDirectories")
|
|
||||||
.or_insert_with(|| serde_json::json!([]))
|
|
||||||
.as_array_mut()
|
|
||||||
.ok_or("additionalDirectories ist kein Array")?;
|
|
||||||
if !dirs.iter().any(|d| d.as_str() == Some(dir)) {
|
|
||||||
dirs.push(serde_json::json!(dir));
|
|
||||||
}
|
|
||||||
let edit = format!("Edit({dir}/**)");
|
|
||||||
let allow = perms
|
|
||||||
.entry("allow")
|
|
||||||
.or_insert_with(|| serde_json::json!([]))
|
|
||||||
.as_array_mut()
|
|
||||||
.ok_or("allow ist kein Array")?;
|
|
||||||
if !allow.iter().any(|p| p.as_str() == Some(&edit)) {
|
|
||||||
allow.push(serde_json::json!(edit));
|
|
||||||
}
|
|
||||||
let parent = sp.parent().ok_or("settings.json ohne Elternordner")?;
|
|
||||||
fs::create_dir_all(parent).map_err(|e| format!("{}: {e}", parent.display()))?;
|
|
||||||
let raw = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
|
||||||
fs::write(&sp, raw + "\n").map_err(|e| format!("{}: {e}", sp.display()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Archiviert den aktuellen Panel-Inhalt des Projekts als Markdown-Datei mit
|
|
||||||
/// Frontmatter im Archiv-Ordner. `dir_override` setzt den Ordner zugleich
|
|
||||||
/// (Terminal-Fallback ohne Dialog). Liefert den geschriebenen Pfad.
|
|
||||||
pub(crate) fn archive_panel_content(
|
|
||||||
project: &str,
|
|
||||||
dir_override: Option<&str>,
|
|
||||||
) -> Result<PathBuf, String> {
|
|
||||||
if let Some(d) = dir_override {
|
|
||||||
set_project_archive_dir(project, d)?;
|
|
||||||
}
|
|
||||||
let dir = project_archive_dir(project).ok_or("kein Archiv-Ordner gesetzt")?;
|
|
||||||
let text = fs::read_to_string(panel_file(project)).unwrap_or_default();
|
|
||||||
if text.trim().is_empty() {
|
|
||||||
return Err("Panel ist leer — nichts zu archivieren".into());
|
|
||||||
}
|
|
||||||
fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
|
|
||||||
|
|
||||||
let secs = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.as_secs();
|
|
||||||
let (stamp, iso) = utc_stamp(secs);
|
|
||||||
let title = first_line(&text);
|
|
||||||
let path = dir.join(format!("{stamp}-{}.md", slugify(&title)));
|
|
||||||
let doc = format!(
|
|
||||||
"---\ntitle: \"{}\"\nproject: {project}\ncreated: {iso}\nsource: ai-control\n---\n\n{}\n",
|
|
||||||
title.replace('"', "'"),
|
|
||||||
text.trim_end(),
|
|
||||||
);
|
|
||||||
fs::write(&path, doc).map_err(|e| format!("{}: {e}", path.display()))?;
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Titelzeile: erste Überschrift (## …) oder sonst erste nicht-leere Zeile.
|
|
||||||
fn first_line(text: &str) -> String {
|
|
||||||
let mut fallback: Option<&str> = None;
|
|
||||||
for line in text.lines() {
|
|
||||||
let t = line.trim();
|
|
||||||
if t.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let h = t.trim_start_matches('#').trim();
|
|
||||||
if t.starts_with('#') && !h.is_empty() {
|
|
||||||
return h.to_string();
|
|
||||||
}
|
|
||||||
fallback.get_or_insert(t);
|
|
||||||
}
|
|
||||||
fallback.unwrap_or("entwurf").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dateinamen-tauglicher Slug (ascii, klein, Bindestriche, max. 60 Zeichen).
|
|
||||||
fn slugify(s: &str) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
for c in s.chars() {
|
|
||||||
if c.is_ascii_alphanumeric() {
|
|
||||||
out.push(c.to_ascii_lowercase());
|
|
||||||
} else if !out.ends_with('-') {
|
|
||||||
out.push('-');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let out: String = out.trim_matches('-').chars().take(60).collect();
|
|
||||||
if out.is_empty() {
|
|
||||||
"entwurf".into()
|
|
||||||
} else {
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UTC aus Epoch-Sekunden: (Dateistempel `YYYY-MM-DD_HHMM`, ISO `…Z`).
|
|
||||||
fn utc_stamp(secs: u64) -> (String, String) {
|
|
||||||
let (h, mi, s) = (secs / 3600 % 24, secs / 60 % 60, secs % 60);
|
|
||||||
let (y, m, d) = civil_from_days((secs / 86400) as i64);
|
|
||||||
(
|
|
||||||
format!("{y:04}-{m:02}-{d:02}_{h:02}{mi:02}"),
|
|
||||||
format!("{y:04}-{m:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kalenderdatum aus Tagen seit 1970-01-01 (Howard-Hinnant-Algorithmus).
|
|
||||||
fn civil_from_days(z: i64) -> (i64, u32, u32) {
|
|
||||||
let z = z + 719468;
|
|
||||||
let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
|
|
||||||
let doe = z - era * 146097;
|
|
||||||
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
||||||
let y = yoe + era * 400;
|
|
||||||
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
||||||
let mp = (5 * doy + 2) / 153;
|
|
||||||
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
|
|
||||||
let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
|
|
||||||
(if m <= 2 { y + 1 } else { y }, m, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn utc_stamp_bekannte_zeit() {
|
|
||||||
// 2026-07-11 (20645 Tage seit Epoch) um 11:14:15 UTC.
|
|
||||||
let secs = 20_645u64 * 86400 + 11 * 3600 + 14 * 60 + 15;
|
|
||||||
let (stamp, iso) = utc_stamp(secs);
|
|
||||||
assert_eq!(stamp, "2026-07-11_1114");
|
|
||||||
assert_eq!(iso, "2026-07-11T11:14:15Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn civil_from_days_referenz() {
|
|
||||||
assert_eq!(civil_from_days(0), (1970, 1, 1));
|
|
||||||
assert_eq!(civil_from_days(20_645), (2026, 7, 11));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn slugify_grenzen() {
|
|
||||||
assert_eq!(slugify("ADR: Logging vereinheitlichen"), "adr-logging-vereinheitlichen");
|
|
||||||
assert_eq!(slugify(" "), "entwurf");
|
|
||||||
assert_eq!(slugify("###"), "entwurf");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn first_line_ueberschrift_oder_erste_zeile() {
|
|
||||||
assert_eq!(first_line("\n\n# Titel\n\nText"), "Titel");
|
|
||||||
assert_eq!(first_line("Kein Heading\nzweite"), "Kein Heading");
|
|
||||||
assert_eq!(first_line(" \n"), "entwurf");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,15 +23,28 @@ pub(crate) trait ApikeyStore {
|
|||||||
fn delete(&self, pool: &str) -> Result<(), String>;
|
fn delete(&self, pool: &str) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keychain-Service-Name des OAuth-Pools: claude legt pro CLAUDE_CONFIG_DIR
|
/// Keychain-Service-Name eines CLAUDE_CONFIG_DIR: claude legt pro Verzeichnis
|
||||||
/// einen suffixierten Eintrag an, Suffix = erste 8 Hex-Zeichen von SHA-256
|
/// einen suffixierten Eintrag an, Suffix = erste 8 Hex-Zeichen von SHA-256
|
||||||
/// über den Pool-Pfad.
|
/// über den Verzeichnispfad. Claudes Default-Verzeichnis läuft ohne gesetzte
|
||||||
pub(crate) fn keychain_service(paths: &Paths, pool: &str) -> String {
|
/// Variable und trägt deshalb den unsuffixierten Eintrag — ein Pool, der
|
||||||
let hash = Sha256::digest(paths.pool_dir(pool).to_string_lossy().as_bytes());
|
/// darauf verweist, erbt damit einen bestehenden Login.
|
||||||
|
pub(crate) fn keychain_service_for(paths: &Paths, dir: &std::path::Path) -> String {
|
||||||
|
if dir == paths.default_claude_dir() {
|
||||||
|
return "Claude Code-credentials".into();
|
||||||
|
}
|
||||||
|
let hash = Sha256::digest(dir.to_string_lossy().as_bytes());
|
||||||
let hex: String = hash.iter().map(|b| format!("{b:02x}")).collect();
|
let hex: String = hash.iter().map(|b| format!("{b:02x}")).collect();
|
||||||
format!("Claude Code-credentials-{}", &hex[..8])
|
format!("Claude Code-credentials-{}", &hex[..8])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keychain-Service-Name eines Pools über sein aufgelöstes Config-Verzeichnis.
|
||||||
|
pub(crate) fn keychain_service(paths: &Paths, pool: &str) -> Result<String, String> {
|
||||||
|
Ok(keychain_service_for(
|
||||||
|
paths,
|
||||||
|
&crate::domain::pool::pool_config_dir(paths, pool)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -43,11 +56,22 @@ mod tests {
|
|||||||
fn keychain_service_suffix() {
|
fn keychain_service_suffix() {
|
||||||
let p = Paths { home: PathBuf::from("/Users/marcus.hinz") };
|
let p = Paths { home: PathBuf::from("/Users/marcus.hinz") };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keychain_service(&p, "privateDefault"),
|
keychain_service_for(&p, &p.pool_dir("privateDefault")),
|
||||||
"Claude Code-credentials-096c4ef9"
|
"Claude Code-credentials-096c4ef9"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Claudes Default-Verzeichnis läuft ohne CLAUDE_CONFIG_DIR und trägt den
|
||||||
|
/// unsuffixierten Eintrag (am echten System 2026-07-22 gegengeprüft).
|
||||||
|
#[test]
|
||||||
|
fn keychain_service_default_dir_ohne_suffix() {
|
||||||
|
let p = Paths { home: PathBuf::from("/Users/marcus.hinz") };
|
||||||
|
assert_eq!(
|
||||||
|
keychain_service_for(&p, &p.default_claude_dir()),
|
||||||
|
"Claude Code-credentials"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apikey_helper_kette_referenz() {
|
fn apikey_helper_kette_referenz() {
|
||||||
let dir = PathBuf::from("/pools/abc");
|
let dir = PathBuf::from("/pools/abc");
|
||||||
|
|||||||
+117
-4
@@ -2,24 +2,137 @@
|
|||||||
//! (injizierbares Home) und den `ApikeyStore`-Trait testbar; OS-Aufrufe
|
//! (injizierbares Home) und den `ApikeyStore`-Trait testbar; OS-Aufrufe
|
||||||
//! laufen ausschließlich über crate::platform.
|
//! laufen ausschließlich über crate::platform.
|
||||||
|
|
||||||
pub(crate) mod archive;
|
|
||||||
pub(crate) mod credentials;
|
pub(crate) mod credentials;
|
||||||
|
pub(crate) mod modules;
|
||||||
pub(crate) mod paths;
|
pub(crate) mod paths;
|
||||||
|
pub(crate) mod trilium;
|
||||||
pub(crate) mod pool;
|
pub(crate) mod pool;
|
||||||
pub(crate) mod project;
|
pub(crate) mod project;
|
||||||
pub(crate) mod registry;
|
pub(crate) mod registry;
|
||||||
pub(crate) mod settings;
|
pub(crate) mod settings;
|
||||||
pub(crate) mod todo;
|
|
||||||
pub(crate) mod usage;
|
pub(crate) mod usage;
|
||||||
pub(crate) mod watcher;
|
pub(crate) mod watcher;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod testutil;
|
pub(crate) mod testutil;
|
||||||
|
|
||||||
/// Namensprüfung für Projekte und Pool-Anzeigenamen (keine Pfad-Bestandteile).
|
/// Datei atomar ersetzen: temp-Datei im Zielverzeichnis, dann rename. Ein
|
||||||
|
/// Absturz oder eine volle Platte hinterlässt nie eine abgeschnittene
|
||||||
|
/// Zieldatei — wichtig für Dateien, die der App nicht allein gehören
|
||||||
|
/// (.claude/settings.json der Projekte, claudes .claude.json, Registry).
|
||||||
|
pub(crate) fn write_atomic(path: &std::path::Path, content: &str) -> Result<(), String> {
|
||||||
|
let dir = path
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| format!("{}: kein Elternordner", path.display()))?;
|
||||||
|
let name = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.ok_or_else(|| format!("{}: kein Dateiname", path.display()))?;
|
||||||
|
let tmp = dir.join(format!(".{name}.{}.tmp", std::process::id()));
|
||||||
|
std::fs::write(&tmp, content).map_err(|e| format!("{}: {e}", tmp.display()))?;
|
||||||
|
std::fs::rename(&tmp, path).map_err(|e| {
|
||||||
|
let _ = std::fs::remove_file(&tmp);
|
||||||
|
format!("{}: {e}", path.display())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read-modify-write auf dem `permissions`-Objekt einer Claude-settings.json
|
||||||
|
/// (Projekt wie Pool). `create` behandelt eine fehlende Datei als leeres
|
||||||
|
/// Objekt — für Einträge, die die Datei erst anlegen; ohne `create` scheitert
|
||||||
|
/// das Lesen laut. Geschrieben wird nur, wenn `f` den Inhalt tatsächlich
|
||||||
|
/// verändert hat — die Datei gehört claude, unnötige Writes unterbleiben.
|
||||||
|
pub(crate) fn update_settings_permissions(
|
||||||
|
sp: &std::path::Path,
|
||||||
|
create: bool,
|
||||||
|
f: impl FnOnce(&mut serde_json::Map<String, serde_json::Value>) -> Result<(), String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut v: serde_json::Value = if !sp.is_file() && create {
|
||||||
|
serde_json::json!({})
|
||||||
|
} else {
|
||||||
|
let raw = std::fs::read_to_string(sp).map_err(|e| format!("{}: {e}", sp.display()))?;
|
||||||
|
serde_json::from_str(&raw).map_err(|e| format!("{}: {e}", sp.display()))?
|
||||||
|
};
|
||||||
|
let before = v.clone();
|
||||||
|
{
|
||||||
|
let root = v.as_object_mut().ok_or("settings.json ist kein Objekt")?;
|
||||||
|
let perms = root
|
||||||
|
.entry("permissions")
|
||||||
|
.or_insert_with(|| serde_json::json!({}))
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or("permissions ist kein Objekt")?;
|
||||||
|
f(perms)?;
|
||||||
|
}
|
||||||
|
// Ein hier erst angelegtes, leer gebliebenes permissions-Objekt wieder
|
||||||
|
// entfernen — sonst zählte ein No-op als Änderung.
|
||||||
|
if before.get("permissions").is_none()
|
||||||
|
&& v["permissions"].as_object().is_some_and(|o| o.is_empty())
|
||||||
|
{
|
||||||
|
v.as_object_mut().unwrap().remove("permissions");
|
||||||
|
}
|
||||||
|
if v == before {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let parent = sp.parent().ok_or("settings.json ohne Elternordner")?;
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| format!("{}: {e}", parent.display()))?;
|
||||||
|
let raw = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
||||||
|
write_atomic(sp, &(raw + "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Array-Feld im permissions-Objekt, bei Bedarf angelegt.
|
||||||
|
pub(crate) fn perm_array<'a>(
|
||||||
|
perms: &'a mut serde_json::Map<String, serde_json::Value>,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<&'a mut Vec<serde_json::Value>, String> {
|
||||||
|
perms
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| serde_json::json!([]))
|
||||||
|
.as_array_mut()
|
||||||
|
.ok_or_else(|| format!("{key} ist kein Array"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Namensprüfung für Projekte und Pool-Anzeigenamen.
|
||||||
|
///
|
||||||
|
/// Der Name wird als Pfadsegment verwendet (Pool-Verzeichnis, Panel-Dateien)
|
||||||
|
/// und landet zugleich in Dateien mit zeilenbasiertem Format — vor allem in der
|
||||||
|
/// `.desktop`-Datei unter Linux. Darum reicht das Verbot von `/` und `..`
|
||||||
|
/// nicht: Ein Zeilenumbruch im Namen hängt dort eigene Schlüssel an, und
|
||||||
|
/// `Exec=` braucht keinen Schrägstrich. Projektnamen stammen aus
|
||||||
|
/// `dir.file_name()`, bei einem geklonten Fremd-Repo also von außen.
|
||||||
pub(crate) fn check_name(name: &str) -> Result<(), String> {
|
pub(crate) fn check_name(name: &str) -> Result<(), String> {
|
||||||
if name.trim().is_empty() || name.contains('/') || name.contains("..") {
|
let ungueltig = name.trim().is_empty()
|
||||||
|
|| name.contains('/')
|
||||||
|
|| name.contains('\\')
|
||||||
|
|| name.contains("..")
|
||||||
|
|| name.starts_with('.')
|
||||||
|
|| name.chars().any(|c| c.is_control());
|
||||||
|
if ungueltig {
|
||||||
return Err(format!("ungültiger Name: {name}"));
|
return Err(format!("ungültiger Name: {name}"));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::check_name;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn namen_pruefung() {
|
||||||
|
for ok in ["projekt", "mein-projekt", "Projekt 2", "äöü"] {
|
||||||
|
assert!(check_name(ok).is_ok(), "{ok} sollte gültig sein");
|
||||||
|
}
|
||||||
|
for bad in [
|
||||||
|
"",
|
||||||
|
" ",
|
||||||
|
"a/b",
|
||||||
|
"a\\b",
|
||||||
|
"..",
|
||||||
|
"../x",
|
||||||
|
".versteckt",
|
||||||
|
"boo\nExec=bash -c pwn", // .desktop-Injektion
|
||||||
|
"a\tb",
|
||||||
|
"a\u{7f}b",
|
||||||
|
] {
|
||||||
|
assert!(check_name(bad).is_err(), "{bad:?} sollte abgelehnt werden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user