diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27b2b49..779c24c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,8 +5,8 @@ set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
option(CST_BUILD_TUI "Build ncurses/PDCurses TUI" ON)
-option(CST_BUILD_WEBUI "Build the HTTP/SSE WebUI (--web)" ON)
-option(CST_BUILD_APP "Build the Qt front end in app/ (in-process engine, no port)" OFF)
+option(CST_BUILD_WEBUI "Build the HTTP/SSE engine API (--web, cst-webui/1)" ON)
+option(CST_BUILD_APP "Build archived Qt front end in archive/qt-app (experimental)" OFF)
option(CST_NATIVE_ARCH "Pass -march=native to sashidown" OFF)
option(CST_BUNDLE_BUSYBOX "Install placeholder/scripts for busybox beside binary" ON)
@@ -218,9 +218,8 @@ if(WIN32)
endif()
# --- the engine as a library ---------------------------------------------
-# cstcore is the core without main()/TUI: the CLI links it and the Qt front
-# end in app/ (CST_BUILD_APP) embeds the very same objects in-process, so the
-# engine is compiled once per build instead of once per front end.
+# cstcore is the core without main()/TUI: the CLI links it; archived clients
+# (archive/qt-app, archive/desktop-tauri) can embed the same objects in-process.
add_library(cstcore STATIC ${CST_CORE_SOURCES})
add_dependencies(cstcore sashidown)
target_include_directories(cstcore PUBLIC
@@ -246,7 +245,8 @@ else()
endif()
if(CST_BUILD_APP)
- add_subdirectory(app)
+ # Frozen reference GUI; not part of the default Hermit delivery.
+ add_subdirectory(archive/qt-app)
endif()
set(CST_SOURCES src/main.c)
diff --git a/README.md b/README.md
index 78389a9..d015618 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,11 @@
> 本仓库 `CST-agent` 仅作迁移兼容入口。换远程:`./scripts/migrate_remote_to_hermit.sh`
> 完整步骤:[MIGRATION.md](MIGRATION.md)。
-本地 coding agent(原 CST-Agent):终端 TUI、可选 WebUI / 桌面 GUI,在工作区里读改文件、跑命令、管会话。
+本地 **极简 TUI** coding agent(原 CST-Agent):在工作区里读改文件、跑命令、管会话。
C11 实现,OpenAI 兼容网关(DeepSeek / Kimi / MiniMax / OpenAI 等)。
+本仓交付:`hermit` CLI/TUI、`libcstcore`、可替换 plugins/MCP/skills,以及 `hermit --web` 提供的 HTTP/SSE 引擎 API(`cst-webui/1`)。完整浏览器 SPA / 桌面 GUI 参考实现在 [`archive/`](archive/)(不编入默认产物)。
+
**适合**:有监督的本地改动与仓库探索(权限确认、diff、`/undo`、沙箱)。
**慎用**:完全无人值守的长任务;headless 默认自动批准。
@@ -58,7 +60,7 @@ make hermit-font # 烘焙 PNG 帧 + hermit-icons.ttf(Pillow + fonttool
make install PREFIX=$HOME/.local # 同时安装字体到 share/fonts/hermit 与 ~/.local/share/fonts
```
-桌面 GUI(Tauri 2 + React,DeepSeek Harness 风格布局):见 [`apps/desktop`](apps/desktop/) 与 [docs/desktop.md](docs/desktop.md)。旧 Qt App(`app/`)已冻结。
+外置客户端契约:hooks(`cst_agent_hooks_t`)与 [`docs/webui.md`](docs/webui.md) / [`docs/webui-schema.md`](docs/webui-schema.md)。归档 GUI:[`archive/desktop-tauri`](archive/desktop-tauri/)、[`archive/qt-app`](archive/qt-app/)、[`archive/web-spa`](archive/web-spa/)。
TUI 图标:`HERMIT_ICONS=auto|mini|font`(默认色块动画帧,状态与 `icons/agent-motion` HTML 套件一致;`font` 输出 PUA `U+E700+`,需终端加载已安装的 Hermit Icons)。环境变量 **`HERMIT_*` 优先,`CST_*` 仍接受**。
@@ -68,13 +70,14 @@ TUI 图标:`HERMIT_ICONS=auto|mini|font`(默认色块动画帧,状态与 `
```bash
hermit # TUI
-hermit --web # http://127.0.0.1:8787/
+hermit --web # HTTP/SSE API + health page
+hermit --web --web-dev archive/web-spa # archived full SPA
hermit --headless --prompt '…'
```
首页品牌 **HERMIT** + 寄居蟹色块动效;色板:brand `#277F94`、crab `#F68042`、eye `#9957AD`。
-更多:Provider / 工具 / 议会 / WebUI 见 [docs/](docs/)。
+更多:Provider / 工具 / 议会 / 引擎 API 见 [docs/](docs/)。
---
diff --git a/app/DEPRECATED.md b/app/DEPRECATED.md
deleted file mode 100644
index 63d68a5..0000000
--- a/app/DEPRECATED.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Deprecated: Qt front end
-
-This directory is the **legacy Qt 6 / QML** Hermit GUI (`cst-agent-app`).
-
-The supported desktop shell is **`apps/desktop`** (Tauri 2 + React), with a
-DeepSeek-Harness-inspired layout and an in-process C engine bridge.
-
-Do not add features here. Once Tauri reaches feature parity, this tree will be
-moved to `archive/qt-app` or removed. Android APK scripts that target this tree
-are transitional.
diff --git a/apps/desktop/index.html b/apps/desktop/index.html
deleted file mode 100644
index 5e1f962..0000000
--- a/apps/desktop/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Hermit
-
-
-
-
-
-
diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs
deleted file mode 100644
index 605b374..0000000
--- a/apps/desktop/src-tauri/build.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-fn main() {
- // Optional: link a prebuilt libcstcore when HERMIT_CORE_LIB is set.
- // Example:
- // HERMIT_CORE_LIB=/path/to/libcstcore.a HERMIT_CORE_INCLUDE=/path/to/include
- // cargo build --features engine
- if std::env::var("CARGO_FEATURE_ENGINE").is_ok() {
- if let Ok(lib) = std::env::var("HERMIT_CORE_LIB") {
- let path = std::path::PathBuf::from(&lib);
- if let Some(dir) = path.parent() {
- println!("cargo:rustc-link-search=native={}", dir.display());
- }
- let stem = path
- .file_stem()
- .and_then(|s| s.to_str())
- .unwrap_or("cstcore")
- .trim_start_matches("lib");
- println!("cargo:rustc-link-lib=static={}", stem);
- println!("cargo:rustc-link-lib=curl");
- println!("cargo:rustc-link-lib=pthread");
- println!("cargo:rustc-link-lib=m");
- println!("cargo:rerun-if-changed={}", lib);
- }
- if let Ok(inc) = std::env::var("HERMIT_CORE_INCLUDE") {
- println!("cargo:rerun-if-env-changed=HERMIT_CORE_INCLUDE");
- println!("cargo:rustc-env=HERMIT_CORE_INCLUDE={}", inc);
- }
- }
- tauri_build::build()
-}
diff --git a/apps/desktop/src-tauri/src/engine.rs b/apps/desktop/src-tauri/src/engine.rs
deleted file mode 100644
index 338e790..0000000
--- a/apps/desktop/src-tauri/src/engine.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-//! Engine façade: mock by default; with `--features engine` + HERMIT_CORE_LIB
-//! this module will grow real FFI around `cst_agent.h` (AgentCore parity).
-
-use serde::{Deserialize, Serialize};
-use std::sync::Mutex;
-use tauri::{AppHandle, Emitter};
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SessionInfo {
- pub id: String,
- pub title: String,
- pub updated_at: i64,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(tag = "kind")]
-pub enum ChatItem {
- #[serde(rename = "user")]
- User { id: String, text: String },
- #[serde(rename = "assistant")]
- Assistant { id: String, text: String },
- #[serde(rename = "thought")]
- Thought { id: String, text: String },
- #[serde(rename = "tool")]
- Tool {
- id: String,
- name: String,
- status: String,
- detail: String,
- },
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Settings {
- pub provider: String,
- pub model: String,
- pub approval: String,
- pub workspace: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum EngineEvent {
- #[serde(rename = "assistant_delta")]
- AssistantDelta { text: String },
- #[serde(rename = "thought_delta")]
- ThoughtDelta { text: String },
- #[serde(rename = "tool_start")]
- ToolStart {
- id: String,
- name: String,
- detail: String,
- },
- #[serde(rename = "tool_end")]
- ToolEnd {
- id: String,
- status: String,
- detail: String,
- },
- #[serde(rename = "consent")]
- Consent {
- req: ConsentRequest,
- },
- #[serde(rename = "turn_end")]
- TurnEnd { ok: bool },
- #[serde(rename = "error")]
- Error { message: String },
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ConsentRequest {
- pub id: String,
- pub tool: String,
- pub detail: String,
-}
-
-pub struct EngineState {
- pub settings: Settings,
- pub sessions: Vec,
- busy: bool,
-}
-
-impl Default for EngineState {
- fn default() -> Self {
- let workspace = std::env::current_dir()
- .ok()
- .and_then(|p| p.to_str().map(|s| s.to_string()))
- .unwrap_or_else(|| ".".into());
- Self {
- settings: Settings {
- provider: "deepseek".into(),
- model: "deepseek-flash".into(),
- approval: "auto".into(),
- workspace,
- },
- sessions: vec![SessionInfo {
- id: "welcome".into(),
- title: "Welcome".into(),
- updated_at: chrono_now(),
- }],
- busy: false,
- }
- }
-}
-
-fn chrono_now() -> i64 {
- std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .map(|d| d.as_millis() as i64)
- .unwrap_or(0)
-}
-
-pub type SharedEngine = Mutex;
-
-pub fn list_sessions(state: &SharedEngine) -> Vec {
- state.lock().unwrap().sessions.clone()
-}
-
-pub fn new_session(state: &SharedEngine) -> SessionInfo {
- let mut g = state.lock().unwrap();
- let s = SessionInfo {
- id: format!("s{}", chrono_now()),
- title: "New session".into(),
- updated_at: chrono_now(),
- };
- g.sessions.insert(0, s.clone());
- s
-}
-
-pub fn open_session(_state: &SharedEngine, _id: &str) -> Vec {
- // Real FFI will replay transcript from cst_agent_store_*.
- Vec::new()
-}
-
-pub fn settings_get(state: &SharedEngine) -> Settings {
- state.lock().unwrap().settings.clone()
-}
-
-pub async fn send_turn(app: AppHandle, state: &SharedEngine, text: String) {
- {
- let mut g = state.lock().unwrap();
- if g.busy {
- return;
- }
- g.busy = true;
- }
-
- // Placeholder stream until libcstcore FFI is wired (feature = "engine").
- let _ = app.emit(
- "hermit://event",
- EngineEvent::ThoughtDelta {
- text: "Rust host received turn; FFI bridge pending full AgentCore port."
- .into(),
- },
- );
- tokio::time::sleep(std::time::Duration::from_millis(200)).await;
- let _ = app.emit(
- "hermit://event",
- EngineEvent::AssistantDelta {
- text: format!("Echo (host): {}", text),
- },
- );
- let _ = app.emit("hermit://event", EngineEvent::TurnEnd { ok: true });
- state.lock().unwrap().busy = false;
-}
-
-pub fn cancel_turn(state: &SharedEngine) {
- state.lock().unwrap().busy = false;
-}
-
-pub fn consent_answer(_id: &str, _answer: &str) {
- // Real path: wake the worker blocked in consent hook.
-}
diff --git a/apps/desktop/src-tauri/src/ffi.rs b/apps/desktop/src-tauri/src/ffi.rs
deleted file mode 100644
index 5446f7c..0000000
--- a/apps/desktop/src-tauri/src/ffi.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-//! Future FFI surface against `include/cst_agent.h` (AgentCore parity).
-//!
-//! Enable with:
-//! make -C ../.. libcstcore.a # or cmake cstcore
-//! HERMIT_CORE_LIB=$PWD/../../libcstcore.a \
-//! HERMIT_CORE_INCLUDE=$PWD/../../include \
-//! cargo build --features engine
-//!
-//! Mapping (Qt AgentCore → Tauri commands/events):
-//! open / send / cancel → session_* / send_turn / cancel_turn
-//! hooks.assistant_delta → hermit://event AssistantDelta
-//! hooks.tool_* → ToolStart / ToolEnd
-//! hooks.consent (blocking) → Consent event + consent_answer wakes waiter
-//! settings / creds → settings_get (+ later settings_set)
-
-#![allow(dead_code)]
-
-#[cfg(feature = "engine")]
-mod live {
- // Bindings land here (cbindgen or manual extern "C").
-}
-
-#[cfg(not(feature = "engine"))]
-pub fn engine_linked() -> bool {
- false
-}
-
-#[cfg(feature = "engine")]
-pub fn engine_linked() -> bool {
- true
-}
diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs
deleted file mode 100644
index fbb8f1b..0000000
--- a/apps/desktop/src-tauri/src/lib.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-mod engine;
-mod ffi;
-
-use engine::{SharedEngine, EngineState};
-use std::sync::Mutex;
-use tauri::Manager;
-
-#[tauri::command]
-fn session_list(state: tauri::State<'_, SharedEngine>) -> Vec {
- engine::list_sessions(&state)
-}
-
-#[tauri::command]
-fn session_new(state: tauri::State<'_, SharedEngine>) -> engine::SessionInfo {
- engine::new_session(&state)
-}
-
-#[tauri::command]
-fn session_open(
- state: tauri::State<'_, SharedEngine>,
- id: String,
-) -> Vec {
- engine::open_session(&state, &id)
-}
-
-#[tauri::command]
-fn settings_get(state: tauri::State<'_, SharedEngine>) -> engine::Settings {
- engine::settings_get(&state)
-}
-
-#[tauri::command]
-async fn send_turn(
- app: tauri::AppHandle,
- state: tauri::State<'_, SharedEngine>,
- text: String,
-) -> Result<(), String> {
- engine::send_turn(app, &state, text).await;
- Ok(())
-}
-
-#[tauri::command]
-fn cancel_turn(state: tauri::State<'_, SharedEngine>) {
- engine::cancel_turn(&state);
-}
-
-#[tauri::command]
-fn consent_answer(id: String, answer: String) {
- engine::consent_answer(&id, &answer);
-}
-
-#[cfg_attr(mobile, tauri::mobile_entry_point)]
-pub fn run() {
- tauri::Builder::default()
- .plugin(tauri_plugin_shell::init())
- .manage(Mutex::new(EngineState::default()))
- .invoke_handler(tauri::generate_handler![
- session_list,
- session_new,
- session_open,
- settings_get,
- send_turn,
- cancel_turn,
- consent_answer
- ])
- .setup(|app| {
- if let Some(w) = app.get_webview_window("main") {
- let _ = w.set_title("Hermit");
- }
- Ok(())
- })
- .run(tauri::generate_context!())
- .expect("error while running Hermit desktop");
-}
diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx
deleted file mode 100644
index dc3f52c..0000000
--- a/apps/desktop/src/App.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import { useCallback, useEffect, useState } from "react";
-import { Rail } from "@/components/Rail";
-import { Transcript } from "@/components/Transcript";
-import { Composer } from "@/components/Composer";
-import { ConsentModal } from "@/components/ConsentModal";
-import {
- answerConsent,
- cancelTurn,
- getSettings,
- listSessions,
- newSession,
- onEngineEvent,
- openSession,
- sendTurn,
- type ChatItem,
- type ConsentRequest,
- type SessionInfo,
- type Settings,
-} from "@/lib/bridge";
-import "@/styles/app.css";
-
-export default function App() {
- const [sessions, setSessions] = useState([]);
- const [activeId, setActiveId] = useState(null);
- const [items, setItems] = useState([]);
- const [draft, setDraft] = useState("");
- const [busy, setBusy] = useState(false);
- const [query, setQuery] = useState("");
- const [settings, setSettings] = useState({
- provider: "deepseek",
- model: "deepseek-flash",
- approval: "auto",
- workspace: ".",
- });
- const [consent, setConsent] = useState(null);
- const [showSettings, setShowSettings] = useState(false);
-
- const refreshSessions = useCallback(async () => {
- setSessions(await listSessions());
- }, []);
-
- useEffect(() => {
- void (async () => {
- setSettings(await getSettings());
- await refreshSessions();
- })();
- }, [refreshSessions]);
-
- useEffect(() => {
- let un: (() => void) | undefined;
- void onEngineEvent((ev) => {
- if (ev.type === "assistant_delta") {
- setItems((prev) => {
- const last = prev[prev.length - 1];
- if (last && last.kind === "assistant") {
- return [
- ...prev.slice(0, -1),
- { ...last, text: last.text + ev.text },
- ];
- }
- return [
- ...prev,
- { kind: "assistant", id: `a${Date.now()}`, text: ev.text },
- ];
- });
- } else if (ev.type === "thought_delta") {
- setItems((prev) => {
- const last = prev[prev.length - 1];
- if (last && last.kind === "thought") {
- return [
- ...prev.slice(0, -1),
- { ...last, text: last.text + "\n" + ev.text },
- ];
- }
- return [
- ...prev,
- { kind: "thought", id: `th${Date.now()}`, text: ev.text },
- ];
- });
- } else if (ev.type === "tool_start") {
- setItems((prev) => [
- ...prev,
- {
- kind: "tool",
- id: ev.id,
- name: ev.name,
- status: "running",
- detail: ev.detail,
- },
- ]);
- } else if (ev.type === "tool_end") {
- setItems((prev) =>
- prev.map((it) =>
- it.kind === "tool" && it.id === ev.id
- ? { ...it, status: ev.status, detail: ev.detail || it.detail }
- : it,
- ),
- );
- } else if (ev.type === "consent") {
- setConsent(ev.req);
- } else if (ev.type === "turn_end") {
- setBusy(false);
- } else if (ev.type === "error") {
- setBusy(false);
- setItems((prev) => [
- ...prev,
- {
- kind: "assistant",
- id: `err${Date.now()}`,
- text: `Error: ${ev.message}`,
- },
- ]);
- }
- }).then((u) => {
- un = u;
- });
- return () => un?.();
- }, []);
-
- const open = async (id: string) => {
- setActiveId(id);
- setItems(await openSession(id));
- };
-
- const onNew = async () => {
- const s = await newSession();
- await refreshSessions();
- setActiveId(s.id);
- setItems([]);
- };
-
- const onSend = async () => {
- const text = draft.trim();
- if (!text || busy) return;
- setDraft("");
- setBusy(true);
- setItems((prev) => [
- ...prev,
- { kind: "user", id: `u${Date.now()}`, text },
- ]);
- await sendTurn(text);
- };
-
- return (
-
-
void onNew()}
- onOpen={(id) => void open(id)}
- onSettings={() => setShowSettings(true)}
- />
-
-
-
- void onSend()}
- onStop={() => void cancelTurn()}
- />
-
- {consent ? (
- {
- void answerConsent(consent.id, a);
- setConsent(null);
- }}
- />
- ) : null}
- {showSettings ? (
- setShowSettings(false)}>
-
e.stopPropagation()}
- >
-
Settings
-
{JSON.stringify(settings, null, 2)}
-
-
-
-
-
- ) : null}
-
- );
-}
diff --git a/apps/desktop/src/components/Composer.tsx b/apps/desktop/src/components/Composer.tsx
deleted file mode 100644
index 9fbea66..0000000
--- a/apps/desktop/src/components/Composer.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useEffect, useRef } from "react";
-import type { Settings } from "@/lib/bridge";
-
-type Props = {
- value: string;
- busy: boolean;
- settings: Settings;
- onChange: (v: string) => void;
- onSend: () => void;
- onStop: () => void;
-};
-
-export function Composer({
- value,
- busy,
- settings,
- onChange,
- onSend,
- onStop,
-}: Props) {
- const ref = useRef(null);
- useEffect(() => {
- const el = ref.current;
- if (!el) return;
- el.style.height = "0px";
- el.style.height = Math.min(200, el.scrollHeight) + "px";
- }, [value]);
-
- return (
-
- );
-}
diff --git a/apps/desktop/src/components/ConsentModal.tsx b/apps/desktop/src/components/ConsentModal.tsx
deleted file mode 100644
index 52b4e1a..0000000
--- a/apps/desktop/src/components/ConsentModal.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { ConsentRequest } from "@/lib/bridge";
-
-type Props = {
- req: ConsentRequest;
- onAnswer: (a: "deny" | "once" | "always_tool" | "always_prefix") => void;
-};
-
-export function ConsentModal({ req, onAnswer }: Props) {
- return (
-
-
-
Allow {req.tool}?
-
{req.detail}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/desktop/src/components/Rail.tsx b/apps/desktop/src/components/Rail.tsx
deleted file mode 100644
index 9194a3a..0000000
--- a/apps/desktop/src/components/Rail.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { SessionInfo } from "@/lib/bridge";
-
-type Props = {
- sessions: SessionInfo[];
- activeId: string | null;
- workspace: string;
- query: string;
- onQuery: (q: string) => void;
- onNew: () => void;
- onOpen: (id: string) => void;
- onSettings: () => void;
-};
-
-export function Rail({
- sessions,
- activeId,
- workspace,
- query,
- onQuery,
- onNew,
- onOpen,
- onSettings,
-}: Props) {
- const filtered = sessions.filter(
- (s) => !query || s.title.toLowerCase().includes(query.toLowerCase()),
- );
- return (
-
- );
-}
diff --git a/apps/desktop/src/components/Transcript.tsx b/apps/desktop/src/components/Transcript.tsx
deleted file mode 100644
index 4046124..0000000
--- a/apps/desktop/src/components/Transcript.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { ChatItem } from "@/lib/bridge";
-import ReactMarkdown from "react-markdown";
-import remarkGfm from "remark-gfm";
-
-type Props = { items: ChatItem[] };
-
-export function Transcript({ items }: Props) {
- if (items.length === 0) {
- return (
-
-
-
Meet Hermit
-
- Local coding agent — read and edit your workspace, run commands with
- approval, keep sessions on disk. Describe a task to begin.
-
-
-
- );
- }
- return (
-
- {items.map((it) => {
- if (it.kind === "user") {
- return (
-
- );
- }
- if (it.kind === "assistant") {
- return (
-
-
Hermit
-
- {it.text}
-
-
- );
- }
- if (it.kind === "thought") {
- return (
-
- Thought for a while
- {it.text}
-
- );
- }
- return (
-
-
-
- {it.name}
- {it.status}
-
- {it.detail ?
{it.detail}
: null}
-
- );
- })}
-
- );
-}
diff --git a/apps/desktop/src/lib/bridge.ts b/apps/desktop/src/lib/bridge.ts
deleted file mode 100644
index 5582030..0000000
--- a/apps/desktop/src/lib/bridge.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/** IPC / mock bridge for Hermit desktop. */
-
-export type SessionInfo = {
- id: string;
- title: string;
- updated_at: number;
-};
-
-export type ConsentRequest = {
- id: string;
- tool: string;
- detail: string;
-};
-
-export type ChatItem =
- | { kind: "user"; id: string; text: string }
- | { kind: "assistant"; id: string; text: string }
- | { kind: "thought"; id: string; text: string }
- | {
- kind: "tool";
- id: string;
- name: string;
- status: "running" | "ok" | "denied" | "error";
- detail: string;
- };
-
-export type EngineEvent =
- | { type: "assistant_delta"; text: string }
- | { type: "thought_delta"; text: string }
- | { type: "tool_start"; id: string; name: string; detail: string }
- | { type: "tool_end"; id: string; status: "ok" | "denied" | "error"; detail: string }
- | { type: "consent"; req: ConsentRequest }
- | { type: "turn_end"; ok: boolean }
- | { type: "error"; message: string };
-
-export type Settings = {
- provider: string;
- model: string;
- approval: "ask" | "auto" | "yolo";
- workspace: string;
-};
-
-type Unlisten = () => void;
-
-async function tryInvoke(cmd: string, args?: Record): Promise {
- try {
- const { invoke } = await import("@tauri-apps/api/core");
- return await invoke(cmd, args);
- } catch {
- return null;
- }
-}
-
-const mockSessions: SessionInfo[] = [
- {
- id: "s1",
- title: "Fix home TUI layout",
- updated_at: Date.now() - 3600_000,
- },
- {
- id: "s2",
- title: "Ship hermit-update",
- updated_at: Date.now() - 86400_000,
- },
-];
-
-let mockBusy = false;
-const mockListeners = new Set<(e: EngineEvent) => void>();
-
-function emitMock(e: EngineEvent) {
- mockListeners.forEach((fn) => fn(e));
-}
-
-export async function listSessions(): Promise {
- const real = await tryInvoke("session_list");
- return real ?? mockSessions;
-}
-
-export async function newSession(): Promise {
- const real = await tryInvoke("session_new");
- if (real) return real;
- const s: SessionInfo = {
- id: `s${Date.now()}`,
- title: "New session",
- updated_at: Date.now(),
- };
- mockSessions.unshift(s);
- return s;
-}
-
-export async function openSession(id: string): Promise {
- const real = await tryInvoke("session_open", { id });
- if (real) return real;
- if (id === "s1") {
- return [
- { kind: "user", id: "u1", text: "右上角布局不对,帮我看看。" },
- {
- kind: "thought",
- id: "t1",
- text: "Sidebar starts at row 0 while topbar only covers the main pane…",
- },
- {
- kind: "tool",
- id: "tool1",
- name: "read_file",
- status: "ok",
- detail: "src/tui.c · draw_side / layout",
- },
- {
- kind: "assistant",
- id: "a1",
- text: "顶栏只盖主区、侧栏从第 0 行铺满,右上角会空出一块。已把 topbar 拉满并把 seat deck 对齐到顶栏下方。",
- },
- ];
- }
- return [];
-}
-
-export async function getSettings(): Promise {
- const real = await tryInvoke("settings_get");
- return (
- real ?? {
- provider: "deepseek",
- model: "deepseek-flash",
- approval: "auto",
- workspace: "~/hermit",
- }
- );
-}
-
-export async function sendTurn(text: string): Promise {
- const real = await tryInvoke("send_turn", { text });
- if (real !== null) return;
- if (mockBusy) return;
- mockBusy = true;
- emitMock({ type: "thought_delta", text: "Planning a short reply…" });
- await delay(400);
- emitMock({
- type: "tool_start",
- id: "m1",
- name: "shell",
- detail: "hermit --version",
- });
- await delay(500);
- emitMock({
- type: "tool_end",
- id: "m1",
- status: "ok",
- detail: "hermit 1.1.0",
- });
- const reply =
- "这是桌面壳的本地演示流(未链上引擎时)。接上 libcstcore 后,这里会变成真实 turn。";
- for (const ch of reply) {
- emitMock({ type: "assistant_delta", text: ch });
- await delay(12);
- }
- emitMock({ type: "turn_end", ok: true });
- mockBusy = false;
-}
-
-export async function cancelTurn(): Promise {
- await tryInvoke("cancel_turn");
- mockBusy = false;
- emitMock({ type: "turn_end", ok: false });
-}
-
-export async function answerConsent(
- id: string,
- answer: "deny" | "once" | "always_tool" | "always_prefix",
-): Promise {
- await tryInvoke("consent_answer", { id, answer });
-}
-
-export async function onEngineEvent(fn: (e: EngineEvent) => void): Promise {
- try {
- const { listen } = await import("@tauri-apps/api/event");
- const un = await listen("hermit://event", (ev) => fn(ev.payload));
- return () => {
- un();
- };
- } catch {
- mockListeners.add(fn);
- return () => {
- mockListeners.delete(fn);
- };
- }
-}
-
-function delay(ms: number) {
- return new Promise((r) => setTimeout(r, ms));
-}
diff --git a/apps/desktop/src/styles/app.css b/apps/desktop/src/styles/app.css
deleted file mode 100644
index 1d7b2d0..0000000
--- a/apps/desktop/src/styles/app.css
+++ /dev/null
@@ -1,425 +0,0 @@
-@import "./theme.css";
-
-.app {
- display: grid;
- grid-template-columns: var(--rail-w) 1fr;
- height: 100%;
- overflow: hidden;
-}
-
-/* —— left rail (DSH-like) —— */
-.rail {
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding: 14px 12px;
- background: var(--bg-elev);
- border-right: 1px solid var(--border);
-}
-
-.brand {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 4px 6px;
-}
-.brand-mark {
- width: 28px;
- height: 28px;
- border-radius: 8px;
- background: linear-gradient(145deg, var(--brand), var(--brand-dim));
- box-shadow: inset 0 0 0 1px rgba(169, 217, 215, 0.25);
- display: grid;
- place-items: center;
- color: var(--accent-text);
- font-weight: 700;
- font-size: 13px;
-}
-.brand-name {
- font-weight: 650;
- letter-spacing: 0.02em;
-}
-.brand-sub {
- color: var(--text-faint);
- font-size: 11px;
-}
-
-.btn-new {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- height: 40px;
- border-radius: var(--radius-sm);
- background: var(--brand);
- color: #fff;
- font-weight: 600;
- transition: background 0.15s ease;
-}
-.btn-new:hover {
- background: #2f94ab;
-}
-
-.rail-sec {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 4px 6px 0;
- color: var(--text-faint);
- font-size: 11px;
- text-transform: uppercase;
- letter-spacing: 0.06em;
-}
-
-.sess-search {
- width: 100%;
- height: 34px;
- padding: 0 10px;
- border-radius: var(--radius-sm);
- border: 1px solid var(--border);
- background: var(--bg);
- outline: none;
-}
-.sess-search:focus {
- border-color: var(--brand);
-}
-
-.sessions {
- flex: 1;
- overflow: auto;
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding-right: 2px;
-}
-
-.sess {
- text-align: left;
- padding: 10px 10px;
- border-radius: var(--radius-sm);
- color: var(--text-muted);
- border: 1px solid transparent;
-}
-.sess:hover {
- background: var(--bg-hover);
- color: var(--text);
-}
-.sess.active {
- background: var(--bg-card);
- border-color: var(--border-strong);
- color: var(--text);
-}
-.sess-title {
- font-weight: 550;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.sess-meta {
- font-size: 11px;
- color: var(--text-faint);
- margin-top: 2px;
-}
-
-.rail-foot {
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding-top: 8px;
- border-top: 1px solid var(--border);
-}
-.workspace {
- padding: 8px 10px;
- border-radius: var(--radius-sm);
- background: var(--bg);
- border: 1px solid var(--border);
- color: var(--text-muted);
- font-size: 12px;
- font-family: var(--mono);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.rail-actions {
- display: flex;
- gap: 6px;
-}
-.icon-btn {
- flex: 1;
- height: 34px;
- border-radius: var(--radius-sm);
- border: 1px solid var(--border);
- color: var(--text-muted);
-}
-.icon-btn:hover {
- background: var(--bg-hover);
- color: var(--text);
-}
-
-/* —— main column —— */
-.main {
- display: flex;
- flex-direction: column;
- min-width: 0;
- background: var(--bg);
-}
-
-.topbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 12px var(--pad);
- border-bottom: 1px solid var(--border);
-}
-.topbar h1 {
- margin: 0;
- font-size: 15px;
- font-weight: 600;
-}
-.chips {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
-}
-.chip {
- height: 28px;
- padding: 0 10px;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: var(--bg-elev);
- color: var(--text-muted);
- font-size: 12px;
- display: inline-flex;
- align-items: center;
- gap: 6px;
-}
-.chip strong {
- color: var(--text);
- font-weight: 550;
-}
-.chip.ok {
- border-color: rgba(61, 154, 106, 0.35);
- color: var(--ok);
-}
-
-.transcript {
- flex: 1;
- overflow: auto;
- padding: 20px var(--pad) 8px;
- display: flex;
- flex-direction: column;
- gap: 14px;
-}
-
-.empty {
- margin: auto;
- max-width: 420px;
- text-align: center;
- color: var(--text-muted);
-}
-.empty h2 {
- margin: 0 0 8px;
- color: var(--text);
- font-weight: 600;
- font-size: 20px;
-}
-.empty p {
- margin: 0;
- color: var(--text-faint);
-}
-
-.msg {
- max-width: 820px;
- width: 100%;
- margin: 0 auto;
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-.msg.user {
- align-items: flex-end;
-}
-.msg-role {
- font-size: 11px;
- color: var(--text-faint);
- letter-spacing: 0.04em;
- text-transform: uppercase;
-}
-.bubble {
- padding: 12px 14px;
- border-radius: var(--radius);
- background: var(--bg-card);
- border: 1px solid var(--border);
- box-shadow: var(--shadow);
- white-space: pre-wrap;
- word-break: break-word;
-}
-.msg.user .bubble {
- background: linear-gradient(160deg, var(--brand-dim), #1a4a56);
- border-color: transparent;
-}
-.msg.assistant .bubble {
- background: var(--bg-elev);
-}
-
-.thought,
-.tool-card {
- max-width: 820px;
- width: 100%;
- margin: 0 auto;
- border-radius: var(--radius);
- border: 1px solid var(--border);
- background: var(--bg-elev);
- overflow: hidden;
-}
-.thought summary,
-.tool-head {
- list-style: none;
- cursor: pointer;
- padding: 10px 12px;
- color: var(--text-muted);
- font-size: 12px;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-.thought summary::-webkit-details-marker {
- display: none;
-}
-.thought[open] summary {
- border-bottom: 1px solid var(--border);
-}
-.thought-body,
-.tool-body {
- padding: 10px 12px;
- font-family: var(--mono);
- font-size: 12px;
- color: var(--text-muted);
- white-space: pre-wrap;
-}
-.tool-head .dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: var(--crab);
-}
-.tool-head .name {
- color: var(--text);
- font-weight: 550;
-}
-.tool-head .status {
- margin-left: auto;
- color: var(--text-faint);
-}
-
-.composer-wrap {
- padding: 10px var(--pad) 16px;
-}
-.composer {
- max-width: 820px;
- margin: 0 auto;
- border: 1px solid var(--border-strong);
- background: var(--bg-card);
- border-radius: 16px;
- padding: 10px 12px 8px;
- box-shadow: var(--shadow);
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-.composer textarea {
- width: 100%;
- min-height: 56px;
- max-height: 200px;
- resize: none;
- border: none;
- outline: none;
- background: transparent;
- padding: 4px 2px;
-}
-.composer-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
-}
-.composer-left {
- display: flex;
- gap: 6px;
- flex-wrap: wrap;
-}
-.send {
- height: 34px;
- min-width: 72px;
- padding: 0 14px;
- border-radius: 999px;
- background: var(--crab);
- color: #1a100c;
- font-weight: 650;
-}
-.send:disabled {
- opacity: 0.45;
- cursor: default;
-}
-.send.stop {
- background: var(--danger);
- color: #fff;
-}
-
-.consent {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.55);
- display: grid;
- place-items: center;
- z-index: 40;
-}
-.consent-card {
- width: min(480px, calc(100% - 32px));
- background: var(--bg-card);
- border: 1px solid var(--border-strong);
- border-radius: var(--radius);
- padding: 18px;
- box-shadow: var(--shadow);
-}
-.consent-card h3 {
- margin: 0 0 8px;
-}
-.consent-card pre {
- margin: 0 0 14px;
- padding: 10px;
- background: var(--bg);
- border-radius: var(--radius-sm);
- font-family: var(--mono);
- font-size: 12px;
- color: var(--text-muted);
- max-height: 180px;
- overflow: auto;
-}
-.consent-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: flex-end;
-}
-.consent-actions button {
- height: 34px;
- padding: 0 12px;
- border-radius: var(--radius-sm);
- border: 1px solid var(--border);
-}
-.consent-actions .primary {
- background: var(--brand);
- border-color: transparent;
- color: #fff;
-}
-
-@media (max-width: 880px) {
- .app {
- grid-template-columns: 1fr;
- }
- .rail {
- display: none;
- }
-}
diff --git a/apps/desktop/src/styles/theme.css b/apps/desktop/src/styles/theme.css
deleted file mode 100644
index 6b50bdd..0000000
--- a/apps/desktop/src/styles/theme.css
+++ /dev/null
@@ -1,72 +0,0 @@
-:root {
- /* Hermit brand + DSH-like rhythm */
- --brand: #277f94;
- --brand-dim: #1d5f6e;
- --crab: #f68042;
- --eye: #9957ad;
- --ok: #3d9a6a;
- --warn: #e9ba47;
- --danger: #c45c4a;
-
- --bg: #0f1114;
- --bg-elev: #161a1f;
- --bg-card: #1b2128;
- --bg-hover: #222933;
- --border: rgba(255, 255, 255, 0.06);
- --border-strong: rgba(255, 255, 255, 0.12);
- --text: #e8ecf0;
- --text-muted: #8b949e;
- --text-faint: #5c6570;
- --accent-text: #a9d9d7;
-
- --radius: 12px;
- --radius-sm: 8px;
- --pad: 16px;
- --rail-w: 260px;
- --font: "IBM Plex Sans", "Segoe UI", "Noto Sans SC", system-ui, sans-serif;
- --mono: "IBM Plex Mono", "JetBrains Mono", ui-monospace, monospace;
- --shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
-}
-
-* {
- box-sizing: border-box;
-}
-
-html,
-body,
-#root {
- height: 100%;
- margin: 0;
- background: var(--bg);
- color: var(--text);
- font-family: var(--font);
- font-size: 14px;
- line-height: 1.45;
- -webkit-font-smoothing: antialiased;
-}
-
-button,
-input,
-textarea {
- font: inherit;
- color: inherit;
-}
-
-button {
- cursor: pointer;
- border: none;
- background: none;
-}
-
-a {
- color: var(--accent-text);
-}
-
-::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.12);
- border-radius: 8px;
-}
diff --git a/apps/desktop/src/vite-env.d.ts b/apps/desktop/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe..0000000
--- a/apps/desktop/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/apps/desktop/tsconfig.tsbuildinfo b/apps/desktop/tsconfig.tsbuildinfo
deleted file mode 100644
index 413b90c..0000000
--- a/apps/desktop/tsconfig.tsbuildinfo
+++ /dev/null
@@ -1 +0,0 @@
-{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Composer.tsx","./src/components/ConsentModal.tsx","./src/components/Rail.tsx","./src/components/Transcript.tsx","./src/lib/bridge.ts"],"version":"5.9.3"}
\ No newline at end of file
diff --git a/archive/README.md b/archive/README.md
new file mode 100644
index 0000000..9ba6c3e
--- /dev/null
+++ b/archive/README.md
@@ -0,0 +1,22 @@
+# archive/ — frozen clients (not shipped with Hermit)
+
+This tree holds **reference front ends** that used to live in the main repo.
+The Hermit product surface is now:
+
+- **CLI + minimal TUI** (`hermit`)
+- **Engine library** (`libcstcore.a` + `include/`)
+- **Replaceable extensions**: plugins / MCP / skills / agents (not core tools)
+- **Client contracts**: `cst_agent_hooks_t` and HTTP/SSE `cst-webui/1` (`hermit --web`)
+
+| Path | Former path | Notes |
+|------|-------------|--------|
+| [`qt-app/`](qt-app/) | `app/` | Qt6/QML GUI (deprecated). Experimental CMake: `-DCST_BUILD_APP=ON`. |
+| [`desktop-tauri/`](desktop-tauri/) | `apps/desktop/` | Tauri 2 + React shell (FFI into `libcstcore`). |
+| [`web-spa/`](web-spa/) | `web/` | Full browser SPA. Load with `hermit --web --web-dev archive/web-spa`. |
+
+**Do not expect** these trees to stay buildable without manual path fixes.
+Default `make` / CI does **not** compile them.
+
+For a new external client, implement against [`docs/webui.md`](../docs/webui.md) and
+[`docs/webui-schema.md`](../docs/webui-schema.md), or embed via `cst_agent.h` hooks / the
+archived FFI bridge under `desktop-tauri/src-tauri/src/ffi_bridge.*`.
diff --git a/apps/desktop/.gitignore b/archive/desktop-tauri/.gitignore
similarity index 51%
rename from apps/desktop/.gitignore
rename to archive/desktop-tauri/.gitignore
index 73c51c8..05caee5 100644
--- a/apps/desktop/.gitignore
+++ b/archive/desktop-tauri/.gitignore
@@ -3,3 +3,7 @@ dist/
src-tauri/target/
.DS_Store
*.log
+
+.layout-check.png
+.layout-dsh.png
+.layout-dsh-qa/
diff --git a/apps/desktop/README.md b/archive/desktop-tauri/README.md
similarity index 96%
rename from apps/desktop/README.md
rename to archive/desktop-tauri/README.md
index 43ac3ff..20bc3df 100644
--- a/apps/desktop/README.md
+++ b/archive/desktop-tauri/README.md
@@ -28,7 +28,7 @@ nix-shell -p rustc cargo nodejs_22 pkg-config webkitgtk_4_1 gtk3 librsvg openssl
## Develop
```bash
-cd apps/desktop
+cd archive/desktop-tauri
npm install
npm run tauri dev
```
@@ -47,11 +47,12 @@ Artifacts land in `src-tauri/target/release/bundle/`.
## Layout
```
-apps/desktop/
+archive/desktop-tauri/
src/ React UI (DSH-like)
src-tauri/ Rust + Tauri + FFI
README.md
```
+
The legacy Qt front end under `/app` is frozen and will be archived once
desktop feature parity lands.
diff --git a/archive/desktop-tauri/index.html b/archive/desktop-tauri/index.html
new file mode 100644
index 0000000..ca0bf6c
--- /dev/null
+++ b/archive/desktop-tauri/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Hermit
+
+
+
+
+
+
+
diff --git a/apps/desktop/package-lock.json b/archive/desktop-tauri/package-lock.json
similarity index 100%
rename from apps/desktop/package-lock.json
rename to archive/desktop-tauri/package-lock.json
diff --git a/apps/desktop/package.json b/archive/desktop-tauri/package.json
similarity index 100%
rename from apps/desktop/package.json
rename to archive/desktop-tauri/package.json
diff --git a/archive/desktop-tauri/run-dev.sh b/archive/desktop-tauri/run-dev.sh
new file mode 100755
index 0000000..de0dccf
--- /dev/null
+++ b/archive/desktop-tauri/run-dev.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+# Hermit Desktop — official local dev entry (engine preferred).
+#
+# Required for real turns:
+# - libcstcore.a at repo root (auto-enables --features engine), OR HERMIT_FEATURES=engine
+# - DEEPSEEK_API_KEY in the environment, OR set the key in Settings after launch
+#
+# Host-only (no engine): HERMIT_FEATURES=host ./run-dev.sh
+# Composer will refuse send until engine is available (no silent mock product path).
+# UI mock turns (browser/host demos only): localStorage hermit.allowMock=1 or VITE_HERMIT_ALLOW_MOCK=1
+#
+# Hand-check: new session → type → streaming assistant → Stop → reopen session has history
+set -euo pipefail
+cd "$(dirname "$0")"
+
+# 补 PKG_CONFIG_PATH(勿整文件 source set-environment:会撞 set -u)
+if [[ -z "${PKG_CONFIG_PATH:-}" ]]; then
+ if [[ -f /etc/set-environment ]]; then
+ PKG_CONFIG_PATH="$(set +u; . /etc/set-environment >/dev/null; printf '%s' "${PKG_CONFIG_PATH:-}")"
+ export PKG_CONFIG_PATH
+ fi
+fi
+
+if [[ -z "${PKG_CONFIG_PATH:-}" ]]; then
+ echo "hermit-dev: PKG_CONFIG_PATH 为空。请用 hermit-tauri-dev 或 nixos-rebuild 后重开终端" >&2
+ exit 1
+fi
+if ! pkg-config --exists webkit2gtk-4.1 gdk-pixbuf-2.0 2>/dev/null; then
+ echo "hermit-dev: 缺少 webkit/gdk-pixbuf .pc" >&2
+ exit 1
+fi
+
+unset WEBKIT_DISABLE_COMPOSITING_MODE || true
+
+if [[ -f /proc/driver/nvidia/version ]]; then
+ export __NV_DISABLE_EXPLICIT_SYNC="${__NV_DISABLE_EXPLICIT_SYNC:-1}"
+ # X11/XWayland 上 WebKit DMABUF →「Failed to create GBM buffer」→ 空白灰窗
+ # 必须关 DMABUF;Wayland 原生则可只靠 __NV_DISABLE_EXPLICIT_SYNC
+ if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then
+ export GDK_BACKEND="${GDK_BACKEND:-x11}"
+ fi
+ if [[ "${GDK_BACKEND:-}" == x11 ]]; then
+ export WEBKIT_DISABLE_DMABUF_RENDERER="${WEBKIT_DISABLE_DMABUF_RENDERER:-1}"
+ else
+ unset WEBKIT_DISABLE_DMABUF_RENDERER || true
+ fi
+fi
+
+export TAURI_DEV_HOST="${TAURI_DEV_HOST:-127.0.0.1}"
+
+# Optional real engine (libcstcore). Example:
+# HERMIT_FEATURES=engine ./run-dev.sh
+# # or:
+# HERMIT_CORE_LIB=$PWD/../../libcstcore.a \
+# HERMIT_CORE_INCLUDE=$PWD/../../include \
+# npm run tauri -- dev -- --features engine
+TAURI_EXTRA=()
+ROOT="$(cd ../.. && pwd)"
+# Auto-enable engine when libcstcore.a exists, unless HERMIT_FEATURES=host
+if [[ "${HERMIT_FEATURES:-}" == *engine* ]] || {
+ [[ "${HERMIT_FEATURES:-}" != *host* ]] && [[ -f "$ROOT/libcstcore.a" ]]
+}; then
+ export HERMIT_CORE_LIB="${HERMIT_CORE_LIB:-$ROOT/libcstcore.a}"
+ export HERMIT_CORE_INCLUDE="${HERMIT_CORE_INCLUDE:-$ROOT/include}"
+ if [[ -f "$HERMIT_CORE_LIB" ]]; then
+ # NixOS: -lcurl needs an explicit libdir (pkg-config or ldd).
+ if [[ -z "${HERMIT_CURL_LIBDIR:-}" ]]; then
+ if pkg-config --exists libcurl 2>/dev/null; then
+ HERMIT_CURL_LIBDIR="$(pkg-config --variable=libdir libcurl 2>/dev/null || true)"
+ fi
+ if [[ -z "${HERMIT_CURL_LIBDIR:-}" ]] && command -v curl >/dev/null 2>&1; then
+ curl_so="$(ldd "$(command -v curl)" 2>/dev/null | awk '/libcurl\.so/ { print $3; exit }' || true)"
+ if [[ -n "${curl_so:-}" && -e "$curl_so" ]]; then
+ HERMIT_CURL_LIBDIR="$(dirname "$curl_so")"
+ fi
+ fi
+ if [[ -n "${HERMIT_CURL_LIBDIR:-}" ]]; then
+ export HERMIT_CURL_LIBDIR
+ export LD_LIBRARY_PATH="${HERMIT_CURL_LIBDIR}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+ fi
+ fi
+ echo "hermit-dev: engine feature HERMIT_CORE_LIB=$HERMIT_CORE_LIB curl=${HERMIT_CURL_LIBDIR:-default}" >&2
+ TAURI_EXTRA+=(--features engine)
+ else
+ echo "hermit-dev: engine requested but missing $HERMIT_CORE_LIB (host mock)" >&2
+ fi
+fi
+
+for p in 1420 1421; do
+ if command -v fuser >/dev/null 2>&1; then
+ fuser -k "${p}/tcp" 2>/dev/null || true
+ else
+ mapfile -t pids < <(ss -ltnp 2>/dev/null | awk -v p=":$p" '$4 ~ p {
+ if (match($0, /pid=[0-9]+/)) { print substr($0, RSTART+4, RLENGTH-4) }
+ }' | sort -u)
+ for pid in "${pids[@]:-}"; do
+ [[ -n "${pid:-}" ]] && kill "$pid" 2>/dev/null || true
+ done
+ fi
+done
+sleep 0.3
+
+if [[ -z "${DEEPSEEK_API_KEY:-}" && -z "${OPENAI_API_KEY:-}" ]]; then
+ echo "hermit-dev: note — no DEEPSEEK_API_KEY/OPENAI_API_KEY in env; set one or paste key in Settings" >&2
+fi
+
+if printf '%s\n' "${TAURI_EXTRA[@]:-}" | grep -qx -- '--features' && printf '%s\n' "${TAURI_EXTRA[@]:-}" | grep -qx engine; then
+ echo "hermit-dev: mode=engine (libcstcore)" >&2
+elif printf '%s\n' "${TAURI_EXTRA[@]:-}" | grep -q engine; then
+ echo "hermit-dev: mode=engine (libcstcore)" >&2
+else
+ echo "hermit-dev: mode=host (Composer send disabled until engine)" >&2
+fi
+
+echo "hermit-dev: GDK_BACKEND=${GDK_BACKEND:-none} DMABUF_OFF=${WEBKIT_DISABLE_DMABUF_RENDERER:-0} NV_SYNC=${__NV_DISABLE_EXPLICIT_SYNC:-0} host=${TAURI_DEV_HOST}" >&2
+exec npm run tauri -- dev "${TAURI_EXTRA[@]}" "$@"
diff --git a/apps/desktop/src-tauri/Cargo.lock b/archive/desktop-tauri/src-tauri/Cargo.lock
similarity index 98%
rename from apps/desktop/src-tauri/Cargo.lock
rename to archive/desktop-tauri/src-tauri/Cargo.lock
index 9cce38b..61ae754 100644
--- a/apps/desktop/src-tauri/Cargo.lock
+++ b/archive/desktop-tauri/src-tauri/Cargo.lock
@@ -1220,6 +1220,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
+ "tauri-plugin-dialog",
"tauri-plugin-shell",
"tokio",
]
@@ -2059,6 +2060,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.13.2",
"block2",
+ "libc",
"objc2",
"objc2-core-foundation",
]
@@ -2545,6 +2547,32 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "rfd"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
+dependencies = [
+ "block2",
+ "dispatch2",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "js-sys",
+ "libc",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "percent-encoding",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "rustc-hash"
version = "2.1.3"
@@ -3241,6 +3269,48 @@ dependencies = [
"walkdir",
]
+[[package]]
+name = "tauri-plugin-dialog"
+version = "2.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce0fb9c2b6d34a9720627ed930ebc8af26f61e648695aecaeb03d5c33c5896f5"
+dependencies = [
+ "log",
+ "raw-window-handle",
+ "rfd",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "tauri-plugin-fs",
+ "thiserror 2.0.21",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-fs"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d58eb1d4984e535f47a642ffec09a2da6f5c18a12b5902d12126cb7626d628b"
+dependencies = [
+ "anyhow",
+ "dunce",
+ "glob",
+ "log",
+ "objc2-foundation",
+ "percent-encoding",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-plugin",
+ "tauri-utils",
+ "thiserror 2.0.21",
+ "toml 1.1.8+spec-1.1.0",
+ "url",
+]
+
[[package]]
name = "tauri-plugin-shell"
version = "2.6.0"
diff --git a/apps/desktop/src-tauri/Cargo.toml b/archive/desktop-tauri/src-tauri/Cargo.toml
similarity index 96%
rename from apps/desktop/src-tauri/Cargo.toml
rename to archive/desktop-tauri/src-tauri/Cargo.toml
index 3d8e4c7..0ff8d4e 100644
--- a/apps/desktop/src-tauri/Cargo.toml
+++ b/archive/desktop-tauri/src-tauri/Cargo.toml
@@ -16,6 +16,7 @@ cc = "1.2"
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
+tauri-plugin-dialog = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros", "time"] }
diff --git a/archive/desktop-tauri/src-tauri/build.rs b/archive/desktop-tauri/src-tauri/build.rs
new file mode 100644
index 0000000..21dc2d0
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/build.rs
@@ -0,0 +1,175 @@
+fn main() {
+ // Optional: link a prebuilt libcstcore when HERMIT_CORE_LIB is set.
+ // Example:
+ // HERMIT_CORE_LIB=/path/to/libcstcore.a HERMIT_CORE_INCLUDE=/path/to/include
+ // cargo build --features engine
+ if std::env::var("CARGO_FEATURE_ENGINE").is_ok() {
+ let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
+ // src-tauri → desktop → apps → repo root
+ let repo_root = manifest_dir.join("../../..");
+ let include = std::env::var("HERMIT_CORE_INCLUDE").unwrap_or_else(|_| {
+ repo_root
+ .join("include")
+ .canonicalize()
+ .map(|p| p.display().to_string())
+ .unwrap_or_else(|_| repo_root.join("include").display().to_string())
+ });
+
+ let mut build = cc::Build::new();
+ build
+ .file("src/ffi_bridge.c")
+ .include(&include)
+ .include("src")
+ .warnings(false);
+ // libcstcore pulls C11 + POSIX.
+ build.std("c11");
+ build.compile("hermit_ffi_bridge");
+ println!("cargo:rerun-if-changed=src/ffi_bridge.c");
+ println!("cargo:rerun-if-changed=src/ffi_bridge.h");
+
+ let core = std::env::var("HERMIT_CORE_LIB")
+ .ok()
+ .map(std::path::PathBuf::from)
+ .filter(|p| p.exists())
+ .or_else(|| {
+ let fallback = repo_root.join("libcstcore.a");
+ fallback.exists().then_some(fallback)
+ });
+
+ if let Some(path) = core {
+ if let Some(dir) = path.parent() {
+ println!("cargo:rustc-link-search=native={}", dir.display());
+ }
+ let stem = path
+ .file_stem()
+ .and_then(|s| s.to_str())
+ .unwrap_or("cstcore")
+ .trim_start_matches("lib");
+ println!("cargo:rustc-link-lib=static={}", stem);
+ // libcstcore leaves sashidown symbols unresolved (same as Makefile LDLIBS).
+ let sashidown_dir = repo_root.join("vendor/sashidown");
+ let sashidown_a = sashidown_dir.join("libsashidown.a");
+ if sashidown_a.exists() {
+ println!(
+ "cargo:rustc-link-search=native={}",
+ sashidown_dir
+ .canonicalize()
+ .unwrap_or_else(|_| sashidown_dir.clone())
+ .display()
+ );
+ println!("cargo:rustc-link-lib=static=sashidown");
+ println!("cargo:rerun-if-changed={}", sashidown_a.display());
+ } else {
+ println!(
+ "cargo:warning=missing {} (make -C vendor/sashidown libsashidown.a)",
+ sashidown_a.display()
+ );
+ }
+ link_curl_and_sys();
+ println!("cargo:rerun-if-changed={}", path.display());
+ } else {
+ println!(
+ "cargo:warning=feature engine enabled but HERMIT_CORE_LIB unset and libcstcore.a missing"
+ );
+ }
+ println!("cargo:rerun-if-env-changed=HERMIT_CORE_LIB");
+ println!("cargo:rerun-if-env-changed=HERMIT_CORE_INCLUDE");
+ println!("cargo:rerun-if-env-changed=HERMIT_CURL_LIBDIR");
+ println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
+ println!("cargo:rustc-env=HERMIT_CORE_INCLUDE={}", include);
+ }
+ tauri_build::build()
+}
+
+/// libcstcore needs libcurl (+ pthread/m/dl). On NixOS `-lcurl` alone fails
+/// because curl is not on the default linker path — resolve via pkg-config,
+/// HERMIT_CURL_LIBDIR, or `ldd $(command -v curl)`.
+fn link_curl_and_sys() {
+ if link_curl_via_pkg_config() || link_curl_via_env() || link_curl_via_ldd() {
+ // ok
+ } else {
+ println!("cargo:warning=libcurl path not found; falling back to -lcurl");
+ println!("cargo:rustc-link-lib=curl");
+ }
+ println!("cargo:rustc-link-lib=pthread");
+ println!("cargo:rustc-link-lib=m");
+ println!("cargo:rustc-link-lib=dl");
+}
+
+fn apply_pkg_libs(libs: &str) -> bool {
+ let mut saw_curl = false;
+ for tok in libs.split_whitespace() {
+ if let Some(dir) = tok.strip_prefix("-L") {
+ if !dir.is_empty() {
+ println!("cargo:rustc-link-search=native={dir}");
+ }
+ } else if let Some(lib) = tok.strip_prefix("-l") {
+ if lib == "curl" {
+ saw_curl = true;
+ }
+ println!("cargo:rustc-link-lib={lib}");
+ }
+ }
+ saw_curl
+}
+
+fn link_curl_via_pkg_config() -> bool {
+ let Ok(out) = std::process::Command::new("pkg-config")
+ .args(["--libs", "libcurl"])
+ .output()
+ else {
+ return false;
+ };
+ if !out.status.success() {
+ return false;
+ }
+ let libs = String::from_utf8_lossy(&out.stdout);
+ apply_pkg_libs(libs.trim())
+}
+
+fn link_curl_via_env() -> bool {
+ let Ok(dir) = std::env::var("HERMIT_CURL_LIBDIR") else {
+ return false;
+ };
+ if dir.is_empty() || !std::path::Path::new(&dir).is_dir() {
+ return false;
+ }
+ println!("cargo:rustc-link-search=native={dir}");
+ println!("cargo:rustc-link-lib=curl");
+ true
+}
+
+fn link_curl_via_ldd() -> bool {
+ // Parse: libcurl.so.4 => /nix/store/.../lib/libcurl.so.4 (0x...)
+ let script = r#"
+set -e
+bin=$(command -v curl 2>/dev/null || true)
+[ -n "$bin" ] || exit 1
+ldd "$bin" 2>/dev/null | awk '/libcurl\.so/ { print $3; exit }'
+"#;
+ let Ok(out) = std::process::Command::new("sh")
+ .arg("-c")
+ .arg(script)
+ .output()
+ else {
+ return false;
+ };
+ if !out.status.success() {
+ return false;
+ }
+ let so = String::from_utf8_lossy(&out.stdout);
+ let so = so.trim();
+ if so.is_empty() || so == "not" {
+ return false;
+ }
+ let path = std::path::Path::new(so);
+ let Some(dir) = path.parent() else {
+ return false;
+ };
+ if !dir.is_dir() {
+ return false;
+ }
+ println!("cargo:rustc-link-search=native={}", dir.display());
+ println!("cargo:rustc-link-lib=curl");
+ true
+}
diff --git a/apps/desktop/src-tauri/capabilities/default.json b/archive/desktop-tauri/src-tauri/capabilities/default.json
similarity index 81%
rename from apps/desktop/src-tauri/capabilities/default.json
rename to archive/desktop-tauri/src-tauri/capabilities/default.json
index d9f6eb6..4612338 100644
--- a/apps/desktop/src-tauri/capabilities/default.json
+++ b/archive/desktop-tauri/src-tauri/capabilities/default.json
@@ -5,6 +5,7 @@
"windows": ["main"],
"permissions": [
"core:default",
- "shell:allow-open"
+ "shell:allow-open",
+ "dialog:default"
]
}
diff --git a/apps/desktop/src-tauri/gen/schemas/acl-manifests.json b/archive/desktop-tauri/src-tauri/gen/schemas/acl-manifests.json
similarity index 89%
rename from apps/desktop/src-tauri/gen/schemas/acl-manifests.json
rename to archive/desktop-tauri/src-tauri/gen/schemas/acl-manifests.json
index cb7b5b6..0864b9d 100644
--- a/apps/desktop/src-tauri/gen/schemas/acl-manifests.json
+++ b/archive/desktop-tauri/src-tauri/gen/schemas/acl-manifests.json
@@ -1 +1 @@
-{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-exit":{"identifier":"allow-exit","description":"Enables the exit command without any pre-configured scope.","commands":{"allow":["exit"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-exit":{"identifier":"deny-exit","description":"Denies the exit command without any pre-configured scope.","commands":{"allow":[],"deny":["exit"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-icon-with-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-fullscreen-on-monitor":{"identifier":"allow-set-fullscreen-on-monitor","description":"Enables the set_fullscreen_on_monitor command without any pre-configured scope.","commands":{"allow":["set_fullscreen_on_monitor"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-fullscreen-on-monitor":{"identifier":"deny-set-fullscreen-on-monitor","description":"Denies the set_fullscreen_on_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen_on_monitor"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}}}
\ No newline at end of file
+{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-exit":{"identifier":"allow-exit","description":"Enables the exit command without any pre-configured scope.","commands":{"allow":["exit"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-exit":{"identifier":"deny-exit","description":"Denies the exit command without any pre-configured scope.","commands":{"allow":[],"deny":["exit"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-icon-with-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-fullscreen-on-monitor":{"identifier":"allow-set-fullscreen-on-monitor","description":"Enables the set_fullscreen_on_monitor command without any pre-configured scope.","commands":{"allow":["set_fullscreen_on_monitor"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-fullscreen-on-monitor":{"identifier":"deny-set-fullscreen-on-monitor","description":"Denies the set_fullscreen_on_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen_on_monitor"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)","commands":{"allow":["message"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)","commands":{"allow":["message"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)","commands":{"allow":[],"deny":["message"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)","commands":{"allow":[],"deny":["message"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}}}
\ No newline at end of file
diff --git a/apps/desktop/src-tauri/gen/schemas/capabilities.json b/archive/desktop-tauri/src-tauri/gen/schemas/capabilities.json
similarity index 68%
rename from apps/desktop/src-tauri/gen/schemas/capabilities.json
rename to archive/desktop-tauri/src-tauri/gen/schemas/capabilities.json
index 2af6d48..5d41701 100644
--- a/apps/desktop/src-tauri/gen/schemas/capabilities.json
+++ b/archive/desktop-tauri/src-tauri/gen/schemas/capabilities.json
@@ -1 +1 @@
-{"default":{"identifier":"default","description":"Default Hermit desktop capabilities","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}}
\ No newline at end of file
+{"default":{"identifier":"default","description":"Default Hermit desktop capabilities","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:default"]}}
\ No newline at end of file
diff --git a/apps/desktop/src-tauri/gen/schemas/desktop-schema.json b/archive/desktop-tauri/src-tauri/gen/schemas/desktop-schema.json
similarity index 97%
rename from apps/desktop/src-tauri/gen/schemas/desktop-schema.json
rename to archive/desktop-tauri/src-tauri/gen/schemas/desktop-schema.json
index 5625606..40e4ae5 100644
--- a/apps/desktop/src-tauri/gen/schemas/desktop-schema.json
+++ b/archive/desktop-tauri/src-tauri/gen/schemas/desktop-schema.json
@@ -2426,6 +2426,72 @@
"const": "core:window:deny-unminimize",
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
},
+ {
+ "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
+ "type": "string",
+ "const": "dialog:default",
+ "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
+ },
+ {
+ "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:allow-ask",
+ "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
+ },
+ {
+ "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:allow-confirm",
+ "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
+ },
+ {
+ "description": "Enables the message command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-message",
+ "markdownDescription": "Enables the message command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the open command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-open",
+ "markdownDescription": "Enables the open command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-save",
+ "markdownDescription": "Enables the save command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:deny-ask",
+ "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
+ },
+ {
+ "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:deny-confirm",
+ "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
+ },
+ {
+ "description": "Denies the message command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-message",
+ "markdownDescription": "Denies the message command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the open command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-open",
+ "markdownDescription": "Denies the open command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-save",
+ "markdownDescription": "Denies the save command without any pre-configured scope."
+ },
{
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
"type": "string",
diff --git a/apps/desktop/src-tauri/gen/schemas/linux-schema.json b/archive/desktop-tauri/src-tauri/gen/schemas/linux-schema.json
similarity index 97%
rename from apps/desktop/src-tauri/gen/schemas/linux-schema.json
rename to archive/desktop-tauri/src-tauri/gen/schemas/linux-schema.json
index 5625606..40e4ae5 100644
--- a/apps/desktop/src-tauri/gen/schemas/linux-schema.json
+++ b/archive/desktop-tauri/src-tauri/gen/schemas/linux-schema.json
@@ -2426,6 +2426,72 @@
"const": "core:window:deny-unminimize",
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
},
+ {
+ "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
+ "type": "string",
+ "const": "dialog:default",
+ "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
+ },
+ {
+ "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:allow-ask",
+ "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
+ },
+ {
+ "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:allow-confirm",
+ "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
+ },
+ {
+ "description": "Enables the message command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-message",
+ "markdownDescription": "Enables the message command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the open command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-open",
+ "markdownDescription": "Enables the open command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:allow-save",
+ "markdownDescription": "Enables the save command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:deny-ask",
+ "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
+ },
+ {
+ "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
+ "type": "string",
+ "const": "dialog:deny-confirm",
+ "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
+ },
+ {
+ "description": "Denies the message command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-message",
+ "markdownDescription": "Denies the message command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the open command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-open",
+ "markdownDescription": "Denies the open command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "dialog:deny-save",
+ "markdownDescription": "Denies the save command without any pre-configured scope."
+ },
{
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
"type": "string",
diff --git a/apps/desktop/src-tauri/icons/128x128.png b/archive/desktop-tauri/src-tauri/icons/128x128.png
similarity index 100%
rename from apps/desktop/src-tauri/icons/128x128.png
rename to archive/desktop-tauri/src-tauri/icons/128x128.png
diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/archive/desktop-tauri/src-tauri/icons/128x128@2x.png
similarity index 100%
rename from apps/desktop/src-tauri/icons/128x128@2x.png
rename to archive/desktop-tauri/src-tauri/icons/128x128@2x.png
diff --git a/apps/desktop/src-tauri/icons/32x32.png b/archive/desktop-tauri/src-tauri/icons/32x32.png
similarity index 100%
rename from apps/desktop/src-tauri/icons/32x32.png
rename to archive/desktop-tauri/src-tauri/icons/32x32.png
diff --git a/apps/desktop/src-tauri/icons/icon.icns b/archive/desktop-tauri/src-tauri/icons/icon.icns
similarity index 100%
rename from apps/desktop/src-tauri/icons/icon.icns
rename to archive/desktop-tauri/src-tauri/icons/icon.icns
diff --git a/apps/desktop/src-tauri/icons/icon.ico b/archive/desktop-tauri/src-tauri/icons/icon.ico
similarity index 100%
rename from apps/desktop/src-tauri/icons/icon.ico
rename to archive/desktop-tauri/src-tauri/icons/icon.ico
diff --git a/apps/desktop/src-tauri/icons/icon.png b/archive/desktop-tauri/src-tauri/icons/icon.png
similarity index 100%
rename from apps/desktop/src-tauri/icons/icon.png
rename to archive/desktop-tauri/src-tauri/icons/icon.png
diff --git a/archive/desktop-tauri/src-tauri/src/engine.rs b/archive/desktop-tauri/src-tauri/src/engine.rs
new file mode 100644
index 0000000..317a2b5
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/src/engine.rs
@@ -0,0 +1,1428 @@
+//! Engine façade: host mock by default; `--features engine` + HERMIT_CORE_LIB
+//! routes turns through libcstcore (AgentCore parity).
+
+use crate::ffi::{self, ConsentGate};
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::path::PathBuf;
+use std::sync::{Arc, Mutex};
+use tauri::{AppHandle, Emitter};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SessionInfo {
+ pub id: String,
+ pub title: String,
+ pub updated_at: i64,
+ #[serde(default)]
+ pub pinned: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub folder: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "kind")]
+pub enum ChatItem {
+ #[serde(rename = "user")]
+ User {
+ id: String,
+ text: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ ts: Option,
+ },
+ #[serde(rename = "assistant")]
+ Assistant {
+ id: String,
+ text: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ ts: Option,
+ },
+ #[serde(rename = "thought")]
+ Thought {
+ id: String,
+ text: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ ts: Option,
+ },
+ #[serde(rename = "tool")]
+ Tool {
+ id: String,
+ name: String,
+ status: String,
+ detail: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ output: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ exit_code: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ ts: Option,
+ },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Settings {
+ pub provider: String,
+ pub model: String,
+ pub approval: String,
+ pub workspace: String,
+ #[serde(default)]
+ pub plan_mode: bool,
+ #[serde(default)]
+ pub api_key: String,
+ /// low | medium | high | max | "" (off)
+ #[serde(default)]
+ pub reasoning_effort: String,
+ #[serde(default = "default_font")]
+ pub font_size: String,
+ #[serde(default = "default_lang")]
+ pub language: String,
+ #[serde(default)]
+ pub native_titlebar: bool,
+}
+
+fn default_font() -> String {
+ "m".into()
+}
+fn default_lang() -> String {
+ "zh".into()
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct BashRun {
+ pub id: String,
+ pub command: String,
+ pub status: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub output: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub exit_code: Option,
+ pub started_at: i64,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub ended_at: Option,
+}
+
+fn is_shell_tool(name: &str) -> bool {
+ let n = name.to_ascii_lowercase();
+ n.contains("shell") || n == "bash" || n == "sh" || n == "run_terminal"
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct AttachmentInfo {
+ pub id: String,
+ pub name: String,
+ pub path: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ProviderInfo {
+ pub name: String,
+ pub model: String,
+ pub has_key: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub key_from: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UsageInfo {
+ pub prompt: i64,
+ pub out: i64,
+ pub cost: f64,
+ pub priced: bool,
+ pub pct: f64,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum EngineEvent {
+ #[serde(rename = "assistant_delta")]
+ AssistantDelta { text: String },
+ #[serde(rename = "thought_delta")]
+ ThoughtDelta { text: String },
+ #[serde(rename = "tool_start")]
+ ToolStart {
+ id: String,
+ name: String,
+ detail: String,
+ },
+ #[serde(rename = "tool_end")]
+ ToolEnd {
+ id: String,
+ status: String,
+ detail: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ output: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ exit_code: Option,
+ },
+ #[serde(rename = "consent")]
+ Consent { req: ConsentRequest },
+ #[serde(rename = "question")]
+ Question { req: QuestionRequest },
+ #[serde(rename = "todo")]
+ Todo { text: String },
+ #[serde(rename = "status")]
+ Status { message: String },
+ #[serde(rename = "turn_end")]
+ TurnEnd { ok: bool },
+ #[serde(rename = "error")]
+ Error { message: String },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ConsentRequest {
+ pub id: String,
+ pub tool: String,
+ pub detail: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub preview: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct QuestionRequest {
+ pub id: String,
+ pub question: String,
+ pub options: Vec,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+struct SessionMeta {
+ #[serde(default)]
+ folder: Option,
+ #[serde(default)]
+ pinned: bool,
+}
+
+pub struct EngineState {
+ pub settings: Settings,
+ pub sessions: Vec,
+ pub transcripts: HashMap>,
+ pub active_id: Option,
+ pub pending: Vec,
+ pub todo: String,
+ pub usage: UsageInfo,
+ pub bash_runs: Vec,
+ session_meta: HashMap,
+ busy: bool,
+ cancel: bool,
+ pub demo_tools: bool,
+ pub consent: Arc,
+ pub question: Arc,
+ #[cfg(feature = "engine")]
+ live: Option,
+}
+
+impl Default for EngineState {
+ fn default() -> Self {
+ let workspace = std::env::current_dir()
+ .ok()
+ .and_then(|p| p.to_str().map(|s| s.to_string()))
+ .unwrap_or_else(|| ".".into());
+ let mut settings = Settings {
+ provider: "deepseek".into(),
+ model: "deepseek-flash".into(),
+ approval: "auto".into(),
+ workspace,
+ plan_mode: false,
+ api_key: String::new(),
+ reasoning_effort: "high".into(),
+ font_size: "m".into(),
+ language: "zh".into(),
+ native_titlebar: false,
+ };
+ if let Some(saved) = load_settings_file() {
+ settings = saved;
+ if settings.workspace.is_empty() {
+ settings.workspace = std::env::current_dir()
+ .ok()
+ .and_then(|p| p.to_str().map(|s| s.to_string()))
+ .unwrap_or_else(|| ".".into());
+ }
+ if settings.font_size.is_empty() {
+ settings.font_size = "m".into();
+ }
+ if settings.language.is_empty() {
+ settings.language = "zh".into();
+ }
+ }
+ let meta = load_session_meta();
+ let welcome = SessionInfo {
+ id: "welcome".into(),
+ title: "Welcome".into(),
+ updated_at: chrono_now(),
+ pinned: meta.get("welcome").map(|m| m.pinned).unwrap_or(false),
+ folder: meta.get("welcome").and_then(|m| m.folder.clone()),
+ };
+ let mut transcripts = HashMap::new();
+ transcripts.insert(welcome.id.clone(), Vec::new());
+ Self {
+ settings,
+ sessions: vec![welcome],
+ transcripts,
+ active_id: Some("welcome".into()),
+ pending: Vec::new(),
+ todo: String::new(),
+ usage: UsageInfo {
+ prompt: 0,
+ out: 0,
+ cost: 0.0,
+ priced: false,
+ pct: 0.0,
+ },
+ bash_runs: Vec::new(),
+ session_meta: meta,
+ busy: false,
+ cancel: false,
+ demo_tools: std::env::var("HERMIT_DEMO_TOOLS")
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
+ .unwrap_or(true),
+ consent: Arc::new(ConsentGate::default()),
+ question: Arc::new(ConsentGate::default()),
+ #[cfg(feature = "engine")]
+ live: None,
+ }
+ }
+}
+
+fn chrono_now() -> i64 {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|d| d.as_millis() as i64)
+ .unwrap_or(0)
+}
+
+fn config_dir() -> PathBuf {
+ if let Ok(home) = std::env::var("HOME") {
+ PathBuf::from(home).join(".config/hermit")
+ } else {
+ PathBuf::from(".")
+ }
+}
+
+fn settings_path() -> PathBuf {
+ config_dir().join("desktop-settings.json")
+}
+
+fn session_meta_path() -> PathBuf {
+ config_dir().join("desktop-session-meta.json")
+}
+
+fn load_settings_file() -> Option {
+ let p = settings_path();
+ let raw = std::fs::read_to_string(p).ok()?;
+ serde_json::from_str(&raw).ok()
+}
+
+fn save_settings_file(s: &Settings) {
+ let p = settings_path();
+ if let Some(parent) = p.parent() {
+ let _ = std::fs::create_dir_all(parent);
+ }
+ if let Ok(raw) = serde_json::to_string_pretty(s) {
+ let _ = std::fs::write(p, raw);
+ }
+}
+
+fn load_session_meta() -> HashMap {
+ let raw = std::fs::read_to_string(session_meta_path()).unwrap_or_default();
+ serde_json::from_str(&raw).unwrap_or_default()
+}
+
+fn save_session_meta(meta: &HashMap) {
+ let p = session_meta_path();
+ if let Some(parent) = p.parent() {
+ let _ = std::fs::create_dir_all(parent);
+ }
+ if let Ok(raw) = serde_json::to_string_pretty(meta) {
+ let _ = std::fs::write(p, raw);
+ }
+}
+
+fn apply_meta(sessions: &mut [SessionInfo], meta: &HashMap) {
+ for s in sessions.iter_mut() {
+ if let Some(m) = meta.get(&s.id) {
+ s.pinned = m.pinned;
+ s.folder = m.folder.clone();
+ }
+ }
+}
+
+fn rebuild_bash_from_items(items: &[ChatItem]) -> Vec {
+ let mut out = Vec::new();
+ for it in items {
+ if let ChatItem::Tool {
+ id,
+ name,
+ status,
+ detail,
+ output,
+ exit_code,
+ ts,
+ } = it
+ {
+ if !is_shell_tool(name) {
+ continue;
+ }
+ let started = ts.unwrap_or_else(chrono_now);
+ out.push(BashRun {
+ id: id.clone(),
+ command: if detail.is_empty() {
+ name.clone()
+ } else {
+ detail.clone()
+ },
+ status: status.clone(),
+ output: output.clone(),
+ exit_code: *exit_code,
+ started_at: started,
+ ended_at: if status == "running" {
+ None
+ } else {
+ Some(started)
+ },
+ });
+ }
+ }
+ out
+}
+
+pub type SharedEngine = Mutex;
+
+#[cfg(feature = "engine")]
+fn ensure_live(g: &mut EngineState) -> Result<(), String> {
+ if g.live.is_some() {
+ return Ok(());
+ }
+ if !ffi::engine_linked() {
+ return Err("engine not linked".into());
+ }
+ let agent = ffi::open(
+ &g.settings.workspace,
+ &g.settings.provider,
+ &g.settings.model,
+ &g.settings.approval,
+ &g.settings.api_key,
+ &g.settings.reasoning_effort,
+ )?;
+ let _ = ffi::set_plan_mode(&agent, g.settings.plan_mode);
+ let _ = ffi::set_approval(&agent, &g.settings.approval);
+ let _ = ffi::set_effort(&agent, &g.settings.reasoning_effort);
+ g.live = Some(agent);
+ Ok(())
+}
+
+#[cfg(feature = "engine")]
+fn reload_history_into(g: &mut EngineState) {
+ let Some(ref live) = g.live else {
+ return;
+ };
+ let Ok(json) = ffi::session_history_json(live) else {
+ return;
+ };
+ let Ok(hist) = serde_json::from_str::>(&json) else {
+ return;
+ };
+ if let Some(id) = g.active_id.clone() {
+ g.bash_runs = rebuild_bash_from_items(&hist);
+ g.transcripts.insert(id, hist);
+ }
+ let todo = ffi::todo_text(live);
+ g.todo = todo;
+}
+
+fn emit(app: &AppHandle, ev: EngineEvent) {
+ let _ = app.emit("hermit://event", ev);
+}
+
+pub fn list_sessions(state: &SharedEngine) -> Vec {
+ let g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ if ffi::engine_linked() {
+ let listed = ffi::store_list(&g.settings.workspace);
+ if !listed.is_empty() {
+ let pinned: HashMap<_, _> = g
+ .sessions
+ .iter()
+ .map(|s| (s.id.clone(), s.pinned))
+ .collect();
+ let mut out: Vec<_> = listed
+ .into_iter()
+ .map(|(id, title, mtime)| {
+ let m = g.session_meta.get(&id);
+ SessionInfo {
+ pinned: m.map(|x| x.pinned).or_else(|| pinned.get(&id).copied()).unwrap_or(false),
+ folder: m.and_then(|x| x.folder.clone()),
+ id,
+ title,
+ updated_at: mtime,
+ }
+ })
+ .collect();
+ apply_meta(&mut out, &g.session_meta);
+ return out;
+ }
+ }
+ let mut out = g.sessions.clone();
+ apply_meta(&mut out, &g.session_meta);
+ out.sort_by(|a, b| {
+ b.pinned
+ .cmp(&a.pinned)
+ .then(b.updated_at.cmp(&a.updated_at))
+ });
+ out
+}
+
+pub fn new_session(state: &SharedEngine) -> SessionInfo {
+ let mut g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ {
+ let _ = ensure_live(&mut g);
+ if let Some(ref live) = g.live {
+ if let Ok(id) = ffi::new_session(live) {
+ let s = SessionInfo {
+ id: id.clone(),
+ title: "New session".into(),
+ updated_at: chrono_now(),
+ pinned: false,
+ folder: None,
+ };
+ g.sessions.insert(0, s.clone());
+ g.transcripts.insert(id.clone(), Vec::new());
+ g.active_id = Some(id);
+ g.todo.clear();
+ g.bash_runs.clear();
+ return s;
+ }
+ }
+ }
+ let s = SessionInfo {
+ id: format!("s{}", chrono_now()),
+ title: "New session".into(),
+ updated_at: chrono_now(),
+ pinned: false,
+ folder: None,
+ };
+ g.transcripts.insert(s.id.clone(), Vec::new());
+ g.active_id = Some(s.id.clone());
+ g.todo.clear();
+ g.bash_runs.clear();
+ g.sessions.insert(0, s.clone());
+ s
+}
+
+pub fn open_session(state: &SharedEngine, id: &str) -> Vec {
+ let mut g = state.lock().unwrap();
+ g.active_id = Some(id.to_string());
+ // Fast path: cached transcript — do not open libcstcore yet (startup freeze).
+ if let Some(hist) = g.transcripts.get(id).cloned() {
+ g.bash_runs = rebuild_bash_from_items(&hist);
+ return hist;
+ }
+ #[cfg(feature = "engine")]
+ {
+ match ensure_live(&mut g) {
+ Ok(()) => {
+ if let Some(ref live) = g.live {
+ let _ = ffi::switch_session(live, id);
+ }
+ reload_history_into(&mut g);
+ if let Some(hist) = g.transcripts.get(id).cloned() {
+ g.bash_runs = rebuild_bash_from_items(&hist);
+ return hist;
+ }
+ }
+ Err(e) => {
+ eprintln!("hermit: open_session ensure_live: {e}");
+ }
+ }
+ }
+ let hist = g.transcripts.get(id).cloned().unwrap_or_default();
+ g.bash_runs = rebuild_bash_from_items(&hist);
+ hist
+}
+
+pub fn rename_session(state: &SharedEngine, id: &str, title: &str) -> bool {
+ let mut g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ {
+ let ws = g.settings.workspace.clone();
+ if ffi::engine_linked() {
+ let _ = ffi::store_title_set(&ws, id, title);
+ }
+ }
+ if let Some(s) = g.sessions.iter_mut().find(|s| s.id == id) {
+ s.title = title.to_string();
+ s.updated_at = chrono_now();
+ return true;
+ }
+ false
+}
+
+pub fn delete_session(state: &SharedEngine, id: &str) -> bool {
+ let mut g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ {
+ let ws = g.settings.workspace.clone();
+ if ffi::engine_linked() {
+ let _ = ffi::store_delete(&ws, id);
+ }
+ }
+ g.transcripts.remove(id);
+ let before = g.sessions.len();
+ g.sessions.retain(|s| s.id != id);
+ if g.active_id.as_deref() == Some(id) {
+ g.active_id = g.sessions.first().map(|s| s.id.clone());
+ }
+ before != g.sessions.len()
+}
+
+pub fn pin_session(state: &SharedEngine, id: &str) -> bool {
+ let mut g = state.lock().unwrap();
+ let pinned = {
+ let entry = g.session_meta.entry(id.to_string()).or_default();
+ entry.pinned = !entry.pinned;
+ entry.pinned
+ };
+ if let Some(s) = g.sessions.iter_mut().find(|s| s.id == id) {
+ s.pinned = pinned;
+ }
+ save_session_meta(&g.session_meta);
+ true
+}
+
+pub fn session_set_folder(state: &SharedEngine, id: &str, folder: Option) -> bool {
+ let mut g = state.lock().unwrap();
+ if let Some(s) = g.sessions.iter_mut().find(|s| s.id == id) {
+ s.folder = folder.clone();
+ }
+ let entry = g.session_meta.entry(id.to_string()).or_default();
+ entry.folder = folder.filter(|f| !f.trim().is_empty());
+ save_session_meta(&g.session_meta);
+ true
+}
+
+pub fn bash_list(state: &SharedEngine) -> Vec {
+ state.lock().unwrap().bash_runs.clone()
+}
+
+pub fn settings_get(state: &SharedEngine) -> Settings {
+ state.lock().unwrap().settings.clone()
+}
+
+pub fn settings_set(state: &SharedEngine, next: Settings) -> Settings {
+ let mut g = state.lock().unwrap();
+ let workspace_changed = g.settings.workspace != next.workspace;
+ let model_changed =
+ g.settings.model != next.model || g.settings.provider != next.provider;
+ let key = next.api_key.clone();
+ let provider = next.provider.clone();
+ g.settings = next.clone();
+ let mut disk = next.clone();
+ disk.api_key.clear();
+ save_settings_file(&disk);
+ #[cfg(feature = "engine")]
+ {
+ if !key.is_empty() {
+ let _ = ffi::creds_save(&provider, &key);
+ }
+ if workspace_changed || model_changed {
+ g.live = None;
+ }
+ let _ = ensure_live(&mut g);
+ if let Some(ref live) = g.live {
+ if workspace_changed {
+ let _ = ffi::set_workspace(live, &next.workspace);
+ }
+ let _ = ffi::set_plan_mode(live, next.plan_mode);
+ let _ = ffi::set_approval(live, &next.approval);
+ let _ = ffi::set_effort(live, &next.reasoning_effort);
+ }
+ }
+ #[cfg(not(feature = "engine"))]
+ {
+ let _ = (workspace_changed, model_changed, key, provider);
+ }
+ next
+}
+
+pub fn attach_list(state: &SharedEngine) -> Vec {
+ state.lock().unwrap().pending.clone()
+}
+
+pub fn attach_file(state: &SharedEngine, path: String) -> Result {
+ let p = PathBuf::from(&path);
+ if !p.exists() {
+ return Err("file not found".into());
+ }
+ let name = p
+ .file_name()
+ .and_then(|s| s.to_str())
+ .unwrap_or("file")
+ .to_string();
+ let info = AttachmentInfo {
+ id: format!("a{}", chrono_now()),
+ name,
+ path: path.clone(),
+ };
+ let mut g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ {
+ let _ = ensure_live(&mut g);
+ if let Some(ref live) = g.live {
+ if let Err(e) = ffi::attach_file(live, &path) {
+ return Err(e);
+ }
+ }
+ }
+ g.pending.push(info.clone());
+ Ok(info)
+}
+
+pub fn attach_clear(state: &SharedEngine) {
+ let mut g = state.lock().unwrap();
+ g.pending.clear();
+ #[cfg(feature = "engine")]
+ if let Some(ref live) = g.live {
+ ffi::pending_clear(live);
+ }
+}
+
+pub fn attach_remove(state: &SharedEngine, id: &str) {
+ let mut g = state.lock().unwrap();
+ // Engine pending is ordered; rebuild by clear+re-attach remaining.
+ g.pending.retain(|a| a.id != id);
+ #[cfg(feature = "engine")]
+ if let Some(ref live) = g.live {
+ ffi::pending_clear(live);
+ for a in g.pending.clone() {
+ let _ = ffi::attach_file(live, &a.path);
+ }
+ }
+}
+
+pub fn providers_list(state: &SharedEngine) -> Vec {
+ let g = state.lock().unwrap();
+ #[cfg(feature = "engine")]
+ if ffi::engine_linked() {
+ if let Ok(list) = ffi::providers_list() {
+ if !list.is_empty() {
+ return list
+ .into_iter()
+ .map(|p| ProviderInfo {
+ name: p.name,
+ model: p.model,
+ has_key: p.has_key,
+ key_from: p.key_from,
+ })
+ .collect();
+ }
+ }
+ }
+ vec![
+ ProviderInfo {
+ name: "deepseek".into(),
+ model: g.settings.model.clone(),
+ has_key: !g.settings.api_key.is_empty()
+ || std::env::var("DEEPSEEK_API_KEY").is_ok()
+ || std::env::var("OPENAI_API_KEY").is_ok(),
+ key_from: Some("env/settings".into()),
+ },
+ ProviderInfo {
+ name: "openai".into(),
+ model: "gpt-4.1".into(),
+ has_key: std::env::var("OPENAI_API_KEY").is_ok(),
+ key_from: Some("env".into()),
+ },
+ ProviderInfo {
+ name: "anthropic".into(),
+ model: "claude-sonnet-4".into(),
+ has_key: std::env::var("ANTHROPIC_API_KEY").is_ok(),
+ key_from: Some("env".into()),
+ },
+ ]
+}
+
+pub fn providers_use(
+ state: &SharedEngine,
+ provider: String,
+ model: String,
+ key: String,
+) -> Result {
+ let mut g = state.lock().unwrap();
+ g.settings.provider = provider.clone();
+ g.settings.model = model.clone();
+ if !key.is_empty() {
+ g.settings.api_key = key.clone();
+ }
+ #[cfg(feature = "engine")]
+ {
+ if !key.is_empty() {
+ let _ = ffi::creds_save(&provider, &key);
+ }
+ g.live = None;
+ }
+ let mut disk = g.settings.clone();
+ disk.api_key.clear();
+ save_settings_file(&disk);
+ Ok(String::new())
+}
+
+pub fn todo_get(state: &SharedEngine) -> String {
+ #[cfg(feature = "engine")]
+ {
+ let mut g = state.lock().unwrap();
+ if ensure_live(&mut g).is_ok() {
+ if let Some(ref live) = g.live {
+ g.todo = ffi::todo_text(live);
+ }
+ }
+ return g.todo.clone();
+ }
+ #[cfg(not(feature = "engine"))]
+ {
+ state.lock().unwrap().todo.clone()
+ }
+}
+
+pub fn usage_get(state: &SharedEngine) -> UsageInfo {
+ state.lock().unwrap().usage.clone()
+}
+
+pub fn workspace_files(state: &SharedEngine, query: String) -> Vec {
+ let ws = state.lock().unwrap().settings.workspace.clone();
+ let root = PathBuf::from(&ws);
+ let q = query.to_lowercase();
+ let mut out = Vec::new();
+ fn walk(dir: &PathBuf, root: &PathBuf, q: &str, out: &mut Vec, depth: usize) {
+ if depth > 4 || out.len() >= 40 {
+ return;
+ }
+ let Ok(rd) = std::fs::read_dir(dir) else {
+ return;
+ };
+ for ent in rd.flatten() {
+ let path = ent.path();
+ let name = ent.file_name().to_string_lossy().to_string();
+ if name.starts_with('.')
+ || name == "node_modules"
+ || name == "target"
+ || name == ".git"
+ {
+ continue;
+ }
+ if path.is_dir() {
+ walk(&path, root, q, out, depth + 1);
+ } else if let Ok(rel) = path.strip_prefix(root) {
+ let s = rel.to_string_lossy().replace('\\', "/");
+ if q.is_empty() || s.to_lowercase().contains(q) {
+ out.push(s);
+ }
+ }
+ if out.len() >= 40 {
+ return;
+ }
+ }
+ }
+ walk(&root, &root, &q, &mut out, 0);
+ out.sort();
+ out.truncate(20);
+ out
+}
+
+pub fn session_compact(state: &SharedEngine) -> String {
+ #[cfg(feature = "engine")]
+ {
+ let mut g = state.lock().unwrap();
+ if ensure_live(&mut g).is_ok() {
+ if let Some(ref live) = g.live {
+ return match ffi::compact(live) {
+ Ok(()) => {
+ reload_history_into(&mut g);
+ "Session compacted.".into()
+ }
+ Err(e) => format!("Compact failed: {e}"),
+ };
+ }
+ }
+ }
+ let _ = state;
+ "Compacted host transcript markers (mock).".into()
+}
+
+pub fn session_export(state: &SharedEngine) -> String {
+ #[cfg(feature = "engine")]
+ {
+ let mut g = state.lock().unwrap();
+ let id = g.active_id.clone().unwrap_or_default();
+ let title = g
+ .sessions
+ .iter()
+ .find(|s| s.id == id)
+ .map(|s| s.title.clone())
+ .unwrap_or_else(|| "Session".into());
+ if ensure_live(&mut g).is_ok() {
+ if let Some(ref live) = g.live {
+ if let Ok(md) = ffi::export_md(live, &title) {
+ return md;
+ }
+ }
+ }
+ return export_items_md(&title, g.transcripts.get(&id).cloned().unwrap_or_default());
+ }
+ #[cfg(not(feature = "engine"))]
+ {
+ let g = state.lock().unwrap();
+ let id = g.active_id.clone().unwrap_or_default();
+ let title = g
+ .sessions
+ .iter()
+ .find(|s| s.id == id)
+ .map(|s| s.title.clone())
+ .unwrap_or_else(|| "Session".into());
+ export_items_md(&title, g.transcripts.get(&id).cloned().unwrap_or_default())
+ }
+}
+
+fn export_items_md(title: &str, items: Vec) -> String {
+ let mut md = format!("# {}\n\n", title);
+ for it in items {
+ match it {
+ ChatItem::User { text, .. } => md.push_str(&format!("## User\n\n{text}\n\n")),
+ ChatItem::Assistant { text, .. } => {
+ md.push_str(&format!("## Hermit\n\n{text}\n\n"))
+ }
+ ChatItem::Thought { text, .. } => {
+ md.push_str(&format!("### Thought\n\n{text}\n\n"))
+ }
+ ChatItem::Tool {
+ name, detail, ..
+ } => md.push_str(&format!("### Tool `{name}`\n\n{detail}\n\n")),
+ }
+ }
+ md
+}
+
+fn append_user(g: &mut EngineState, text: &str) {
+ let id = g.active_id.clone().unwrap_or_else(|| {
+ let s = SessionInfo {
+ id: format!("s{}", chrono_now()),
+ title: "New session".into(),
+ updated_at: chrono_now(),
+ pinned: false,
+ folder: None,
+ };
+ g.transcripts.insert(s.id.clone(), Vec::new());
+ g.active_id = Some(s.id.clone());
+ g.sessions.insert(0, s.clone());
+ s.id
+ });
+ let items = g.transcripts.entry(id.clone()).or_default();
+ items.push(ChatItem::User {
+ id: format!("u{}", chrono_now()),
+ text: text.to_string(),
+ ts: Some(chrono_now()),
+ });
+ if let Some(s) = g.sessions.iter_mut().find(|s| s.id == id) {
+ if s.title == "New session" || s.title == "Welcome" {
+ let t = text.chars().take(48).collect::();
+ s.title = if t.is_empty() {
+ "New session".into()
+ } else {
+ t
+ };
+ }
+ s.updated_at = chrono_now();
+ }
+}
+
+fn append_event_item(g: &mut EngineState, item: ChatItem) {
+ if let Some(id) = g.active_id.clone() {
+ g.transcripts.entry(id).or_default().push(item);
+ }
+}
+
+pub async fn send_turn(app: AppHandle, state: &SharedEngine, text: String) {
+ {
+ let mut g = state.lock().unwrap();
+ if g.busy {
+ return;
+ }
+ g.busy = true;
+ g.cancel = false;
+ append_user(&mut g, &text);
+ }
+
+ #[cfg(feature = "engine")]
+ {
+ if try_live_turn(&app, state, &text).await {
+ {
+ let mut g = state.lock().unwrap();
+ g.busy = false;
+ }
+ attach_clear(state);
+ return;
+ }
+ }
+
+ host_mock_turn(&app, state, &text).await;
+ {
+ let mut g = state.lock().unwrap();
+ g.busy = false;
+ }
+ attach_clear(state);
+}
+
+#[cfg(feature = "engine")]
+async fn try_live_turn(app: &AppHandle, state: &SharedEngine, text: &str) -> bool {
+ if !ffi::engine_linked() {
+ return false;
+ }
+
+ {
+ let mut g = state.lock().unwrap();
+ if g.live.is_none() {
+ match ffi::open(
+ &g.settings.workspace,
+ &g.settings.provider,
+ &g.settings.model,
+ &g.settings.approval,
+ &g.settings.api_key,
+ &g.settings.reasoning_effort,
+ ) {
+ Ok(agent) => {
+ let _ = ffi::set_plan_mode(&agent, g.settings.plan_mode);
+ let _ = ffi::set_effort(&agent, &g.settings.reasoning_effort);
+ g.live = Some(agent);
+ }
+ Err(e) => {
+ emit(
+ app,
+ EngineEvent::Error {
+ message: format!("engine open: {e}"),
+ },
+ );
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return true;
+ }
+ }
+ }
+ // Attach pending files before turn.
+ if let Some(ref live) = g.live {
+ for a in g.pending.clone() {
+ let _ = ffi::attach_file(live, &a.path);
+ }
+ }
+ }
+
+ let gate = state.lock().unwrap().consent.clone();
+ let qgate = state.lock().unwrap().question.clone();
+ let app2 = app.clone();
+ let app3 = app.clone();
+ let app4 = app.clone();
+ let app5 = app.clone();
+ let app6 = app.clone();
+ let app7 = app.clone();
+ let app8 = app.clone();
+
+ let agent = {
+ let mut g = state.lock().unwrap();
+ g.live.take()
+ };
+ let Some(agent) = agent else {
+ emit(
+ app,
+ EngineEvent::Error {
+ message: "engine agent missing".into(),
+ },
+ );
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return true;
+ };
+
+ let text_owned = text.to_string();
+ let turn_result = tokio::task::spawn_blocking(move || {
+ let cb = ffi::TurnCallbacks {
+ on_assistant: Box::new({
+ let app = app2;
+ move |t: &str| {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::AssistantDelta {
+ text: t.to_string(),
+ },
+ );
+ }
+ }),
+ on_thought: Box::new({
+ let app = app3;
+ move |t: &str| {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::ThoughtDelta {
+ text: t.to_string(),
+ },
+ );
+ }
+ }),
+ on_tool: Box::new({
+ let app = app4;
+ move |phase: &str, id: &str, name: &str, detail: &str| {
+ let phase_l = phase.to_ascii_lowercase();
+ if phase_l.contains("start")
+ || phase_l == "begin"
+ || phase_l == "call"
+ {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::ToolStart {
+ id: id.to_string(),
+ name: name.to_string(),
+ detail: detail.to_string(),
+ },
+ );
+ } else {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::ToolEnd {
+ id: id.to_string(),
+ status: "ok".into(),
+ detail: detail.to_string(),
+ output: Some(detail.to_string()),
+ exit_code: Some(0),
+ },
+ );
+ }
+ }
+ }),
+ on_tool_result: Box::new({
+ let app = app8;
+ move |id: &str, name: &str, out: &str, exit: i32| {
+ let status = if exit == 0 { "ok" } else { "error" };
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::ToolEnd {
+ id: id.to_string(),
+ status: status.into(),
+ detail: name.to_string(),
+ output: Some(out.to_string()),
+ exit_code: Some(exit),
+ },
+ );
+ }
+ }),
+ on_todo: Box::new({
+ let app = app6;
+ move |t: &str| {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::Todo {
+ text: t.to_string(),
+ },
+ );
+ }
+ }),
+ on_status: Box::new({
+ let app = app7;
+ move |t: &str| {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::Status {
+ message: t.to_string(),
+ },
+ );
+ }
+ }),
+ gate,
+ question_gate: qgate,
+ emit_consent: Box::new({
+ let app = app5.clone();
+ move |id, tool, detail, preview| {
+ let _ = app.emit(
+ "hermit://event",
+ EngineEvent::Consent {
+ req: ConsentRequest {
+ id,
+ tool,
+ detail,
+ preview,
+ },
+ },
+ );
+ }
+ }),
+ emit_question: Box::new(move |id, question, options| {
+ let _ = app5.emit(
+ "hermit://event",
+ EngineEvent::Question {
+ req: QuestionRequest {
+ id,
+ question,
+ options,
+ },
+ },
+ );
+ }),
+ };
+ let r = ffi::run_turn(&agent, &text_owned, cb);
+ let usage = ffi::usage(&agent).ok();
+ (agent, r, usage)
+ })
+ .await;
+
+ match turn_result {
+ Ok((agent, Ok(()), usage)) => {
+ let mut g = state.lock().unwrap();
+ g.live = Some(agent);
+ if let Some(u) = usage {
+ g.usage = UsageInfo {
+ prompt: u.prompt,
+ out: u.out,
+ cost: u.cost,
+ priced: u.priced,
+ pct: u.pct,
+ };
+ }
+ reload_history_into(&mut g);
+ if !g.todo.is_empty() {
+ emit(
+ app,
+ EngineEvent::Todo {
+ text: g.todo.clone(),
+ },
+ );
+ }
+ emit(app, EngineEvent::TurnEnd { ok: true });
+ }
+ Ok((agent, Err(e), usage)) => {
+ let mut g = state.lock().unwrap();
+ g.live = Some(agent);
+ if let Some(u) = usage {
+ g.usage = UsageInfo {
+ prompt: u.prompt,
+ out: u.out,
+ cost: u.cost,
+ priced: u.priced,
+ pct: u.pct,
+ };
+ }
+ reload_history_into(&mut g);
+ emit(app, EngineEvent::Error { message: e });
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ }
+ Err(e) => {
+ emit(
+ app,
+ EngineEvent::Error {
+ message: format!("join: {e}"),
+ },
+ );
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ }
+ }
+ true
+}
+
+async fn host_mock_turn(app: &AppHandle, state: &SharedEngine, text: &str) {
+ emit(
+ app,
+ EngineEvent::ThoughtDelta {
+ text: "Planning a short reply…".into(),
+ },
+ );
+ tokio::time::sleep(std::time::Duration::from_millis(220)).await;
+ if state.lock().unwrap().cancel {
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return;
+ }
+
+ let (approval, demo) = {
+ let g = state.lock().unwrap();
+ (g.settings.approval.clone(), g.demo_tools)
+ };
+
+ if demo {
+ let tool_id = format!("t{}", chrono_now());
+ let started = chrono_now();
+ {
+ let mut g = state.lock().unwrap();
+ g.bash_runs.push(BashRun {
+ id: tool_id.clone(),
+ command: "hermit --version".into(),
+ status: "running".into(),
+ output: None,
+ exit_code: None,
+ started_at: started,
+ ended_at: None,
+ });
+ }
+ emit(
+ app,
+ EngineEvent::ToolStart {
+ id: tool_id.clone(),
+ name: "shell".into(),
+ detail: "hermit --version".into(),
+ },
+ );
+
+ if approval == "ask" {
+ let consent_id = format!("c{}", chrono_now());
+ emit(
+ app,
+ EngineEvent::Consent {
+ req: ConsentRequest {
+ id: consent_id.clone(),
+ tool: "shell".into(),
+ detail: "hermit --version".into(),
+ preview: Some("$ hermit --version\n".into()),
+ },
+ },
+ );
+ let gate = state.lock().unwrap().consent.clone();
+ let ans = tokio::task::spawn_blocking(move || {
+ gate.request(&consent_id, "shell", "hermit --version")
+ })
+ .await
+ .unwrap_or(0);
+ if ans == 0 || state.lock().unwrap().cancel {
+ emit(
+ app,
+ EngineEvent::ToolEnd {
+ id: tool_id,
+ status: "denied".into(),
+ detail: "rejected".into(),
+ output: None,
+ exit_code: None,
+ },
+ );
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return;
+ }
+ }
+
+ tokio::time::sleep(std::time::Duration::from_millis(350)).await;
+ {
+ let mut g = state.lock().unwrap();
+ if let Some(b) = g.bash_runs.iter_mut().find(|b| b.id == tool_id) {
+ b.status = "ok".into();
+ b.output = Some("hermit 1.1.0\n".into());
+ b.exit_code = Some(0);
+ b.ended_at = Some(chrono_now());
+ }
+ }
+ emit(
+ app,
+ EngineEvent::ToolEnd {
+ id: tool_id,
+ status: "ok".into(),
+ detail: "hermit 1.1.0".into(),
+ output: Some("hermit 1.1.0\n".into()),
+ exit_code: Some(0),
+ },
+ );
+ emit(
+ app,
+ EngineEvent::Todo {
+ text: "- [x] Inspect request\n- [ ] Apply change\n- [ ] Verify".into(),
+ },
+ );
+ state.lock().unwrap().todo =
+ "- [x] Inspect request\n- [ ] Apply change\n- [ ] Verify".into();
+ }
+
+ if text.to_ascii_lowercase().contains("ask") {
+ let qid = format!("q{}", chrono_now());
+ emit(
+ app,
+ EngineEvent::Question {
+ req: QuestionRequest {
+ id: qid.clone(),
+ question: "Which approach should we take?".into(),
+ options: vec![
+ "Polish first".into(),
+ "Features first".into(),
+ "Skip".into(),
+ ],
+ },
+ },
+ );
+ let gate = state.lock().unwrap().question.clone();
+ let _ = tokio::task::spawn_blocking(move || gate.request(&qid, "ask_user", ""))
+ .await;
+ }
+
+ if state.lock().unwrap().cancel {
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return;
+ }
+
+ let reply = if ffi::engine_linked() {
+ format!("Engine linked but open failed earlier. Echo: {text}")
+ } else {
+ format!(
+ "Host demo reply for: **{}**\n\nWire `--features engine` + `HERMIT_CORE_LIB` for a real turn.",
+ text.chars().take(120).collect::()
+ )
+ };
+
+ for ch in reply.chars() {
+ if state.lock().unwrap().cancel {
+ emit(app, EngineEvent::TurnEnd { ok: false });
+ return;
+ }
+ emit(
+ app,
+ EngineEvent::AssistantDelta {
+ text: ch.to_string(),
+ },
+ );
+ tokio::time::sleep(std::time::Duration::from_millis(8)).await;
+ }
+
+ {
+ let mut g = state.lock().unwrap();
+ g.usage.prompt += 120;
+ g.usage.out += reply.len() as i64;
+ g.usage.pct = ((g.usage.prompt + g.usage.out) as f64 / 128_000.0 * 100.0).min(99.0);
+ append_event_item(
+ &mut g,
+ ChatItem::Assistant {
+ id: format!("a{}", chrono_now()),
+ text: reply,
+ ts: Some(chrono_now()),
+ },
+ );
+ }
+ emit(app, EngineEvent::TurnEnd { ok: true });
+}
+
+pub fn cancel_turn(state: &SharedEngine) {
+ let mut g = state.lock().unwrap();
+ g.cancel = true;
+ g.busy = false;
+ g.consent.cancel();
+ g.question.cancel();
+ #[cfg(feature = "engine")]
+ if let Some(ref live) = g.live {
+ ffi::cancel(live);
+ }
+}
+
+pub fn consent_answer(state: &SharedEngine, id: &str, answer: &str) {
+ let g = state.lock().unwrap();
+ let _ = g.consent.answer(id, answer);
+}
+
+pub fn question_answer(state: &SharedEngine, id: &str, answer: &str) {
+ let g = state.lock().unwrap();
+ if answer.is_empty() {
+ let _ = g.question.answer(id, "deny");
+ } else {
+ let _ = g.question.answer(id, answer);
+ }
+}
+
+pub fn engine_status() -> String {
+ if ffi::engine_linked() {
+ "engine".into()
+ } else {
+ "host".into()
+ }
+}
diff --git a/archive/desktop-tauri/src-tauri/src/ffi.rs b/archive/desktop-tauri/src-tauri/src/ffi.rs
new file mode 100644
index 0000000..ba36357
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/src/ffi.rs
@@ -0,0 +1,780 @@
+//! FFI surface against `include/cst_agent.h` via `ffi_bridge.c`.
+//!
+//! Enable with:
+//! HERMIT_CORE_LIB=$PWD/../../libcstcore.a \
+//! HERMIT_CORE_INCLUDE=$PWD/../../include \
+//! cargo build --features engine
+
+#![allow(dead_code)]
+
+use std::sync::{Condvar, Mutex};
+
+#[cfg(not(feature = "engine"))]
+pub fn engine_linked() -> bool {
+ false
+}
+
+#[cfg(feature = "engine")]
+pub fn engine_linked() -> bool {
+ true
+}
+
+/// Shared waiter: UI answers via `consent_answer` / `question_answer`.
+#[derive(Default)]
+pub struct ConsentGate {
+ inner: Mutex,
+ cv: Condvar,
+}
+
+#[derive(Default)]
+struct ConsentGateInner {
+ pending_id: Option,
+ pending_tool: String,
+ pending_detail: String,
+ answer: Option,
+ answer_text: String,
+ cancel: bool,
+}
+
+impl ConsentGate {
+ pub fn request(&self, id: &str, tool: &str, detail: &str) -> i32 {
+ let mut g = self.inner.lock().unwrap();
+ g.pending_id = Some(id.to_string());
+ g.pending_tool = tool.to_string();
+ g.pending_detail = detail.to_string();
+ g.answer = None;
+ g.answer_text.clear();
+ g.cancel = false;
+ loop {
+ if g.cancel {
+ g.pending_id = None;
+ return 0;
+ }
+ if let Some(ans) = g.answer.take() {
+ g.pending_id = None;
+ g.answer_text = ans.clone();
+ return match ans.as_str() {
+ "once" | "always_tool" | "always_prefix" => {
+ if ans == "once" {
+ 1
+ } else if ans == "always_tool" {
+ 2
+ } else {
+ 3
+ }
+ }
+ _ if !ans.is_empty() && ans != "deny" => 1,
+ _ => 0,
+ };
+ }
+ g = self.cv.wait(g).unwrap();
+ }
+ }
+
+ pub fn request_text(&self, id: &str, tool: &str, detail: &str) -> Option {
+ let code = self.request(id, tool, detail);
+ if code == 0 {
+ return None;
+ }
+ let g = self.inner.lock().unwrap();
+ if g.answer_text.is_empty() || g.answer_text == "once" {
+ Some(String::new())
+ } else {
+ Some(g.answer_text.clone())
+ }
+ }
+
+ pub fn answer(&self, id: &str, answer: &str) -> bool {
+ let mut g = self.inner.lock().unwrap();
+ if g.pending_id.as_deref() != Some(id) {
+ return false;
+ }
+ g.answer = Some(answer.to_string());
+ g.answer_text = answer.to_string();
+ self.cv.notify_all();
+ true
+ }
+
+ pub fn cancel(&self) {
+ let mut g = self.inner.lock().unwrap();
+ g.cancel = true;
+ self.cv.notify_all();
+ }
+}
+
+#[cfg(feature = "engine")]
+mod live {
+ use super::*;
+ use std::ffi::{CStr, CString};
+ use std::os::raw::{c_char, c_int, c_void};
+ use std::ptr;
+ use std::sync::Arc;
+
+ #[repr(C)]
+ struct HermitHooks {
+ on_assistant: Option,
+ on_thought: Option,
+ on_tool: Option<
+ extern "C" fn(
+ *const c_char,
+ *const c_char,
+ *const c_char,
+ *const c_char,
+ *mut c_void,
+ ),
+ >,
+ on_tool_result: Option<
+ extern "C" fn(*const c_char, *const c_char, *const c_char, usize, c_int, *mut c_void),
+ >,
+ on_todo: Option,
+ on_status: Option,
+ on_perm: Option<
+ extern "C" fn(*const c_char, *const c_char, *const c_char, *mut c_void) -> c_int,
+ >,
+ on_ask: Option<
+ extern "C" fn(
+ *const c_char,
+ *const c_char,
+ *mut c_char,
+ usize,
+ *mut c_void,
+ ) -> c_int,
+ >,
+ ud: *mut c_void,
+ }
+
+ extern "C" {
+ fn hermit_agent_open(
+ workspace: *const c_char,
+ provider: *const c_char,
+ model: *const c_char,
+ approval: *const c_char,
+ api_key: *const c_char,
+ effort: *const c_char,
+ err: *mut c_char,
+ err_sz: usize,
+ ) -> *mut c_void;
+ fn hermit_agent_close(agent: *mut c_void);
+ fn hermit_agent_turn(
+ agent: *mut c_void,
+ user_text: *const c_char,
+ hooks: *const HermitHooks,
+ err: *mut c_char,
+ err_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_cancel(agent: *mut c_void);
+ fn hermit_agent_set_workspace(
+ agent: *mut c_void,
+ path: *const c_char,
+ err: *mut c_char,
+ err_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_new_session(
+ agent: *mut c_void,
+ id_out: *mut c_char,
+ id_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_switch_session(agent: *mut c_void, id: *const c_char) -> c_int;
+ fn hermit_agent_session_id(
+ agent: *mut c_void,
+ out: *mut c_char,
+ out_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_set_plan_mode(agent: *mut c_void, on: c_int) -> c_int;
+ fn hermit_agent_set_approval(agent: *mut c_void, approval: *const c_char) -> c_int;
+ fn hermit_agent_set_effort(agent: *mut c_void, effort: *const c_char) -> c_int;
+ fn hermit_agent_attach_file(
+ agent: *mut c_void,
+ path: *const c_char,
+ err: *mut c_char,
+ err_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_pending_clear(agent: *mut c_void);
+ fn hermit_agent_compact(
+ agent: *mut c_void,
+ err: *mut c_char,
+ err_sz: usize,
+ ) -> c_int;
+ fn hermit_agent_usage(
+ agent: *mut c_void,
+ prompt: *mut i64,
+ out_tokens: *mut i64,
+ cost: *mut f64,
+ priced: *mut c_int,
+ );
+ fn hermit_agent_history_json(agent: *mut c_void) -> *mut c_char;
+ fn hermit_agent_todo(agent: *mut c_void) -> *mut c_char;
+ fn hermit_agent_export_md(agent: *mut c_void, title: *const c_char) -> *mut c_char;
+ fn hermit_creds_has(provider: *const c_char) -> c_int;
+ fn free(p: *mut c_void);
+ fn hermit_store_list(
+ workspace: *const c_char,
+ ids: *mut [c_char; 128],
+ max_ids: c_int,
+ ) -> c_int;
+ fn hermit_store_info(
+ workspace: *const c_char,
+ id: *const c_char,
+ title: *mut c_char,
+ title_sz: usize,
+ mtime_ms: *mut i64,
+ ) -> c_int;
+ fn hermit_store_title_set(
+ workspace: *const c_char,
+ id: *const c_char,
+ title: *const c_char,
+ ) -> c_int;
+ fn hermit_store_delete(workspace: *const c_char, id: *const c_char) -> c_int;
+ fn hermit_creds_save(provider: *const c_char, key: *const c_char) -> c_int;
+ }
+
+ pub struct LiveAgent {
+ ptr: *mut c_void,
+ }
+
+ unsafe impl Send for LiveAgent {}
+
+ impl Drop for LiveAgent {
+ fn drop(&mut self) {
+ if !self.ptr.is_null() {
+ unsafe { hermit_agent_close(self.ptr) };
+ self.ptr = ptr::null_mut();
+ }
+ }
+ }
+
+ fn cstr_lossy(buf: &[u8]) -> String {
+ CStr::from_bytes_until_nul(buf)
+ .ok()
+ .map(|c| c.to_string_lossy().into_owned())
+ .unwrap_or_default()
+ }
+
+ pub fn open(
+ workspace: &str,
+ provider: &str,
+ model: &str,
+ approval: &str,
+ api_key: &str,
+ effort: &str,
+ ) -> Result {
+ let ws = CString::new(workspace).map_err(|e| e.to_string())?;
+ let prov = CString::new(provider).map_err(|e| e.to_string())?;
+ let modl = CString::new(model).map_err(|e| e.to_string())?;
+ let appr = CString::new(approval).map_err(|e| e.to_string())?;
+ let key = CString::new(api_key).map_err(|e| e.to_string())?;
+ let eff = CString::new(effort).map_err(|e| e.to_string())?;
+ let mut err = vec![0u8; 512];
+ let ptr = unsafe {
+ hermit_agent_open(
+ ws.as_ptr(),
+ prov.as_ptr(),
+ modl.as_ptr(),
+ appr.as_ptr(),
+ key.as_ptr(),
+ eff.as_ptr(),
+ err.as_mut_ptr() as *mut c_char,
+ err.len(),
+ )
+ };
+ if ptr.is_null() {
+ return Err(cstr_lossy(&err));
+ }
+ Ok(LiveAgent { ptr })
+ }
+
+ pub fn cancel(agent: &LiveAgent) {
+ unsafe { hermit_agent_cancel(agent.ptr) };
+ }
+
+ pub fn set_workspace(agent: &LiveAgent, path: &str) -> Result<(), String> {
+ let p = CString::new(path).map_err(|e| e.to_string())?;
+ let mut err = vec![0u8; 512];
+ let rc = unsafe {
+ hermit_agent_set_workspace(
+ agent.ptr,
+ p.as_ptr(),
+ err.as_mut_ptr() as *mut c_char,
+ err.len(),
+ )
+ };
+ if rc != 0 {
+ return Err(cstr_lossy(&err));
+ }
+ Ok(())
+ }
+
+ pub fn set_plan_mode(agent: &LiveAgent, on: bool) -> Result<(), String> {
+ let rc = unsafe { hermit_agent_set_plan_mode(agent.ptr, if on { 1 } else { 0 }) };
+ if rc != 0 {
+ return Err("set_plan_mode failed".into());
+ }
+ Ok(())
+ }
+
+ pub fn set_approval(agent: &LiveAgent, approval: &str) -> Result<(), String> {
+ let a = CString::new(approval).map_err(|e| e.to_string())?;
+ let rc = unsafe { hermit_agent_set_approval(agent.ptr, a.as_ptr()) };
+ if rc != 0 {
+ return Err("set_approval failed".into());
+ }
+ Ok(())
+ }
+
+ pub fn set_effort(agent: &LiveAgent, effort: &str) -> Result<(), String> {
+ let e = CString::new(effort).map_err(|e| e.to_string())?;
+ let rc = unsafe { hermit_agent_set_effort(agent.ptr, e.as_ptr()) };
+ if rc != 0 {
+ return Err("set_effort failed".into());
+ }
+ Ok(())
+ }
+
+ pub fn attach_file(agent: &LiveAgent, path: &str) -> Result<(), String> {
+ let p = CString::new(path).map_err(|e| e.to_string())?;
+ let mut err = vec![0u8; 512];
+ let rc = unsafe {
+ hermit_agent_attach_file(
+ agent.ptr,
+ p.as_ptr(),
+ err.as_mut_ptr() as *mut c_char,
+ err.len(),
+ )
+ };
+ if rc != 0 {
+ return Err(cstr_lossy(&err));
+ }
+ Ok(())
+ }
+
+ pub fn pending_clear(agent: &LiveAgent) {
+ unsafe { hermit_agent_pending_clear(agent.ptr) };
+ }
+
+ pub fn compact(agent: &LiveAgent) -> Result<(), String> {
+ let mut err = vec![0u8; 512];
+ let rc = unsafe {
+ hermit_agent_compact(agent.ptr, err.as_mut_ptr() as *mut c_char, err.len())
+ };
+ if rc != 0 {
+ return Err(cstr_lossy(&err));
+ }
+ Ok(())
+ }
+
+ pub struct UsageSnap {
+ pub prompt: i64,
+ pub out: i64,
+ pub cost: f64,
+ pub priced: bool,
+ pub pct: f64,
+ }
+
+ pub fn usage(agent: &LiveAgent) -> Result {
+ let mut prompt = 0i64;
+ let mut out = 0i64;
+ let mut cost = 0f64;
+ let mut priced = 0i32;
+ unsafe {
+ hermit_agent_usage(agent.ptr, &mut prompt, &mut out, &mut cost, &mut priced);
+ }
+ let pct = ((prompt + out) as f64 / 128_000.0 * 100.0).min(99.0);
+ Ok(UsageSnap {
+ prompt,
+ out,
+ cost,
+ priced: priced != 0,
+ pct,
+ })
+ }
+
+ pub fn new_session(agent: &LiveAgent) -> Result {
+ let mut buf = vec![0u8; 64];
+ let rc = unsafe {
+ hermit_agent_new_session(agent.ptr, buf.as_mut_ptr() as *mut c_char, buf.len())
+ };
+ if rc != 0 {
+ return Err("new_session failed".into());
+ }
+ Ok(cstr_lossy(&buf))
+ }
+
+ pub fn switch_session(agent: &LiveAgent, id: &str) -> Result<(), String> {
+ let i = CString::new(id).map_err(|e| e.to_string())?;
+ let rc = unsafe { hermit_agent_switch_session(agent.ptr, i.as_ptr()) };
+ if rc != 0 {
+ return Err("switch_session failed".into());
+ }
+ Ok(())
+ }
+
+ pub fn session_history_json(agent: &LiveAgent) -> Result {
+ let ptr = unsafe { hermit_agent_history_json(agent.ptr) };
+ if ptr.is_null() {
+ return Ok("[]".into());
+ }
+ let json = unsafe { CStr::from_ptr(ptr) }
+ .to_string_lossy()
+ .into_owned();
+ unsafe { free(ptr as *mut c_void) };
+ Ok(json)
+ }
+
+ pub fn todo_text(agent: &LiveAgent) -> String {
+ let ptr = unsafe { hermit_agent_todo(agent.ptr) };
+ if ptr.is_null() {
+ return String::new();
+ }
+ let s = unsafe { CStr::from_ptr(ptr) }
+ .to_string_lossy()
+ .into_owned();
+ unsafe { free(ptr as *mut c_void) };
+ s
+ }
+
+ pub fn export_md(agent: &LiveAgent, title: &str) -> Result {
+ let t = CString::new(title).map_err(|e| e.to_string())?;
+ let ptr = unsafe { hermit_agent_export_md(agent.ptr, t.as_ptr()) };
+ if ptr.is_null() {
+ return Err("export failed".into());
+ }
+ let s = unsafe { CStr::from_ptr(ptr) }
+ .to_string_lossy()
+ .into_owned();
+ unsafe { free(ptr as *mut c_void) };
+ Ok(s)
+ }
+
+ pub fn creds_has(provider: &str) -> bool {
+ let Ok(p) = CString::new(provider) else {
+ return false;
+ };
+ unsafe { hermit_creds_has(p.as_ptr()) != 0 }
+ }
+
+ pub struct TurnCallbacks {
+ pub on_assistant: Box,
+ pub on_thought: Box,
+ pub on_tool: Box,
+ pub on_tool_result: Box,
+ pub on_todo: Box,
+ pub on_status: Box,
+ pub gate: Arc,
+ pub question_gate: Arc,
+ pub emit_consent: Box) + Send>,
+ pub emit_question: Box) + Send>,
+ }
+
+ struct TurnUd {
+ cb: *mut TurnCallbacks,
+ }
+
+ unsafe fn cstr(p: *const c_char) -> String {
+ if p.is_null() {
+ return String::new();
+ }
+ CStr::from_ptr(p).to_string_lossy().into_owned()
+ }
+
+ extern "C" fn c_assistant(text: *const c_char, len: usize, ud: *mut c_void) {
+ if text.is_null() || ud.is_null() {
+ return;
+ }
+ let s = unsafe { std::slice::from_raw_parts(text as *const u8, len) };
+ let t = String::from_utf8_lossy(s);
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_assistant)(&t);
+ }
+
+ extern "C" fn c_thought(text: *const c_char, len: usize, ud: *mut c_void) {
+ if text.is_null() || ud.is_null() {
+ return;
+ }
+ let s = unsafe { std::slice::from_raw_parts(text as *const u8, len) };
+ let t = String::from_utf8_lossy(s);
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_thought)(&t);
+ }
+
+ extern "C" fn c_tool(
+ phase: *const c_char,
+ id: *const c_char,
+ name: *const c_char,
+ detail: *const c_char,
+ ud: *mut c_void,
+ ) {
+ if ud.is_null() {
+ return;
+ }
+ let phase = unsafe { cstr(phase) };
+ let id = unsafe { cstr(id) };
+ let name = unsafe { cstr(name) };
+ let detail = unsafe { cstr(detail) };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_tool)(&phase, &id, &name, &detail);
+ }
+
+ extern "C" fn c_tool_result(
+ id: *const c_char,
+ name: *const c_char,
+ out: *const c_char,
+ len: usize,
+ exit: c_int,
+ ud: *mut c_void,
+ ) {
+ if ud.is_null() {
+ return;
+ }
+ let id = unsafe { cstr(id) };
+ let name = unsafe { cstr(name) };
+ let out = if out.is_null() {
+ String::new()
+ } else {
+ let s = unsafe { std::slice::from_raw_parts(out as *const u8, len) };
+ String::from_utf8_lossy(s).into_owned()
+ };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_tool_result)(&id, &name, &out, exit);
+ }
+
+ extern "C" fn c_todo(text: *const c_char, ud: *mut c_void) {
+ if ud.is_null() {
+ return;
+ }
+ let t = unsafe { cstr(text) };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_todo)(&t);
+ }
+
+ extern "C" fn c_status(text: *const c_char, ud: *mut c_void) {
+ if ud.is_null() {
+ return;
+ }
+ let t = unsafe { cstr(text) };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ (cb.on_status)(&t);
+ }
+
+ extern "C" fn c_perm(
+ tool: *const c_char,
+ detail: *const c_char,
+ preview: *const c_char,
+ ud: *mut c_void,
+ ) -> c_int {
+ if ud.is_null() {
+ return 0;
+ }
+ let tool = unsafe { cstr(tool) };
+ let detail = unsafe { cstr(detail) };
+ let preview = if preview.is_null() {
+ None
+ } else {
+ Some(unsafe { cstr(preview) })
+ };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ let id = format!(
+ "c{}",
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|d| d.as_millis())
+ .unwrap_or(0)
+ );
+ (cb.emit_consent)(id.clone(), tool.clone(), detail.clone(), preview);
+ cb.gate.request(&id, &tool, &detail)
+ }
+
+ extern "C" fn c_ask(
+ question: *const c_char,
+ options: *const c_char,
+ ans: *mut c_char,
+ ans_sz: usize,
+ ud: *mut c_void,
+ ) -> c_int {
+ if ud.is_null() || ans.is_null() || ans_sz == 0 {
+ return -1;
+ }
+ let question = unsafe { cstr(question) };
+ let options_raw = unsafe { cstr(options) };
+ let opts: Vec = if options_raw.is_empty() {
+ Vec::new()
+ } else if options_raw.trim_start().starts_with('[') {
+ serde_json::from_str(&options_raw).unwrap_or_default()
+ } else {
+ options_raw
+ .split(|c| c == '\n' || c == '|')
+ .map(|s| s.trim().to_string())
+ .filter(|s| !s.is_empty())
+ .collect()
+ };
+ let turn = unsafe { &mut *(ud as *mut TurnUd) };
+ let cb = unsafe { &mut *turn.cb };
+ let id = format!(
+ "q{}",
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|d| d.as_millis())
+ .unwrap_or(0)
+ );
+ (cb.emit_question)(id.clone(), question.clone(), opts);
+ match cb.question_gate.request_text(&id, "ask_user", &question) {
+ None => -1,
+ Some(text) => {
+ let bytes = text.as_bytes();
+ let n = bytes.len().min(ans_sz - 1);
+ unsafe {
+ std::ptr::copy_nonoverlapping(bytes.as_ptr(), ans as *mut u8, n);
+ *ans.add(n) = 0;
+ }
+ 0
+ }
+ }
+ }
+
+ pub fn run_turn(agent: &LiveAgent, text: &str, mut cb: TurnCallbacks) -> Result<(), String> {
+ let prompt = CString::new(text).map_err(|e| e.to_string())?;
+ let mut err = vec![0u8; 1024];
+ let mut turn_ud = TurnUd {
+ cb: &mut cb as *mut TurnCallbacks,
+ };
+ let hooks = HermitHooks {
+ on_assistant: Some(c_assistant),
+ on_thought: Some(c_thought),
+ on_tool: Some(c_tool),
+ on_tool_result: Some(c_tool_result),
+ on_todo: Some(c_todo),
+ on_status: Some(c_status),
+ on_perm: Some(c_perm),
+ on_ask: Some(c_ask),
+ ud: &mut turn_ud as *mut TurnUd as *mut c_void,
+ };
+ let rc = unsafe {
+ hermit_agent_turn(
+ agent.ptr,
+ prompt.as_ptr(),
+ &hooks,
+ err.as_mut_ptr() as *mut c_char,
+ err.len(),
+ )
+ };
+ if rc != 0 {
+ return Err(cstr_lossy(&err));
+ }
+ Ok(())
+ }
+
+ pub fn store_list(workspace: &str) -> Vec<(String, String, i64)> {
+ let Ok(ws) = CString::new(workspace) else {
+ return Vec::new();
+ };
+ let mut ids = [[0i8; 128]; 200];
+ let n = unsafe { hermit_store_list(ws.as_ptr(), ids.as_mut_ptr(), 200) };
+ if n <= 0 {
+ return Vec::new();
+ }
+ let mut out = Vec::new();
+ for i in 0..(n as usize) {
+ let id = unsafe { CStr::from_ptr(ids[i].as_ptr()) }
+ .to_string_lossy()
+ .into_owned();
+ if id.is_empty() {
+ continue;
+ }
+ let Ok(id_c) = CString::new(id.as_str()) else {
+ continue;
+ };
+ let mut title_buf = vec![0u8; 128];
+ let mut mtime: i64 = 0;
+ let rc = unsafe {
+ hermit_store_info(
+ ws.as_ptr(),
+ id_c.as_ptr(),
+ title_buf.as_mut_ptr() as *mut c_char,
+ title_buf.len(),
+ &mut mtime,
+ )
+ };
+ let title = if rc == 0 {
+ cstr_lossy(&title_buf)
+ } else {
+ id.clone()
+ };
+ out.push((id, title, mtime));
+ }
+ out
+ }
+
+ pub fn store_title_set(workspace: &str, id: &str, title: &str) -> bool {
+ let Ok(ws) = CString::new(workspace) else {
+ return false;
+ };
+ let Ok(i) = CString::new(id) else {
+ return false;
+ };
+ let Ok(t) = CString::new(title) else {
+ return false;
+ };
+ unsafe { hermit_store_title_set(ws.as_ptr(), i.as_ptr(), t.as_ptr()) == 0 }
+ }
+
+ pub fn store_delete(workspace: &str, id: &str) -> bool {
+ let Ok(ws) = CString::new(workspace) else {
+ return false;
+ };
+ let Ok(i) = CString::new(id) else {
+ return false;
+ };
+ unsafe { hermit_store_delete(ws.as_ptr(), i.as_ptr()) == 0 }
+ }
+
+ pub fn creds_save(provider: &str, key: &str) -> bool {
+ let Ok(p) = CString::new(provider) else {
+ return false;
+ };
+ let Ok(k) = CString::new(key) else {
+ return false;
+ };
+ unsafe { hermit_creds_save(p.as_ptr(), k.as_ptr()) == 0 }
+ }
+
+ pub struct ProviderSnap {
+ pub name: String,
+ pub model: String,
+ pub has_key: bool,
+ pub key_from: Option,
+ }
+
+ pub fn providers_list() -> Result, String> {
+ let presets = [
+ ("deepseek", "deepseek-flash"),
+ ("openai", "gpt-4.1"),
+ ("anthropic", "claude-sonnet-4"),
+ ];
+ Ok(presets
+ .into_iter()
+ .map(|(name, model)| {
+ let has = creds_has(name)
+ || std::env::var(format!("{}_API_KEY", name.to_ascii_uppercase())).is_ok()
+ || (name == "openai" && std::env::var("OPENAI_API_KEY").is_ok())
+ || (name == "deepseek"
+ && (std::env::var("DEEPSEEK_API_KEY").is_ok()
+ || std::env::var("OPENAI_API_KEY").is_ok()));
+ ProviderSnap {
+ name: name.into(),
+ model: model.into(),
+ has_key: has,
+ key_from: if has { Some("creds/env".into()) } else { None },
+ }
+ })
+ .collect())
+ }
+}
+
+#[cfg(feature = "engine")]
+pub use live::*;
diff --git a/archive/desktop-tauri/src-tauri/src/ffi_bridge.c b/archive/desktop-tauri/src-tauri/src/ffi_bridge.c
new file mode 100644
index 0000000..5fc0618
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/src/ffi_bridge.c
@@ -0,0 +1,517 @@
+#include "ffi_bridge.h"
+
+#include "cst_agent.h"
+
+#include
+#include
+#include
+
+typedef struct {
+ const hermit_hooks_t *h;
+} bridge_ud_t;
+
+static void hook_assistant(const char *text, size_t len, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_assistant)
+ b->h->on_assistant(text, len, b->h->ud);
+}
+
+static void hook_think(const char *text, size_t len, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_thought)
+ b->h->on_thought(text, len, b->h->ud);
+}
+
+static void hook_tool(const char *phase, const ctl_tool_call_t *call,
+ const char *detail, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (!b || !b->h || !b->h->on_tool || !call)
+ return;
+ b->h->on_tool(phase ? phase : "", call->id, call->name,
+ detail ? detail : "", b->h->ud);
+}
+
+static void hook_tool_result(const ctl_tool_call_t *call, const char *out,
+ size_t len, int exit_code, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (!b || !b->h || !b->h->on_tool_result || !call)
+ return;
+ b->h->on_tool_result(call->id, call->name, out ? out : "", len, exit_code,
+ b->h->ud);
+}
+
+static void hook_todo(const char *text, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_todo)
+ b->h->on_todo(text ? text : "", b->h->ud);
+}
+
+static void hook_status(const char *msg, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_status)
+ b->h->on_status(msg ? msg : "", b->h->ud);
+}
+
+static int hook_perm(const char *tool_name, const char *detail,
+ const char *preview, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (!b || !b->h || !b->h->on_perm)
+ return CST_PERM_DENY;
+ return b->h->on_perm(tool_name ? tool_name : "", detail ? detail : "",
+ preview, b->h->ud);
+}
+
+static int hook_ask(const char *question, const char *options, char *ans,
+ size_t ans_sz, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (!b || !b->h || !b->h->on_ask)
+ return -1;
+ return b->h->on_ask(question ? question : "", options ? options : "", ans,
+ ans_sz, b->h->ud);
+}
+
+static void hook_turn_begin(const char *user_text, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_turn_begin)
+ b->h->on_turn_begin(user_text ? user_text : "", b->h->ud);
+}
+
+static void hook_turn_end(int rc, const char *err, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_turn_end)
+ b->h->on_turn_end(rc, err, b->h->ud);
+}
+
+static void hook_error(const char *kind, const char *message, void *ud) {
+ bridge_ud_t *b = (bridge_ud_t *)ud;
+ if (b && b->h && b->h->on_error)
+ b->h->on_error(kind ? kind : "", message ? message : "", b->h->ud);
+}
+
+void *hermit_agent_open(const char *workspace, const char *provider,
+ const char *model, const char *approval,
+ const char *api_key, const char *effort, char *err,
+ size_t err_sz) {
+ if (err && err_sz)
+ err[0] = '\0';
+ if (!workspace || !workspace[0]) {
+ if (err && err_sz)
+ snprintf(err, err_sz, "workspace required");
+ return NULL;
+ }
+ if (cst_agent_store_ensure(workspace) != 0) {
+ if (err && err_sz)
+ snprintf(err, err_sz, "store ensure failed");
+ return NULL;
+ }
+
+ cst_agent_config_t cfg;
+ memset(&cfg, 0, sizeof(cfg));
+ snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", workspace);
+ if (provider && provider[0])
+ snprintf(cfg.provider, sizeof(cfg.provider), "%s", provider);
+ else
+ snprintf(cfg.provider, sizeof(cfg.provider), "%s", "deepseek");
+ if (model && model[0])
+ snprintf(cfg.model, sizeof(cfg.model), "%s", model);
+ else
+ snprintf(cfg.model, sizeof(cfg.model), "%s", "deepseek-flash");
+ snprintf(cfg.mode, sizeof(cfg.mode), "%s", CST_DEFAULT_MODE);
+ cfg.protocol = CST_PROTO_NATIVE_EOS;
+ cfg.max_steps = CST_DEFAULT_MAX_STEPS;
+ cfg.tool_out_cap = CST_DEFAULT_TOOL_OUT_CAP;
+ cfg.approval = CST_DEFAULT_APPROVAL;
+ if (approval && approval[0]) {
+ int a = CST_DEFAULT_APPROVAL;
+ if (cst_approval_parse(approval, &a) == 0)
+ cfg.approval = a;
+ }
+ if (effort && effort[0] && cst_effort_valid(effort))
+ snprintf(cfg.effort, sizeof(cfg.effort), "%s", effort);
+
+ char id[64];
+ if (cst_agent_store_resolve_default(cfg.workspace_root, cfg.session_path,
+ sizeof(cfg.session_path), id,
+ sizeof(id)) != 0) {
+ if (cst_agent_store_new_id(id, sizeof(id)) != 0 ||
+ cst_agent_store_path_for_id(cfg.workspace_root, id, cfg.session_path,
+ sizeof(cfg.session_path)) != 0) {
+ if (err && err_sz)
+ snprintf(err, err_sz, "session path failed");
+ return NULL;
+ }
+ cst_agent_store_set_current(cfg.workspace_root, id);
+ }
+
+ if (api_key && api_key[0]) {
+ snprintf(cfg.api_key, sizeof(cfg.api_key), "%s", api_key);
+ } else {
+ char key[512];
+ const char *from = NULL;
+ if (cst_creds_resolve(cfg.provider, key, sizeof(key), &from) == 0)
+ snprintf(cfg.api_key, sizeof(cfg.api_key), "%s", key);
+ }
+
+ cst_agent_t *a = cst_agent_create(&cfg);
+ if (!a && err && err_sz)
+ snprintf(err, err_sz, "cst_agent_create failed");
+ return a;
+}
+
+void hermit_agent_close(void *agent) {
+ if (agent)
+ cst_agent_destroy((cst_agent_t *)agent);
+}
+
+int hermit_agent_turn(void *agent, const char *user_text,
+ const hermit_hooks_t *hooks, char *err, size_t err_sz) {
+ if (!agent || !user_text)
+ return -1;
+ bridge_ud_t bud = {.h = hooks};
+ cst_agent_hooks_t h;
+ memset(&h, 0, sizeof(h));
+ h.on_assistant_delta = hook_assistant;
+ h.on_think_delta = hook_think;
+ h.on_tool_event = hook_tool;
+ h.on_tool_result = hook_tool_result;
+ h.on_todo = hook_todo;
+ h.on_status = hook_status;
+ h.on_permission = hook_perm;
+ h.on_ask_user = hook_ask;
+ h.on_turn_begin = hook_turn_begin;
+ h.on_turn_end = hook_turn_end;
+ h.on_error = hook_error;
+ h.userdata = &bud;
+ return cst_agent_run_turn_ex((cst_agent_t *)agent, user_text, &h, err,
+ err_sz);
+}
+
+void hermit_agent_cancel(void *agent) {
+ if (agent)
+ cst_agent_request_cancel((cst_agent_t *)agent);
+}
+
+int hermit_agent_set_workspace(void *agent, const char *path, char *err,
+ size_t err_sz) {
+ if (!agent || !path)
+ return -1;
+ return cst_agent_set_workspace((cst_agent_t *)agent, path, err, err_sz);
+}
+
+int hermit_agent_new_session(void *agent, char *id_out, size_t id_sz) {
+ if (!agent)
+ return -1;
+ return cst_agent_new_session((cst_agent_t *)agent, id_out, id_sz);
+}
+
+int hermit_agent_switch_session(void *agent, const char *id) {
+ if (!agent || !id)
+ return -1;
+ return cst_agent_switch_session((cst_agent_t *)agent, id);
+}
+
+int hermit_agent_session_id(void *agent, char *out, size_t out_sz) {
+ if (!agent || !out || out_sz == 0)
+ return -1;
+ cst_agent_session_id((cst_agent_t *)agent, out, out_sz);
+ return 0;
+}
+
+int hermit_agent_set_plan_mode(void *agent, int on) {
+ if (!agent)
+ return -1;
+ cst_agent_config_t *cfg = cst_agent_config_mut((cst_agent_t *)agent);
+ if (!cfg)
+ return -1;
+ cfg->plan_mode = on ? 1 : 0;
+ return 0;
+}
+
+int hermit_agent_set_approval(void *agent, const char *approval) {
+ if (!agent || !approval)
+ return -1;
+ cst_agent_config_t *cfg = cst_agent_config_mut((cst_agent_t *)agent);
+ if (!cfg)
+ return -1;
+ int a = cfg->approval;
+ if (cst_approval_parse(approval, &a) == 0)
+ cfg->approval = a;
+ return 0;
+}
+
+int hermit_agent_set_effort(void *agent, const char *effort) {
+ if (!agent)
+ return -1;
+ cst_agent_config_t *cfg = cst_agent_config_mut((cst_agent_t *)agent);
+ if (!cfg)
+ return -1;
+ if (!effort || !effort[0]) {
+ cfg->effort[0] = '\0';
+ return 0;
+ }
+ if (!cst_effort_valid(effort))
+ return -1;
+ snprintf(cfg->effort, sizeof(cfg->effort), "%s", effort);
+ return 0;
+}
+
+int hermit_agent_attach_file(void *agent, const char *path, char *err,
+ size_t err_sz) {
+ if (!agent || !path)
+ return -1;
+ return cst_agent_attach_file((cst_agent_t *)agent, path, err, err_sz);
+}
+
+void hermit_agent_pending_clear(void *agent) {
+ if (agent)
+ cst_agent_pending_clear((cst_agent_t *)agent);
+}
+
+int hermit_agent_compact(void *agent, char *err, size_t err_sz) {
+ if (!agent)
+ return -1;
+ return cst_agent_compact((cst_agent_t *)agent, err, err_sz);
+}
+
+void hermit_agent_usage(void *agent, long *prompt, long *out_tokens,
+ double *cost, int *priced) {
+ if (!agent)
+ return;
+ cst_usage_t s, t;
+ memset(&s, 0, sizeof(s));
+ memset(&t, 0, sizeof(t));
+ cst_agent_usage((cst_agent_t *)agent, &s, &t);
+ if (prompt)
+ *prompt = s.prompt;
+ if (out_tokens)
+ *out_tokens = s.out;
+ if (cost)
+ *cost = s.cost;
+ if (priced)
+ *priced = s.priced;
+}
+
+static void json_escape_append(char **buf, size_t *len, size_t *cap,
+ const char *s) {
+ if (!s)
+ s = "";
+ for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
+ char tmp[8];
+ const char *chunk;
+ size_t n;
+ if (*p == '"' || *p == '\\') {
+ tmp[0] = '\\';
+ tmp[1] = (char)*p;
+ tmp[2] = 0;
+ chunk = tmp;
+ n = 2;
+ } else if (*p < 0x20) {
+ n = (size_t)snprintf(tmp, sizeof(tmp), "\\u%04x", *p);
+ chunk = tmp;
+ } else {
+ tmp[0] = (char)*p;
+ tmp[1] = 0;
+ chunk = tmp;
+ n = 1;
+ }
+ if (*len + n + 1 >= *cap) {
+ *cap = (*cap + n + 1) * 2;
+ char *nb = (char *)realloc(*buf, *cap);
+ if (!nb)
+ return;
+ *buf = nb;
+ }
+ memcpy(*buf + *len, chunk, n);
+ *len += n;
+ (*buf)[*len] = 0;
+ }
+}
+
+static int hist_append_obj(char **buf, size_t *len, size_t *cap, int *first,
+ const char *head, const char *body) {
+ if (!*first) {
+ if (*len + 2 >= *cap) {
+ *cap *= 2;
+ char *nb = (char *)realloc(*buf, *cap);
+ if (!nb)
+ return -1;
+ *buf = nb;
+ }
+ (*buf)[(*len)++] = ',';
+ (*buf)[*len] = 0;
+ }
+ *first = 0;
+ size_t hn = strlen(head);
+ if (*len + hn + 8 >= *cap) {
+ *cap = (*cap + hn) * 2;
+ char *nb = (char *)realloc(*buf, *cap);
+ if (!nb)
+ return -1;
+ *buf = nb;
+ }
+ memcpy(*buf + *len, head, hn);
+ *len += hn;
+ (*buf)[*len] = 0;
+ json_escape_append(buf, len, cap, body ? body : "");
+ if (*len + 3 >= *cap) {
+ *cap *= 2;
+ char *nb = (char *)realloc(*buf, *cap);
+ if (!nb)
+ return -1;
+ *buf = nb;
+ }
+ (*buf)[(*len)++] = '"';
+ (*buf)[(*len)++] = '}';
+ (*buf)[*len] = 0;
+ return 0;
+}
+
+char *hermit_agent_history_json(void *agent) {
+ if (!agent)
+ return NULL;
+ cst_session_t *sess = cst_agent_session((cst_agent_t *)agent);
+ if (!sess)
+ return NULL;
+ cst_session_lock(sess);
+ int n = cst_session_count(sess);
+ size_t cap = 4096, len = 0;
+ char *buf = (char *)malloc(cap);
+ if (!buf) {
+ cst_session_unlock(sess);
+ return NULL;
+ }
+ buf[0] = '[';
+ len = 1;
+ int first = 1;
+ for (int i = 0; i < n; i++) {
+ const cst_message_t *m = cst_session_get(sess, i);
+ if (!m)
+ continue;
+ if (m->role == CST_ROLE_SYSTEM)
+ continue;
+ char idbuf[64], head[288];
+ snprintf(idbuf, sizeof(idbuf), "h%d", i);
+
+ if (m->role == CST_ROLE_ASSISTANT && m->reasoning && m->reasoning[0]) {
+ snprintf(head, sizeof(head),
+ "{\"kind\":\"thought\",\"id\":\"%s-r\",\"text\":\"", idbuf);
+ if (hist_append_obj(&buf, &len, &cap, &first, head, m->reasoning) != 0)
+ break;
+ }
+
+ if (m->role == CST_ROLE_TOOL) {
+ const char *tname =
+ (m->tool_name[0] && !strpbrk(m->tool_name, "\\\"\n\r"))
+ ? m->tool_name
+ : "tool";
+ snprintf(head, sizeof(head),
+ "{\"kind\":\"tool\",\"id\":\"%s\",\"name\":\"%s\",\"status\":"
+ "\"ok\",\"detail\":\"%s\",\"output\":\"",
+ idbuf, tname, tname);
+ if (hist_append_obj(&buf, &len, &cap, &first, head,
+ m->content ? m->content : "") != 0)
+ break;
+ continue;
+ }
+
+ if (m->role == CST_ROLE_USER) {
+ if (!m->content || !m->content[0])
+ continue;
+ snprintf(head, sizeof(head),
+ "{\"kind\":\"user\",\"id\":\"%s\",\"text\":\"", idbuf);
+ if (hist_append_obj(&buf, &len, &cap, &first, head, m->content) != 0)
+ break;
+ continue;
+ }
+
+ if (m->role == CST_ROLE_ASSISTANT) {
+ if (!m->content || !m->content[0])
+ continue;
+ snprintf(head, sizeof(head),
+ "{\"kind\":\"assistant\",\"id\":\"%s\",\"text\":\"", idbuf);
+ if (hist_append_obj(&buf, &len, &cap, &first, head, m->content) != 0)
+ break;
+ }
+ }
+ if (len + 2 >= cap) {
+ char *nb = (char *)realloc(buf, len + 2);
+ if (nb)
+ buf = nb;
+ }
+ buf[len++] = ']';
+ buf[len] = 0;
+ cst_session_unlock(sess);
+ return buf;
+}
+
+char *hermit_agent_todo(void *agent) {
+ if (!agent)
+ return NULL;
+ const char *t = cst_agent_todo((cst_agent_t *)agent);
+ if (!t || !t[0])
+ return NULL;
+ size_t n = strlen(t);
+ char *out = (char *)malloc(n + 1);
+ if (!out)
+ return NULL;
+ memcpy(out, t, n + 1);
+ return out;
+}
+
+char *hermit_agent_export_md(void *agent, const char *title) {
+ if (!agent)
+ return NULL;
+ cst_session_t *sess = cst_agent_session((cst_agent_t *)agent);
+ if (!sess)
+ return NULL;
+ char sid[128];
+ cst_agent_session_id((cst_agent_t *)agent, sid, sizeof(sid));
+ const cst_agent_config_t *cfg = cst_agent_config((cst_agent_t *)agent);
+ const char *model = cfg && cfg->model[0] ? cfg->model : "";
+ return cst_session_export_md(sess, title ? title : "Session", sid, model);
+}
+
+int hermit_creds_has(const char *provider) {
+ char key[512];
+ const char *from = NULL;
+ if (!provider || !provider[0])
+ return 0;
+ return cst_creds_resolve(provider, key, sizeof(key), &from) == 0 ? 1 : 0;
+}
+
+int hermit_store_list(const char *workspace, char ids[][128], int max_ids) {
+ return cst_agent_store_list(workspace, ids, max_ids);
+}
+
+int hermit_store_info(const char *workspace, const char *id, char *title,
+ size_t title_sz, long long *mtime_ms) {
+ cst_session_info_t info;
+ memset(&info, 0, sizeof(info));
+ if (cst_agent_store_info(workspace, id, &info) != 0)
+ return -1;
+ if (title && title_sz) {
+ if (info.title[0])
+ snprintf(title, title_sz, "%s", info.title);
+ else if (info.first_user[0])
+ snprintf(title, title_sz, "%s", info.first_user);
+ else
+ snprintf(title, title_sz, "%s", id);
+ }
+ /* cst_session_info_t.mtime is seconds since epoch (os_lstat); JS Date wants ms. */
+ if (mtime_ms)
+ *mtime_ms = info.mtime > 0 ? info.mtime * 1000LL : 0;
+ return 0;
+}
+
+int hermit_store_title_set(const char *workspace, const char *id,
+ const char *title) {
+ return cst_agent_store_title_set(workspace, id, title);
+}
+
+int hermit_store_delete(const char *workspace, const char *id) {
+ return cst_agent_store_delete(workspace, id);
+}
+
+int hermit_creds_save(const char *provider, const char *key) {
+ return cst_creds_save(provider, key);
+}
diff --git a/archive/desktop-tauri/src-tauri/src/ffi_bridge.h b/archive/desktop-tauri/src-tauri/src/ffi_bridge.h
new file mode 100644
index 0000000..ddac8e0
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/src/ffi_bridge.h
@@ -0,0 +1,88 @@
+/* Thin C façade over cst_agent.h for the Tauri desktop host. */
+#pragma once
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*hermit_on_text_fn)(const char *text, size_t len, void *ud);
+typedef void (*hermit_on_tool_fn)(const char *phase, const char *id,
+ const char *name, const char *detail,
+ void *ud);
+typedef void (*hermit_on_tool_result_fn)(const char *id, const char *name,
+ const char *out, size_t out_len,
+ int exit_code, void *ud);
+typedef void (*hermit_on_todo_fn)(const char *text, void *ud);
+typedef void (*hermit_on_status_fn)(const char *msg, void *ud);
+/* Return CST_PERM_* . preview may be NULL. */
+typedef int (*hermit_on_perm_fn)(const char *tool, const char *detail,
+ const char *preview, void *ud);
+/* Fill ans; return 0 ok, -1 dismiss. options is CSV or JSON-ish string. */
+typedef int (*hermit_on_ask_fn)(const char *question, const char *options,
+ char *ans, size_t ans_sz, void *ud);
+typedef void (*hermit_on_turn_begin_fn)(const char *user_text, void *ud);
+typedef void (*hermit_on_turn_end_fn)(int rc, const char *err, void *ud);
+typedef void (*hermit_on_error_fn)(const char *kind, const char *message,
+ void *ud);
+
+typedef struct {
+ hermit_on_text_fn on_assistant;
+ hermit_on_text_fn on_thought;
+ hermit_on_tool_fn on_tool;
+ hermit_on_tool_result_fn on_tool_result;
+ hermit_on_todo_fn on_todo;
+ hermit_on_status_fn on_status;
+ hermit_on_perm_fn on_perm;
+ hermit_on_ask_fn on_ask;
+ hermit_on_turn_begin_fn on_turn_begin;
+ hermit_on_turn_end_fn on_turn_end;
+ hermit_on_error_fn on_error;
+ void *ud;
+} hermit_hooks_t;
+
+void *hermit_agent_open(const char *workspace, const char *provider,
+ const char *model, const char *approval,
+ const char *api_key, const char *effort, char *err,
+ size_t err_sz);
+void hermit_agent_close(void *agent);
+int hermit_agent_turn(void *agent, const char *user_text,
+ const hermit_hooks_t *hooks, char *err, size_t err_sz);
+void hermit_agent_cancel(void *agent);
+
+int hermit_agent_set_workspace(void *agent, const char *path, char *err,
+ size_t err_sz);
+int hermit_agent_new_session(void *agent, char *id_out, size_t id_sz);
+int hermit_agent_switch_session(void *agent, const char *id);
+int hermit_agent_session_id(void *agent, char *out, size_t out_sz);
+int hermit_agent_set_plan_mode(void *agent, int on);
+int hermit_agent_set_approval(void *agent, const char *approval);
+int hermit_agent_set_effort(void *agent, const char *effort);
+int hermit_agent_attach_file(void *agent, const char *path, char *err,
+ size_t err_sz);
+void hermit_agent_pending_clear(void *agent);
+int hermit_agent_compact(void *agent, char *err, size_t err_sz);
+void hermit_agent_usage(void *agent, long *prompt, long *out_tokens,
+ double *cost, int *priced);
+
+/* Malloc'd JSON array of chat items; caller frees. NULL on failure. */
+char *hermit_agent_history_json(void *agent);
+/* Malloc'd todo text or NULL. */
+char *hermit_agent_todo(void *agent);
+/* Malloc'd markdown export; caller frees. */
+char *hermit_agent_export_md(void *agent, const char *title);
+/* 1 if a saved/env key exists for provider. */
+int hermit_creds_has(const char *provider);
+
+int hermit_store_list(const char *workspace, char ids[][128], int max_ids);
+int hermit_store_info(const char *workspace, const char *id, char *title,
+ size_t title_sz, long long *mtime_ms);
+int hermit_store_title_set(const char *workspace, const char *id,
+ const char *title);
+int hermit_store_delete(const char *workspace, const char *id);
+int hermit_creds_save(const char *provider, const char *key);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/apps/desktop/src-tauri/src/ffi_stub.h b/archive/desktop-tauri/src-tauri/src/ffi_stub.h
similarity index 100%
rename from apps/desktop/src-tauri/src/ffi_stub.h
rename to archive/desktop-tauri/src-tauri/src/ffi_stub.h
diff --git a/archive/desktop-tauri/src-tauri/src/lib.rs b/archive/desktop-tauri/src-tauri/src/lib.rs
new file mode 100644
index 0000000..88e751a
--- /dev/null
+++ b/archive/desktop-tauri/src-tauri/src/lib.rs
@@ -0,0 +1,291 @@
+mod engine;
+mod ffi;
+
+use engine::{SharedEngine, EngineState};
+use std::sync::Mutex;
+use tauri::{
+ menu::{Menu, MenuItem, PredefinedMenuItem, Submenu},
+ Emitter, Manager,
+};
+
+#[tauri::command]
+fn session_list(state: tauri::State<'_, SharedEngine>) -> Vec {
+ engine::list_sessions(&state)
+}
+
+#[tauri::command]
+fn session_new(state: tauri::State<'_, SharedEngine>) -> engine::SessionInfo {
+ engine::new_session(&state)
+}
+
+#[tauri::command]
+fn session_open(
+ state: tauri::State<'_, SharedEngine>,
+ id: String,
+) -> Vec {
+ engine::open_session(&state, &id)
+}
+
+#[tauri::command]
+fn session_rename(
+ state: tauri::State<'_, SharedEngine>,
+ id: String,
+ title: String,
+) -> bool {
+ engine::rename_session(&state, &id, &title)
+}
+
+#[tauri::command]
+fn session_delete(state: tauri::State<'_, SharedEngine>, id: String) -> bool {
+ engine::delete_session(&state, &id)
+}
+
+#[tauri::command]
+fn session_pin(state: tauri::State<'_, SharedEngine>, id: String) -> bool {
+ engine::pin_session(&state, &id)
+}
+
+#[tauri::command]
+fn session_set_folder(
+ state: tauri::State<'_, SharedEngine>,
+ id: String,
+ folder: Option,
+) -> bool {
+ engine::session_set_folder(&state, &id, folder)
+}
+
+#[tauri::command]
+fn session_compact(state: tauri::State<'_, SharedEngine>) -> String {
+ engine::session_compact(&state)
+}
+
+#[tauri::command]
+fn session_export(state: tauri::State<'_, SharedEngine>) -> String {
+ engine::session_export(&state)
+}
+
+#[tauri::command]
+fn settings_get(state: tauri::State<'_, SharedEngine>) -> engine::Settings {
+ engine::settings_get(&state)
+}
+
+#[tauri::command]
+fn settings_set(
+ state: tauri::State<'_, SharedEngine>,
+ settings: engine::Settings,
+) -> engine::Settings {
+ engine::settings_set(&state, settings)
+}
+
+#[tauri::command]
+fn attach_list(state: tauri::State<'_, SharedEngine>) -> Vec {
+ engine::attach_list(&state)
+}
+
+#[tauri::command]
+fn attach_file(
+ state: tauri::State<'_, SharedEngine>,
+ path: String,
+) -> Result {
+ engine::attach_file(&state, path)
+}
+
+#[tauri::command]
+fn attach_clear(state: tauri::State<'_, SharedEngine>) {
+ engine::attach_clear(&state)
+}
+
+#[tauri::command]
+fn attach_remove(state: tauri::State<'_, SharedEngine>, id: String) {
+ engine::attach_remove(&state, &id)
+}
+
+#[tauri::command]
+fn providers_list(state: tauri::State<'_, SharedEngine>) -> Vec {
+ engine::providers_list(&state)
+}
+
+#[tauri::command]
+fn providers_use(
+ state: tauri::State<'_, SharedEngine>,
+ provider: String,
+ model: String,
+ key: String,
+) -> Result {
+ engine::providers_use(&state, provider, model, key)
+}
+
+#[tauri::command]
+fn usage_get(state: tauri::State<'_, SharedEngine>) -> engine::UsageInfo {
+ engine::usage_get(&state)
+}
+
+#[tauri::command]
+fn todo_get(state: tauri::State<'_, SharedEngine>) -> String {
+ engine::todo_get(&state)
+}
+
+#[tauri::command]
+fn bash_list(state: tauri::State<'_, SharedEngine>) -> Vec {
+ engine::bash_list(&state)
+}
+
+#[tauri::command]
+fn workspace_files(
+ state: tauri::State<'_, SharedEngine>,
+ query: String,
+) -> Vec {
+ engine::workspace_files(&state, query)
+}
+
+#[tauri::command]
+fn engine_status() -> String {
+ engine::engine_status()
+}
+
+#[tauri::command]
+async fn send_turn(
+ app: tauri::AppHandle,
+ state: tauri::State<'_, SharedEngine>,
+ text: String,
+) -> Result<(), String> {
+ engine::send_turn(app, &state, text).await;
+ Ok(())
+}
+
+#[tauri::command]
+fn cancel_turn(state: tauri::State<'_, SharedEngine>) {
+ engine::cancel_turn(&state);
+}
+
+#[tauri::command]
+fn consent_answer(state: tauri::State<'_, SharedEngine>, id: String, answer: String) {
+ engine::consent_answer(&state, &id, &answer);
+}
+
+#[tauri::command]
+fn question_answer(state: tauri::State<'_, SharedEngine>, id: String, answer: String) {
+ engine::question_answer(&state, &id, &answer);
+}
+
+#[cfg(target_os = "linux")]
+fn apply_linux_webview_workarounds() {
+ let nvidia = std::path::Path::new("/proc/driver/nvidia/version").exists();
+ let wayland = std::env::var_os("WAYLAND_DISPLAY").is_some()
+ || std::env::var("XDG_SESSION_TYPE").ok().as_deref() == Some("wayland");
+ unsafe {
+ if nvidia && std::env::var_os("__NV_DISABLE_EXPLICIT_SYNC").is_none() {
+ std::env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1");
+ }
+ if nvidia && wayland && std::env::var_os("GDK_BACKEND").is_none() {
+ std::env::set_var("GDK_BACKEND", "x11");
+ }
+ let backend = std::env::var("GDK_BACKEND").unwrap_or_default();
+ let on_x11 = backend == "x11" || (!wayland && std::env::var_os("DISPLAY").is_some());
+ if nvidia && on_x11 && std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() {
+ std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
+ }
+ }
+}
+
+fn build_menu(app: &tauri::App) -> tauri::Result