From 697691e5d9712b7e109eeb79e5fd9a4682743c5f Mon Sep 17 00:00:00 2001 From: tarunerror Date: Fri, 16 Jan 2026 14:56:02 +0530 Subject: [PATCH 1/4] feat: Add Windows compatibility for OpenCode CLI detection - Support .cmd and .bat extensions for npm-installed OpenCode on Windows - Add %APPDATA%\npm to search paths for Windows npm global installs - Add APPDATA and USERPROFILE fallbacks for config path resolution - Show Windows-specific install instructions (npm install -g opencode-ai) - Update UI to show platform-appropriate paths and commands - Add GitHub Actions workflow for building on all platforms (Windows, macOS, Linux) --- .claude/settings.local.json | 9 + .github/workflows/build-all-platforms.yml | 111 + AGENTS.md | 19 +- src-tauri/Cargo.lock | 2 +- src-tauri/gen/schemas/windows-schema.json | 2310 +++++++++++++++++++++ 5 files changed, 2439 insertions(+), 12 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 .github/workflows/build-all-platforms.yml create mode 100644 src-tauri/gen/schemas/windows-schema.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..801bfe3 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "mcp__acp__Bash", + "mcp__acp__Write", + "mcp__acp__Edit" + ] + } +} diff --git a/.github/workflows/build-all-platforms.yml b/.github/workflows/build-all-platforms.yml new file mode 100644 index 0000000..f8cb28e --- /dev/null +++ b/.github/workflows/build-all-platforms.yml @@ -0,0 +1,111 @@ +name: Build All Platforms + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., v0.1.2). Leave empty to use current ref." + required: false + type: string + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build (${{ matrix.platform }}) + strategy: + fail-fast: false + matrix: + include: + # Windows + - platform: windows-latest + target: x86_64-pc-windows-msvc + args: "" + + # macOS Intel + - platform: macos-13 + target: x86_64-apple-darwin + args: "--target x86_64-apple-darwin" + + # macOS Apple Silicon + - platform: macos-14 + target: aarch64-apple-darwin + args: "--target aarch64-apple-darwin" + + # Linux + - platform: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + args: "" + + runs-on: ${{ matrix.platform }} + timeout-minutes: 60 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set release tag + shell: bash + run: | + set -euo pipefail + TAG_INPUT="${{ inputs.tag }}" + if [ -n "$TAG_INPUT" ]; then + if [[ "$TAG_INPUT" == v* ]]; then + TAG="$TAG_INPUT" + else + TAG="v$TAG_INPUT" + fi + else + TAG="${GITHUB_REF_NAME}" + fi + echo "RELEASE_TAG=$TAG" >> "$GITHUB_ENV" + + # Linux dependencies + - name: Install Linux dependencies + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.27.0 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0.5.17 + env: + CI: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tagName: ${{ env.RELEASE_TAG }} + releaseName: OpenWork ${{ env.RELEASE_TAG }} + releaseBody: | + ## Downloads + - **Windows**: `.exe` or `.msi` installer + - **macOS**: `.dmg` disk image + - **Linux**: `.AppImage` or `.deb` package + releaseDraft: true + prerelease: false + args: ${{ matrix.args }} diff --git a/AGENTS.md b/AGENTS.md index a47fc16..77e6acc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,17 +96,14 @@ This captures OpenWork’s preferred reactivity + UI state patterns (avoid globa ## Skill: Trigger a Release -OpenWork releases are built by GitHub Actions (`Release App`). A release is triggered by pushing a `v*` tag (e.g. `v0.1.6`). +OpenWork releases are built by GitHub Actions (`Release App`) and publish signed + notarized macOS DMGs to the GitHub Release for a tag. ### Standard release (recommended) -1. Ensure `main` is green and up to date. -2. Bump versions (keep these in sync): - - `apps/openwork/package.json` (`version`) - - `apps/openwork/src-tauri/tauri.conf.json` (`version`) - - `apps/openwork/src-tauri/Cargo.toml` (`version`) -3. Merge the version bump to `main`. -4. Create and push a tag: +1. Bump versions (at minimum `apps/openwork/package.json`, and keep Tauri/Rust versions in sync). +2. Merge to `main`. +3. Create and push a version tag: + - `git tag vX.Y.Z` - `git push origin vX.Y.Z` @@ -120,7 +117,7 @@ If the workflow needs to be re-run for an existing tag (e.g. notarization retry) ### Verify -- Runs: `gh run list --repo different-ai/openwork --workflow "Release App" --limit 5` -- Release: `gh release view vX.Y.Z --repo different-ai/openwork` +- `gh run list --repo different-ai/openwork --workflow "Release App" --limit 5` +- `gh release view vX.Y.Z --repo different-ai/openwork` -Confirm the DMG assets are attached and versioned correctly. \ No newline at end of file +Confirm the DMG assets are attached and versioned correctly. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8ee555e..14b63c3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2014,7 +2014,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openwork" -version = "0.1.3" +version = "0.1.6" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/gen/schemas/windows-schema.json b/src-tauri/gen/schemas/windows-schema.json new file mode 100644 index 0000000..5aa9e87 --- /dev/null +++ b/src-tauri/gen/schemas/windows-schema.json @@ -0,0 +1,2310 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "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-ask`\n- `allow-confirm`\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-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "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." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file From 6efc9a2cb1ad7d41512c740753dcf4614ea01d78 Mon Sep 17 00:00:00 2001 From: tarunerror Date: Fri, 16 Jan 2026 15:07:31 +0530 Subject: [PATCH 2/4] feat: Add Windows compatibility for OpenCode CLI detection - Support .cmd and .bat extensions for npm-installed OpenCode on Windows - Add %APPDATA%\npm to search paths for Windows npm global installs - Add APPDATA and USERPROFILE fallbacks for config path resolution - Show Windows-specific install instructions (npm install -g opencode-ai) - Update UI to show platform-appropriate paths and commands - Add GitHub Actions workflow for building on all platforms --- src-tauri/src/lib.rs | 906 ++++++++++++++++++++++--------------------- src/App.tsx | 896 ++++++++++++++++++++++++++++++++---------- 2 files changed, 1155 insertions(+), 647 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0b6d3b5..1492c81 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,11 +1,11 @@ use std::{ - env, - ffi::OsStr, - fs, - net::TcpListener, - path::{Path, PathBuf}, - process::{Child, Command, Stdio}, - sync::Mutex, + env, + ffi::OsStr, + fs, + net::TcpListener, + path::{Path, PathBuf}, + process::{Child, Command, Stdio}, + sync::Mutex, }; use serde::Serialize; @@ -13,475 +13,497 @@ use tauri::State; #[derive(Default)] struct EngineManager { - inner: Mutex, + inner: Mutex, } #[derive(Default)] struct EngineState { - child: Option, - project_dir: Option, - hostname: Option, - port: Option, - base_url: Option, + child: Option, + project_dir: Option, + hostname: Option, + port: Option, + base_url: Option, } #[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct EngineInfo { - pub running: bool, - pub base_url: Option, - pub project_dir: Option, - pub hostname: Option, - pub port: Option, - pub pid: Option, + pub running: bool, + pub base_url: Option, + pub project_dir: Option, + pub hostname: Option, + pub port: Option, + pub pid: Option, } #[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct EngineDoctorResult { - pub found: bool, - pub in_path: bool, - pub resolved_path: Option, - pub version: Option, - pub supports_serve: bool, - pub notes: Vec, + pub found: bool, + pub in_path: bool, + pub resolved_path: Option, + pub version: Option, + pub supports_serve: bool, + pub notes: Vec, } #[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ExecResult { - pub ok: bool, - pub status: i32, - pub stdout: String, - pub stderr: String, + pub ok: bool, + pub status: i32, + pub stdout: String, + pub stderr: String, } #[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct OpencodeConfigFile { - pub path: String, - pub exists: bool, - pub content: Option, + pub path: String, + pub exists: bool, + pub content: Option, } fn find_free_port() -> Result { - let listener = TcpListener::bind(("127.0.0.1", 0)).map_err(|e| e.to_string())?; - let port = listener.local_addr().map_err(|e| e.to_string())?.port(); - Ok(port) + let listener = TcpListener::bind(("127.0.0.1", 0)).map_err(|e| e.to_string())?; + let port = listener.local_addr().map_err(|e| e.to_string())?.port(); + Ok(port) } #[cfg(windows)] -const OPENCODE_EXECUTABLE: &str = "opencode.exe"; +const OPENCODE_EXECUTABLES: &[&str] = &["opencode.exe", "opencode.cmd", "opencode.bat"]; #[cfg(not(windows))] -const OPENCODE_EXECUTABLE: &str = "opencode"; +const OPENCODE_EXECUTABLES: &[&str] = &["opencode"]; fn home_dir() -> Option { - if let Ok(home) = env::var("HOME") { - if !home.trim().is_empty() { - return Some(PathBuf::from(home)); + if let Ok(home) = env::var("HOME") { + if !home.trim().is_empty() { + return Some(PathBuf::from(home)); + } } - } - if let Ok(profile) = env::var("USERPROFILE") { - if !profile.trim().is_empty() { - return Some(PathBuf::from(profile)); + if let Ok(profile) = env::var("USERPROFILE") { + if !profile.trim().is_empty() { + return Some(PathBuf::from(profile)); + } } - } - None + None } fn path_entries() -> Vec { - let mut entries = Vec::new(); - let Some(path) = env::var_os("PATH") else { - return entries; - }; + let mut entries = Vec::new(); + let Some(path) = env::var_os("PATH") else { + return entries; + }; - entries.extend(env::split_paths(&path)); - entries + entries.extend(env::split_paths(&path)); + entries } -fn resolve_in_path(name: &str) -> Option { - for dir in path_entries() { - let candidate = dir.join(name); - if candidate.is_file() { - return Some(candidate); +fn resolve_in_path(names: &[&str]) -> Option { + for dir in path_entries() { + for name in names { + let candidate = dir.join(name); + if candidate.is_file() { + return Some(candidate); + } + } } - } - None + None } fn candidate_opencode_paths() -> Vec { - let mut candidates = Vec::new(); + let mut candidates = Vec::new(); - if let Some(home) = home_dir() { - candidates.push(home.join(".opencode").join("bin").join(OPENCODE_EXECUTABLE)); - } + if let Some(home) = home_dir() { + for exe in OPENCODE_EXECUTABLES { + candidates.push(home.join(".opencode").join("bin").join(exe)); + } + } - // Homebrew default paths. - candidates.push(PathBuf::from("/opt/homebrew/bin").join(OPENCODE_EXECUTABLE)); - candidates.push(PathBuf::from("/usr/local/bin").join(OPENCODE_EXECUTABLE)); + // Windows npm global install location + #[cfg(windows)] + { + if let Ok(appdata) = env::var("APPDATA") { + for exe in OPENCODE_EXECUTABLES { + candidates.push(PathBuf::from(&appdata).join("npm").join(exe)); + } + } + } - // Common Linux paths. - candidates.push(PathBuf::from("/usr/bin").join(OPENCODE_EXECUTABLE)); - candidates.push(PathBuf::from("/usr/local/bin").join(OPENCODE_EXECUTABLE)); + // Homebrew default paths (macOS/Linux). + for exe in OPENCODE_EXECUTABLES { + candidates.push(PathBuf::from("/opt/homebrew/bin").join(exe)); + candidates.push(PathBuf::from("/usr/local/bin").join(exe)); + candidates.push(PathBuf::from("/usr/bin").join(exe)); + } - candidates + candidates } fn opencode_version(program: &OsStr) -> Option { - let output = Command::new(program).arg("--version").output().ok()?; - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - - if !stdout.is_empty() { - return Some(stdout); - } - if !stderr.is_empty() { - return Some(stderr); - } - - None + let output = Command::new(program).arg("--version").output().ok()?; + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + + if !stdout.is_empty() { + return Some(stdout); + } + if !stderr.is_empty() { + return Some(stderr); + } + + None } fn opencode_supports_serve(program: &OsStr) -> bool { - Command::new(program) - .arg("serve") - .arg("--help") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map(|s| s.success()) - .unwrap_or(false) + Command::new(program) + .arg("serve") + .arg("--help") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) } fn resolve_opencode_executable() -> (Option, bool, Vec) { - let mut notes = Vec::new(); + let mut notes = Vec::new(); - if let Some(path) = resolve_in_path(OPENCODE_EXECUTABLE) { - notes.push(format!("Found in PATH: {}", path.display())); - return (Some(path), true, notes); - } + if let Some(path) = resolve_in_path(OPENCODE_EXECUTABLES) { + notes.push(format!("Found in PATH: {}", path.display())); + return (Some(path), true, notes); + } - notes.push("Not found on PATH".to_string()); + notes.push("Not found on PATH".to_string()); - for candidate in candidate_opencode_paths() { - if candidate.is_file() { - notes.push(format!("Found at {}", candidate.display())); - return (Some(candidate), false, notes); - } + for candidate in candidate_opencode_paths() { + if candidate.is_file() { + notes.push(format!("Found at {}", candidate.display())); + return (Some(candidate), false, notes); + } - notes.push(format!("Missing: {}", candidate.display())); - } + notes.push(format!("Missing: {}", candidate.display())); + } - (None, false, notes) + (None, false, notes) } fn run_capture_optional(command: &mut Command) -> Result, String> { - match command.output() { - Ok(output) => { - let status = output.status.code().unwrap_or(-1); - Ok(Some(ExecResult { - ok: output.status.success(), - status, - stdout: String::from_utf8_lossy(&output.stdout).to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - })) + match command.output() { + Ok(output) => { + let status = output.status.code().unwrap_or(-1); + Ok(Some(ExecResult { + ok: output.status.success(), + status, + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + })) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(format!( + "Failed to run {}: {e}", + command.get_program().to_string_lossy() + )), } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(e) => Err(format!( - "Failed to run {}: {e}", - command.get_program().to_string_lossy() - )), - } } fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<(), String> { - if !src.is_dir() { - return Err(format!("Source is not a directory: {}", src.display())); - } + if !src.is_dir() { + return Err(format!("Source is not a directory: {}", src.display())); + } - fs::create_dir_all(dest).map_err(|e| format!("Failed to create dir {}: {e}", dest.display()))?; + fs::create_dir_all(dest) + .map_err(|e| format!("Failed to create dir {}: {e}", dest.display()))?; - for entry in fs::read_dir(src).map_err(|e| format!("Failed to read dir {}: {e}", src.display()))? { - let entry = entry.map_err(|e| e.to_string())?; - let file_type = entry.file_type().map_err(|e| e.to_string())?; + for entry in + fs::read_dir(src).map_err(|e| format!("Failed to read dir {}: {e}", src.display()))? + { + let entry = entry.map_err(|e| e.to_string())?; + let file_type = entry.file_type().map_err(|e| e.to_string())?; - let from = entry.path(); - let to = dest.join(entry.file_name()); + let from = entry.path(); + let to = dest.join(entry.file_name()); - if file_type.is_dir() { - copy_dir_recursive(&from, &to)?; - continue; - } + if file_type.is_dir() { + copy_dir_recursive(&from, &to)?; + continue; + } - if file_type.is_file() { - fs::copy(&from, &to) - .map_err(|e| format!("Failed to copy {} -> {}: {e}", from.display(), to.display()))?; - continue; - } + if file_type.is_file() { + fs::copy(&from, &to).map_err(|e| { + format!("Failed to copy {} -> {}: {e}", from.display(), to.display()) + })?; + continue; + } - // Skip symlinks and other non-regular entries. - } + // Skip symlinks and other non-regular entries. + } - Ok(()) + Ok(()) } fn resolve_opencode_config_path(scope: &str, project_dir: &str) -> Result { - match scope { - "project" => { - if project_dir.trim().is_empty() { - return Err("projectDir is required".to_string()); - } - Ok(PathBuf::from(project_dir).join("opencode.json")) - } - "global" => { - let base = if let Ok(dir) = env::var("XDG_CONFIG_HOME") { - PathBuf::from(dir) - } else if let Ok(home) = env::var("HOME") { - PathBuf::from(home).join(".config") - } else { - return Err("Unable to resolve config directory".to_string()); - }; - - Ok(base.join("opencode").join("opencode.json")) + match scope { + "project" => { + if project_dir.trim().is_empty() { + return Err("projectDir is required".to_string()); + } + Ok(PathBuf::from(project_dir).join("opencode.json")) + } + "global" => { + let base = if let Ok(dir) = env::var("XDG_CONFIG_HOME") { + PathBuf::from(dir) + } else if let Ok(appdata) = env::var("APPDATA") { + // Windows: use %APPDATA% + PathBuf::from(appdata) + } else if let Ok(home) = env::var("HOME") { + PathBuf::from(home).join(".config") + } else if let Ok(userprofile) = env::var("USERPROFILE") { + // Windows fallback: use %USERPROFILE%\.config + PathBuf::from(userprofile).join(".config") + } else { + return Err("Unable to resolve config directory".to_string()); + }; + + Ok(base.join("opencode").join("opencode.json")) + } + _ => Err("scope must be 'project' or 'global'".to_string()), } - _ => Err("scope must be 'project' or 'global'".to_string()), - } } impl EngineManager { - fn snapshot_locked(state: &mut EngineState) -> EngineInfo { - let (running, pid) = match state.child.as_mut() { - None => (false, None), - Some(child) => match child.try_wait() { - Ok(Some(_status)) => { - // Process exited. - state.child = None; - (false, None) + fn snapshot_locked(state: &mut EngineState) -> EngineInfo { + let (running, pid) = match state.child.as_mut() { + None => (false, None), + Some(child) => match child.try_wait() { + Ok(Some(_status)) => { + // Process exited. + state.child = None; + (false, None) + } + Ok(None) => (true, Some(child.id())), + Err(_) => (true, Some(child.id())), + }, + }; + + EngineInfo { + running, + base_url: state.base_url.clone(), + project_dir: state.project_dir.clone(), + hostname: state.hostname.clone(), + port: state.port, + pid, } - Ok(None) => (true, Some(child.id())), - Err(_) => (true, Some(child.id())), - }, - }; - - EngineInfo { - running, - base_url: state.base_url.clone(), - project_dir: state.project_dir.clone(), - hostname: state.hostname.clone(), - port: state.port, - pid, } - } - fn stop_locked(state: &mut EngineState) { - if let Some(mut child) = state.child.take() { - let _ = child.kill(); - let _ = child.wait(); + fn stop_locked(state: &mut EngineState) { + if let Some(mut child) = state.child.take() { + let _ = child.kill(); + let _ = child.wait(); + } + state.base_url = None; + state.project_dir = None; + state.hostname = None; + state.port = None; } - state.base_url = None; - state.project_dir = None; - state.hostname = None; - state.port = None; - } } #[tauri::command] fn engine_info(manager: State) -> EngineInfo { - let mut state = manager.inner.lock().expect("engine mutex poisoned"); - EngineManager::snapshot_locked(&mut state) + let mut state = manager.inner.lock().expect("engine mutex poisoned"); + EngineManager::snapshot_locked(&mut state) } #[tauri::command] fn engine_stop(manager: State) -> EngineInfo { - let mut state = manager.inner.lock().expect("engine mutex poisoned"); - EngineManager::stop_locked(&mut state); - EngineManager::snapshot_locked(&mut state) + let mut state = manager.inner.lock().expect("engine mutex poisoned"); + EngineManager::stop_locked(&mut state); + EngineManager::snapshot_locked(&mut state) } #[tauri::command] fn engine_doctor() -> EngineDoctorResult { - let (resolved, in_path, notes) = resolve_opencode_executable(); - - let (version, supports_serve) = match resolved.as_ref() { - Some(path) => ( - opencode_version(path.as_os_str()), - opencode_supports_serve(path.as_os_str()), - ), - None => (None, false), - }; - - EngineDoctorResult { - found: resolved.is_some(), - in_path, - resolved_path: resolved.map(|path| path.to_string_lossy().to_string()), - version, - supports_serve, - notes, - } + let (resolved, in_path, notes) = resolve_opencode_executable(); + + let (version, supports_serve) = match resolved.as_ref() { + Some(path) => ( + opencode_version(path.as_os_str()), + opencode_supports_serve(path.as_os_str()), + ), + None => (None, false), + }; + + EngineDoctorResult { + found: resolved.is_some(), + in_path, + resolved_path: resolved.map(|path| path.to_string_lossy().to_string()), + version, + supports_serve, + notes, + } } #[tauri::command] fn engine_install() -> Result { - #[cfg(windows)] - { - return Ok(ExecResult { + #[cfg(windows)] + { + return Ok(ExecResult { ok: false, status: -1, stdout: String::new(), stderr: "Guided install is not supported on Windows yet. Install OpenCode via Scoop/Chocolatey or https://opencode.ai/install, then restart OpenWork.".to_string(), }); - } - - #[cfg(not(windows))] - { - let install_dir = home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".opencode") - .join("bin"); - - let output = Command::new("bash") - .arg("-lc") - .arg("curl -fsSL https://opencode.ai/install | bash") - .env("OPENCODE_INSTALL_DIR", install_dir) - .output() - .map_err(|e| format!("Failed to run installer: {e}"))?; - - let status = output.status.code().unwrap_or(-1); - Ok(ExecResult { - ok: output.status.success(), - status, - stdout: String::from_utf8_lossy(&output.stdout).to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - }) - } + } + + #[cfg(not(windows))] + { + let install_dir = home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".opencode") + .join("bin"); + + let output = Command::new("bash") + .arg("-lc") + .arg("curl -fsSL https://opencode.ai/install | bash") + .env("OPENCODE_INSTALL_DIR", install_dir) + .output() + .map_err(|e| format!("Failed to run installer: {e}"))?; + + let status = output.status.code().unwrap_or(-1); + Ok(ExecResult { + ok: output.status.success(), + status, + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + }) + } } #[tauri::command] fn engine_start(manager: State, project_dir: String) -> Result { - let project_dir = project_dir.trim().to_string(); - if project_dir.is_empty() { - return Err("projectDir is required".to_string()); - } - - let hostname = "127.0.0.1".to_string(); - let port = find_free_port()?; - - let mut state = manager.inner.lock().expect("engine mutex poisoned"); - - // Stop any existing engine first. - EngineManager::stop_locked(&mut state); - - let (program, _in_path, notes) = resolve_opencode_executable(); - let Some(program) = program else { - let notes_text = notes.join("\n"); - return Err(format!( - "OpenCode CLI not found.\n\nInstall with:\n- brew install anomalyco/tap/opencode\n- curl -fsSL https://opencode.ai/install | bash\n\nNotes:\n{notes_text}" - )); - }; - - let mut command = Command::new(&program); - command - .arg("serve") - .arg("--hostname") - .arg(&hostname) - .arg("--port") - .arg(port.to_string()) - // Allow the Vite dev server origin, plus common Tauri origins. - .arg("--cors") - .arg("http://localhost:5173") - .arg("--cors") - .arg("tauri://localhost") - .arg("--cors") - .arg("http://tauri.localhost") - .current_dir(&project_dir) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - - let child = command - .spawn() - .map_err(|e| format!("Failed to start opencode: {e}"))?; - - state.child = Some(child); - state.project_dir = Some(project_dir); - state.hostname = Some(hostname.clone()); - state.port = Some(port); - state.base_url = Some(format!("http://{hostname}:{port}")); - - Ok(EngineManager::snapshot_locked(&mut state)) + let project_dir = project_dir.trim().to_string(); + if project_dir.is_empty() { + return Err("projectDir is required".to_string()); + } + + let hostname = "127.0.0.1".to_string(); + let port = find_free_port()?; + + let mut state = manager.inner.lock().expect("engine mutex poisoned"); + + // Stop any existing engine first. + EngineManager::stop_locked(&mut state); + + let (program, _in_path, notes) = resolve_opencode_executable(); + let Some(program) = program else { + let notes_text = notes.join("\n"); + #[cfg(windows)] + let install_msg = "OpenCode CLI not found.\n\nInstall with:\n- npm install -g opencode-ai\n- Or download from https://opencode.ai/install\n\nAfter installing, restart OpenWork."; + #[cfg(not(windows))] + let install_msg = "OpenCode CLI not found.\n\nInstall with:\n- brew install anomalyco/tap/opencode\n- curl -fsSL https://opencode.ai/install | bash"; + return Err(format!("{install_msg}\n\nNotes:\n{notes_text}")); + }; + + let mut command = Command::new(&program); + command + .arg("serve") + .arg("--hostname") + .arg(&hostname) + .arg("--port") + .arg(port.to_string()) + // Allow the Vite dev server origin, plus common Tauri origins. + .arg("--cors") + .arg("http://localhost:5173") + .arg("--cors") + .arg("tauri://localhost") + .arg("--cors") + .arg("http://tauri.localhost") + .current_dir(&project_dir) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + let child = command + .spawn() + .map_err(|e| format!("Failed to start opencode: {e}"))?; + + state.child = Some(child); + state.project_dir = Some(project_dir); + state.hostname = Some(hostname.clone()); + state.port = Some(port); + state.base_url = Some(format!("http://{hostname}:{port}")); + + Ok(EngineManager::snapshot_locked(&mut state)) } #[tauri::command] fn opkg_install(project_dir: String, package: String) -> Result { - let project_dir = project_dir.trim().to_string(); - if project_dir.is_empty() { - return Err("projectDir is required".to_string()); - } - - let package = package.trim().to_string(); - if package.is_empty() { - return Err("package is required".to_string()); - } - - let mut opkg = Command::new("opkg"); - opkg - .arg("install") - .arg(&package) - .current_dir(&project_dir) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - if let Some(result) = run_capture_optional(&mut opkg)? { - return Ok(result); - } - - let mut openpackage = Command::new("openpackage"); - openpackage - .arg("install") - .arg(&package) - .current_dir(&project_dir) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - if let Some(result) = run_capture_optional(&mut openpackage)? { - return Ok(result); - } - - let mut pnpm = Command::new("pnpm"); - pnpm - .arg("dlx") - .arg("opkg") - .arg("install") - .arg(&package) - .current_dir(&project_dir) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - if let Some(result) = run_capture_optional(&mut pnpm)? { - return Ok(result); - } - - let mut npx = Command::new("npx"); - npx - .arg("opkg") - .arg("install") - .arg(&package) - .current_dir(&project_dir) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - if let Some(result) = run_capture_optional(&mut npx)? { - return Ok(result); - } - - Ok(ExecResult { + let project_dir = project_dir.trim().to_string(); + if project_dir.is_empty() { + return Err("projectDir is required".to_string()); + } + + let package = package.trim().to_string(); + if package.is_empty() { + return Err("package is required".to_string()); + } + + let mut opkg = Command::new("opkg"); + opkg.arg("install") + .arg(&package) + .current_dir(&project_dir) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + if let Some(result) = run_capture_optional(&mut opkg)? { + return Ok(result); + } + + let mut openpackage = Command::new("openpackage"); + openpackage + .arg("install") + .arg(&package) + .current_dir(&project_dir) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + if let Some(result) = run_capture_optional(&mut openpackage)? { + return Ok(result); + } + + let mut pnpm = Command::new("pnpm"); + pnpm.arg("dlx") + .arg("opkg") + .arg("install") + .arg(&package) + .current_dir(&project_dir) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + if let Some(result) = run_capture_optional(&mut pnpm)? { + return Ok(result); + } + + let mut npx = Command::new("npx"); + npx.arg("opkg") + .arg("install") + .arg(&package) + .current_dir(&project_dir) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + if let Some(result) = run_capture_optional(&mut npx)? { + return Ok(result); + } + + Ok(ExecResult { ok: false, status: -1, stdout: String::new(), @@ -490,104 +512,114 @@ fn opkg_install(project_dir: String, package: String) -> Result Result { - let project_dir = project_dir.trim().to_string(); - if project_dir.is_empty() { - return Err("projectDir is required".to_string()); - } - - let source_dir = source_dir.trim().to_string(); - if source_dir.is_empty() { - return Err("sourceDir is required".to_string()); - } - - let src = PathBuf::from(&source_dir); - let name = src - .file_name() - .and_then(|s| s.to_str()) - .ok_or_else(|| "Failed to infer skill name from directory".to_string())?; - - let dest = PathBuf::from(&project_dir) - .join(".opencode") - .join("skill") - .join(name); - - if dest.exists() { - if overwrite { - fs::remove_dir_all(&dest) - .map_err(|e| format!("Failed to remove existing skill dir {}: {e}", dest.display()))?; - } else { - return Err(format!("Skill already exists at {}", dest.display())); +fn import_skill( + project_dir: String, + source_dir: String, + overwrite: bool, +) -> Result { + let project_dir = project_dir.trim().to_string(); + if project_dir.is_empty() { + return Err("projectDir is required".to_string()); } - } - copy_dir_recursive(&src, &dest)?; + let source_dir = source_dir.trim().to_string(); + if source_dir.is_empty() { + return Err("sourceDir is required".to_string()); + } - Ok(ExecResult { - ok: true, - status: 0, - stdout: format!("Imported skill to {}", dest.display()), - stderr: String::new(), - }) + let src = PathBuf::from(&source_dir); + let name = src + .file_name() + .and_then(|s| s.to_str()) + .ok_or_else(|| "Failed to infer skill name from directory".to_string())?; + + let dest = PathBuf::from(&project_dir) + .join(".opencode") + .join("skill") + .join(name); + + if dest.exists() { + if overwrite { + fs::remove_dir_all(&dest).map_err(|e| { + format!( + "Failed to remove existing skill dir {}: {e}", + dest.display() + ) + })?; + } else { + return Err(format!("Skill already exists at {}", dest.display())); + } + } + + copy_dir_recursive(&src, &dest)?; + + Ok(ExecResult { + ok: true, + status: 0, + stdout: format!("Imported skill to {}", dest.display()), + stderr: String::new(), + }) } #[tauri::command] fn read_opencode_config(scope: String, project_dir: String) -> Result { - let path = resolve_opencode_config_path(scope.trim(), &project_dir)?; - let exists = path.exists(); - - let content = if exists { - Some(fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {e}", path.display()))?) - } else { - None - }; + let path = resolve_opencode_config_path(scope.trim(), &project_dir)?; + let exists = path.exists(); + + let content = if exists { + Some( + fs::read_to_string(&path) + .map_err(|e| format!("Failed to read {}: {e}", path.display()))?, + ) + } else { + None + }; - Ok(OpencodeConfigFile { - path: path.to_string_lossy().to_string(), - exists, - content, - }) + Ok(OpencodeConfigFile { + path: path.to_string_lossy().to_string(), + exists, + content, + }) } #[tauri::command] fn write_opencode_config( - scope: String, - project_dir: String, - content: String, + scope: String, + project_dir: String, + content: String, ) -> Result { - let path = resolve_opencode_config_path(scope.trim(), &project_dir)?; + let path = resolve_opencode_config_path(scope.trim(), &project_dir)?; - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .map_err(|e| format!("Failed to create config dir {}: {e}", parent.display()))?; - } + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("Failed to create config dir {}: {e}", parent.display()))?; + } - fs::write(&path, content) - .map_err(|e| format!("Failed to write {}: {e}", path.display()))?; + fs::write(&path, content).map_err(|e| format!("Failed to write {}: {e}", path.display()))?; - Ok(ExecResult { - ok: true, - status: 0, - stdout: format!("Wrote {}", path.display()), - stderr: String::new(), - }) + Ok(ExecResult { + ok: true, + status: 0, + stdout: format!("Wrote {}", path.display()), + stderr: String::new(), + }) } pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_dialog::init()) - .manage(EngineManager::default()) - .invoke_handler(tauri::generate_handler![ - engine_start, - engine_stop, - engine_info, - engine_doctor, - engine_install, - opkg_install, - import_skill, - read_opencode_config, - write_opencode_config - ]) - .run(tauri::generate_context!()) - .expect("error while running OpenWork"); + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .manage(EngineManager::default()) + .invoke_handler(tauri::generate_handler![ + engine_start, + engine_stop, + engine_info, + engine_doctor, + engine_install, + opkg_install, + import_skill, + read_opencode_config, + write_opencode_config + ]) + .run(tauri::generate_context!()) + .expect("error while running OpenWork"); } diff --git a/src/App.tsx b/src/App.tsx index a443445..24722c5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,7 +83,13 @@ type Mode = "host" | "client"; type OnboardingStep = "mode" | "host" | "client" | "connecting"; -type DashboardTab = "home" | "sessions" | "templates" | "skills" | "plugins" | "settings"; +type DashboardTab = + | "home" + | "sessions" + | "templates" + | "skills" + | "plugins" + | "settings"; type Template = { id: string; @@ -205,7 +211,9 @@ function modelEquals(a: ModelRef, b: ModelRef) { function formatModelLabel(model: ModelRef) { if (model.providerID === ZEN_PROVIDER_ID) { - const match = ZEN_MODEL_OPTIONS.find((opt) => opt.modelID === model.modelID); + const match = ZEN_MODEL_OPTIONS.find( + (opt) => opt.modelID === model.modelID, + ); return match?.label ?? `${ZEN_PROVIDER_LABEL} · ${model.modelID}`; } @@ -216,7 +224,8 @@ const CURATED_PACKAGES: CuratedPackage[] = [ { name: "OpenPackage Essentials", source: "essentials", - description: "Starter rules, commands, and skills from the OpenPackage registry.", + description: + "Starter rules, commands, and skills from the OpenPackage registry.", tags: ["registry", "starter"], installable: true, }, @@ -229,7 +238,8 @@ const CURATED_PACKAGES: CuratedPackage[] = [ }, { name: "Claude Code Commit Commands", - source: "github:anthropics/claude-code#subdirectory=plugins/commit-commands", + source: + "github:anthropics/claude-code#subdirectory=plugins/commit-commands", description: "Commit message helper commands (Claude Code plugin).", tags: ["github", "workflow"], installable: true, @@ -244,7 +254,8 @@ const CURATED_PACKAGES: CuratedPackage[] = [ { name: "Awesome Claude Skills", source: "https://github.com/ComposioHQ/awesome-claude-skills", - description: "Curated list of Claude skills and prompts (not an OpenPackage yet).", + description: + "Curated list of Claude skills and prompts (not an OpenPackage yet).", tags: ["community", "list"], installable: false, }, @@ -268,7 +279,8 @@ const SUGGESTED_PLUGINS: SuggestedPlugin[] = [ steps: [ { title: "Run the installer", - description: "Installs the extension + native host and prepares the local broker.", + description: + "Installs the extension + native host and prepares the local broker.", command: "bunx @different-ai/opencode-browser@latest install", note: "Use npx @different-ai/opencode-browser@latest install if you do not have bunx.", }, @@ -277,7 +289,9 @@ const SUGGESTED_PLUGINS: SuggestedPlugin[] = [ description: "Open chrome://extensions, enable Developer mode, click Load unpacked, and select the extension folder.", url: "chrome://extensions", - path: "~/.opencode-browser/extension", + path: navigator.platform.includes("Win") + ? "%USERPROFILE%\\.opencode-browser\\extension" + : "~/.opencode-browser/extension", }, { title: "Pin the extension", @@ -285,14 +299,17 @@ const SUGGESTED_PLUGINS: SuggestedPlugin[] = [ }, { title: "Add plugin to config", - description: "Click Add to write @different-ai/opencode-browser into opencode.json.", + description: + "Click Add to write @different-ai/opencode-browser into opencode.json.", }, ], }, ]; function isTauriRuntime() { - return typeof window !== "undefined" && (window as any).__TAURI_INTERNALS__ != null; + return ( + typeof window !== "undefined" && (window as any).__TAURI_INTERNALS__ != null + ); } function readModePreference(): Mode | null { @@ -470,7 +487,11 @@ function upsertPart(list: MessageWithParts[], nextPart: Part) { return copy; } -function removePart(list: MessageWithParts[], messageID: string, partID: string) { +function removePart( + list: MessageWithParts[], + messageID: string, + partID: string, +) { const msgIdx = list.findIndex((m) => m.info.id === messageID); if (msgIdx === -1) return list; @@ -499,7 +520,8 @@ function modelFromUserMessage(info: Message): ModelRef | null { const providerID = (model as any).providerID; const modelID = (model as any).modelID; - if (typeof providerID !== "string" || typeof modelID !== "string") return null; + if (typeof providerID !== "string" || typeof modelID !== "string") + return null; return { providerID, modelID }; } @@ -515,14 +537,20 @@ function lastUserModelFromMessages(list: MessageWithParts[]): ModelRef | null { export default function App() { const [view, setView] = createSignal("onboarding"); const [mode, setMode] = createSignal(null); - const [onboardingStep, setOnboardingStep] = createSignal("mode"); + const [onboardingStep, setOnboardingStep] = + createSignal("mode"); const [rememberModeChoice, setRememberModeChoice] = createSignal(false); const [tab, setTab] = createSignal("home"); const [engine, setEngine] = createSignal(null); - const [engineDoctorResult, setEngineDoctorResult] = createSignal(null); - const [engineDoctorCheckedAt, setEngineDoctorCheckedAt] = createSignal(null); - const [engineInstallLogs, setEngineInstallLogs] = createSignal(null); + const [engineDoctorResult, setEngineDoctorResult] = + createSignal(null); + const [engineDoctorCheckedAt, setEngineDoctorCheckedAt] = createSignal< + number | null + >(null); + const [engineInstallLogs, setEngineInstallLogs] = createSignal( + null, + ); const [projectDir, setProjectDir] = createSignal(""); const [authorizedDirs, setAuthorizedDirs] = createSignal([]); @@ -532,18 +560,26 @@ export default function App() { const [clientDirectory, setClientDirectory] = createSignal(""); const [client, setClient] = createSignal(null); - const [connectedVersion, setConnectedVersion] = createSignal(null); + const [connectedVersion, setConnectedVersion] = createSignal( + null, + ); const [sseConnected, setSseConnected] = createSignal(false); const [sessions, setSessions] = createSignal([]); - const [selectedSessionId, setSelectedSessionId] = createSignal(null); - const [sessionStatusById, setSessionStatusById] = createSignal>({}); + const [selectedSessionId, setSelectedSessionId] = createSignal( + null, + ); + const [sessionStatusById, setSessionStatusById] = createSignal< + Record + >({}); const [messages, setMessages] = createSignal([]); const [todos, setTodos] = createSignal< Array<{ id: string; content: string; status: string; priority: string }> >([]); - const [pendingPermissions, setPendingPermissions] = createSignal([]); + const [pendingPermissions, setPendingPermissions] = createSignal< + PendingPermission[] + >([]); const [permissionReplyBusy, setPermissionReplyBusy] = createSignal(false); const [prompt, setPrompt] = createSignal(""); @@ -552,7 +588,8 @@ export default function App() { const [templates, setTemplates] = createSignal([]); const [templateModalOpen, setTemplateModalOpen] = createSignal(false); const [templateDraftTitle, setTemplateDraftTitle] = createSignal(""); - const [templateDraftDescription, setTemplateDraftDescription] = createSignal(""); + const [templateDraftDescription, setTemplateDraftDescription] = + createSignal(""); const [templateDraftPrompt, setTemplateDraftPrompt] = createSignal(""); const [skills, setSkills] = createSignal([]); @@ -561,20 +598,29 @@ export default function App() { const [packageSearch, setPackageSearch] = createSignal(""); const [pluginScope, setPluginScope] = createSignal("project"); - const [pluginConfig, setPluginConfig] = createSignal(null); + const [pluginConfig, setPluginConfig] = + createSignal(null); const [pluginList, setPluginList] = createSignal([]); const [pluginInput, setPluginInput] = createSignal(""); const [pluginStatus, setPluginStatus] = createSignal(null); - const [activePluginGuide, setActivePluginGuide] = createSignal(null); + const [activePluginGuide, setActivePluginGuide] = createSignal( + null, + ); const [events, setEvents] = createSignal([]); const [developerMode, setDeveloperMode] = createSignal(false); const [defaultModel, setDefaultModel] = createSignal(DEFAULT_MODEL); const [modelPickerOpen, setModelPickerOpen] = createSignal(false); - const [modelPickerTarget, setModelPickerTarget] = createSignal<"session" | "default">("session"); - const [sessionModelOverrideById, setSessionModelOverrideById] = createSignal>({}); - const [sessionModelById, setSessionModelById] = createSignal>({}); + const [modelPickerTarget, setModelPickerTarget] = createSignal< + "session" | "default" + >("session"); + const [sessionModelOverrideById, setSessionModelOverrideById] = createSignal< + Record + >({}); + const [sessionModelById, setSessionModelById] = createSignal< + Record + >({}); const [busy, setBusy] = createSignal(false); const [busyLabel, setBusyLabel] = createSignal(null); @@ -593,7 +639,12 @@ export default function App() { if (busy() && label === "Running") return false; // Otherwise, block during engine / connection transitions. - if (busy() && (label === "Connecting" || label === "Starting engine" || label === "Disconnecting")) { + if ( + busy() && + (label === "Connecting" || + label === "Starting engine" || + label === "Disconnecting") + ) { return true; } @@ -605,7 +656,12 @@ export default function App() { if (!query) return CURATED_PACKAGES; return CURATED_PACKAGES.filter((pkg) => { - const haystack = [pkg.name, pkg.source, pkg.description, pkg.tags.join(" ")] + const haystack = [ + pkg.name, + pkg.source, + pkg.description, + pkg.tags.join(" "), + ] .join(" ") .toLowerCase(); return haystack.includes(query); @@ -663,7 +719,9 @@ export default function App() { const isPluginInstalled = (pluginName: string, aliases: string[] = []) => { const list = pluginNamesLower(); - return [pluginName, ...aliases].some((entry) => list.has(entry.toLowerCase())); + return [pluginName, ...aliases].some((entry) => + list.has(entry.toLowerCase()), + ); }; const loadPluginsFromConfig = (config: OpencodeConfigFile | null) => { @@ -673,12 +731,16 @@ export default function App() { } try { - const parsed = parse(config.content) as Record | undefined; + const parsed = parse(config.content) as + | Record + | undefined; const next = normalizePluginList(parsed?.plugin); setPluginList(next); } catch (e) { setPluginList([]); - setPluginStatus(e instanceof Error ? e.message : "Failed to parse opencode.json"); + setPluginStatus( + e instanceof Error ? e.message : "Failed to parse opencode.json", + ); } }; @@ -710,7 +772,9 @@ export default function App() { return defaultModel(); }); - const selectedSessionModelLabel = createMemo(() => formatModelLabel(selectedSessionModel())); + const selectedSessionModelLabel = createMemo(() => + formatModelLabel(selectedSessionModel()), + ); const modelPickerCurrent = createMemo(() => modelPickerTarget() === "default" ? defaultModel() : selectedSessionModel(), @@ -797,7 +861,10 @@ export default function App() { setPendingPermissions((current) => { const now = Date.now(); const byId = new Map(current.map((p) => [p.id, p] as const)); - return list.map((p) => ({ ...p, receivedAt: byId.get(p.id)?.receivedAt ?? now })); + return list.map((p) => ({ + ...p, + receivedAt: byId.get(p.id)?.receivedAt ?? now, + })); }); } @@ -857,14 +924,19 @@ export default function App() { setEngineDoctorCheckedAt(Date.now()); if (!result.found) { + const isWindows = navigator.platform.includes("Win"); setError( - "OpenCode CLI not found. Install with `brew install anomalyco/tap/opencode` or `curl -fsSL https://opencode.ai/install | bash`, then retry.", + isWindows + ? "OpenCode CLI not found. Install with `npm install -g opencode-ai` or download from https://opencode.ai/install, then retry." + : "OpenCode CLI not found. Install with `brew install anomalyco/tap/opencode` or `curl -fsSL https://opencode.ai/install | bash`, then retry.", ); return false; } if (!result.supportsServe) { - setError("OpenCode CLI is installed, but `opencode serve` is unavailable. Update OpenCode and retry."); + setError( + "OpenCode CLI is installed, but `opencode serve` is unavailable. Update OpenCode and retry.", + ); return false; } } catch (e) { @@ -881,7 +953,10 @@ export default function App() { setEngine(info); if (info.baseUrl) { - const ok = await connectToServer(info.baseUrl, info.projectDir ?? undefined); + const ok = await connectToServer( + info.baseUrl, + info.projectDir ?? undefined, + ); if (!ok) return false; } @@ -1189,7 +1264,9 @@ export default function App() { if (!config.exists) { setPluginList([]); - setPluginStatus("No opencode.json found yet. Add a plugin to create one."); + setPluginStatus( + "No opencode.json found yet. Add a plugin to create one.", + ); return; } @@ -1197,7 +1274,9 @@ export default function App() { } catch (e) { setPluginConfig(null); setPluginList([]); - setPluginStatus(e instanceof Error ? e.message : "Failed to load opencode.json"); + setPluginStatus( + e instanceof Error ? e.message : "Failed to load opencode.json", + ); } } @@ -1235,7 +1314,11 @@ export default function App() { $schema: "https://opencode.ai/config.json", plugin: [pluginName], }; - await writeOpencodeConfig(scope, targetDir, `${JSON.stringify(payload, null, 2)}\n`); + await writeOpencodeConfig( + scope, + targetDir, + `${JSON.stringify(payload, null, 2)}\n`, + ); if (isManualInput) { setPluginInput(""); } @@ -1247,7 +1330,11 @@ export default function App() { const plugins = normalizePluginList(parsed?.plugin); const desired = stripPluginVersion(pluginName).toLowerCase(); - if (plugins.some((entry) => stripPluginVersion(entry).toLowerCase() === desired)) { + if ( + plugins.some( + (entry) => stripPluginVersion(entry).toLowerCase() === desired, + ) + ) { setPluginStatus("Plugin already listed in opencode.json."); return; } @@ -1264,7 +1351,9 @@ export default function App() { } await refreshPlugins(scope); } catch (e) { - setPluginStatus(e instanceof Error ? e.message : "Failed to update opencode.json"); + setPluginStatus( + e instanceof Error ? e.message : "Failed to update opencode.json", + ); } } @@ -1283,7 +1372,9 @@ export default function App() { } if (!pkg) { - setError("Enter an OpenPackage source (e.g. github:anthropics/claude-code)."); + setError( + "Enter an OpenPackage source (e.g. github:anthropics/claude-code).", + ); return; } @@ -1295,7 +1386,9 @@ export default function App() { try { const result = await opkgInstall(targetDir, pkg); if (!result.ok) { - setSkillsStatus(result.stderr || result.stdout || `opkg failed (${result.status})`); + setSkillsStatus( + result.stderr || result.stdout || `opkg failed (${result.status})`, + ); } else { setSkillsStatus(result.stdout || "Installed."); } @@ -1339,15 +1432,23 @@ export default function App() { try { const selection = await pickDirectory({ title: "Select skill folder" }); const sourceDir = - typeof selection === "string" ? selection : Array.isArray(selection) ? selection[0] : null; + typeof selection === "string" + ? selection + : Array.isArray(selection) + ? selection[0] + : null; if (!sourceDir) { return; } - const result = await importSkill(targetDir, sourceDir, { overwrite: false }); + const result = await importSkill(targetDir, sourceDir, { + overwrite: false, + }); if (!result.ok) { - setSkillsStatus(result.stderr || result.stdout || `Import failed (${result.status})`); + setSkillsStatus( + result.stderr || result.stdout || `Import failed (${result.status})`, + ); } else { setSkillsStatus(result.stdout || "Imported."); } @@ -1360,7 +1461,10 @@ export default function App() { } } - async function respondPermission(requestID: string, reply: "once" | "always" | "reject") { + async function respondPermission( + requestID: string, + reply: "once" | "always" | "reject", + ) { const c = client(); if (!c || permissionReplyBusy()) return; @@ -1405,25 +1509,35 @@ export default function App() { setBaseUrl(storedBaseUrl); } - const storedClientDir = window.localStorage.getItem("openwork.clientDirectory"); + const storedClientDir = window.localStorage.getItem( + "openwork.clientDirectory", + ); if (storedClientDir) { setClientDirectory(storedClientDir); } - const storedProjectDir = window.localStorage.getItem("openwork.projectDir"); + const storedProjectDir = window.localStorage.getItem( + "openwork.projectDir", + ); if (storedProjectDir) { setProjectDir(storedProjectDir); } - const storedAuthorized = window.localStorage.getItem("openwork.authorizedDirs"); + const storedAuthorized = window.localStorage.getItem( + "openwork.authorizedDirs", + ); if (storedAuthorized) { const parsed = JSON.parse(storedAuthorized) as unknown; - if (Array.isArray(parsed) && parsed.every((v) => typeof v === "string")) { + if ( + Array.isArray(parsed) && + parsed.every((v) => typeof v === "string") + ) { setAuthorizedDirs(parsed); } } - const storedTemplates = window.localStorage.getItem("openwork.templates"); + const storedTemplates = + window.localStorage.getItem("openwork.templates"); if (storedTemplates) { const parsed = JSON.parse(storedTemplates) as unknown; if (Array.isArray(parsed)) { @@ -1438,7 +1552,10 @@ export default function App() { } else { setDefaultModel(DEFAULT_MODEL); try { - window.localStorage.setItem(MODEL_PREF_KEY, formatModelRef(DEFAULT_MODEL)); + window.localStorage.setItem( + MODEL_PREF_KEY, + formatModelRef(DEFAULT_MODEL), + ); } catch { // ignore } @@ -1464,7 +1581,10 @@ export default function App() { if (info?.running && info.baseUrl) { setOnboardingStep("connecting"); - const ok = await connectToServer(info.baseUrl, info.projectDir ?? undefined); + const ok = await connectToServer( + info.baseUrl, + info.projectDir ?? undefined, + ); if (!ok) { setMode(null); setOnboardingStep("mode"); @@ -1526,7 +1646,10 @@ export default function App() { createEffect(() => { if (typeof window === "undefined") return; try { - window.localStorage.setItem("openwork.clientDirectory", clientDirectory()); + window.localStorage.setItem( + "openwork.clientDirectory", + clientDirectory(), + ); } catch { // ignore } @@ -1544,7 +1667,10 @@ export default function App() { createEffect(() => { if (typeof window === "undefined") return; try { - window.localStorage.setItem("openwork.authorizedDirs", JSON.stringify(authorizedDirs())); + window.localStorage.setItem( + "openwork.authorizedDirs", + JSON.stringify(authorizedDirs()), + ); } catch { // ignore } @@ -1553,7 +1679,10 @@ export default function App() { createEffect(() => { if (typeof window === "undefined") return; try { - window.localStorage.setItem("openwork.templates", JSON.stringify(templates())); + window.localStorage.setItem( + "openwork.templates", + JSON.stringify(templates()), + ); } catch { // ignore } @@ -1562,7 +1691,10 @@ export default function App() { createEffect(() => { if (typeof window === "undefined") return; try { - window.localStorage.setItem(MODEL_PREF_KEY, formatModelRef(defaultModel())); + window.localStorage.setItem( + MODEL_PREF_KEY, + formatModelRef(defaultModel()), + ); } catch { // ignore } @@ -1577,7 +1709,9 @@ export default function App() { (async () => { try { - const sub = await c.event.subscribe(undefined, { signal: controller.signal }); + const sub = await c.event.subscribe(undefined, { + signal: controller.signal, + }); for await (const raw of sub.stream) { if (cancelled) break; @@ -1591,16 +1725,24 @@ export default function App() { if (developerMode()) { setEvents((current) => { - const next = [{ type: event.type, properties: event.properties }, ...current]; + const next = [ + { type: event.type, properties: event.properties }, + ...current, + ]; return next.slice(0, 150); }); } - if (event.type === "session.updated" || event.type === "session.created") { + if ( + event.type === "session.updated" || + event.type === "session.created" + ) { if (event.properties && typeof event.properties === "object") { const record = event.properties as Record; if (record.info && typeof record.info === "object") { - setSessions((current) => upsertSession(current, record.info as Session)); + setSessions((current) => + upsertSession(current, record.info as Session), + ); } } } @@ -1610,7 +1752,9 @@ export default function App() { const record = event.properties as Record; const info = record.info as Session | undefined; if (info?.id) { - setSessions((current) => current.filter((s) => s.id !== info.id)); + setSessions((current) => + current.filter((s) => s.id !== info.id), + ); } } } @@ -1618,7 +1762,8 @@ export default function App() { if (event.type === "session.status") { if (event.properties && typeof event.properties === "object") { const record = event.properties as Record; - const sessionID = typeof record.sessionID === "string" ? record.sessionID : null; + const sessionID = + typeof record.sessionID === "string" ? record.sessionID : null; if (sessionID) { setSessionStatusById((current) => ({ ...current, @@ -1631,7 +1776,8 @@ export default function App() { if (event.type === "session.idle") { if (event.properties && typeof event.properties === "object") { const record = event.properties as Record; - const sessionID = typeof record.sessionID === "string" ? record.sessionID : null; + const sessionID = + typeof record.sessionID === "string" ? record.sessionID : null; if (sessionID) { setSessionStatusById((current) => ({ ...current, @@ -1662,7 +1808,10 @@ export default function App() { }); } - if (selectedSessionId() && info.sessionID === selectedSessionId()) { + if ( + selectedSessionId() && + info.sessionID === selectedSessionId() + ) { setMessages((current) => upsertMessage(current, info)); } } @@ -1677,7 +1826,9 @@ export default function App() { record.sessionID === selectedSessionId() && typeof record.messageID === "string" ) { - setMessages((current) => current.filter((m) => m.info.id !== record.messageID)); + setMessages((current) => + current.filter((m) => m.info.id !== record.messageID), + ); } } } @@ -1687,7 +1838,10 @@ export default function App() { const record = event.properties as Record; if (record.part && typeof record.part === "object") { const part = record.part as Part; - if (selectedSessionId() && part.sessionID === selectedSessionId()) { + if ( + selectedSessionId() && + part.sessionID === selectedSessionId() + ) { setMessages((current) => upsertPart(current, part)); } } @@ -1697,19 +1851,34 @@ export default function App() { if (event.type === "message.part.removed") { if (event.properties && typeof event.properties === "object") { const record = event.properties as Record; - const sessionID = typeof record.sessionID === "string" ? record.sessionID : null; - const messageID = typeof record.messageID === "string" ? record.messageID : null; - const partID = typeof record.partID === "string" ? record.partID : null; + const sessionID = + typeof record.sessionID === "string" ? record.sessionID : null; + const messageID = + typeof record.messageID === "string" ? record.messageID : null; + const partID = + typeof record.partID === "string" ? record.partID : null; - if (sessionID && selectedSessionId() && sessionID === selectedSessionId() && messageID && partID) { - setMessages((current) => removePart(current, messageID, partID)); + if ( + sessionID && + selectedSessionId() && + sessionID === selectedSessionId() && + messageID && + partID + ) { + setMessages((current) => + removePart(current, messageID, partID), + ); } } } if (event.type === "todo.updated") { const id = selectedSessionId(); - if (id && event.properties && typeof event.properties === "object") { + if ( + id && + event.properties && + typeof event.properties === "object" + ) { const record = event.properties as Record; if (record.sessionID === id && Array.isArray(record.todos)) { setTodos(record.todos as any); @@ -1717,7 +1886,10 @@ export default function App() { } } - if (event.type === "permission.asked" || event.type === "permission.replied") { + if ( + event.type === "permission.asked" || + event.type === "permission.replied" + ) { try { await refreshPendingPermissions(c); } catch { @@ -1783,10 +1955,14 @@ export default function App() {

- {mode() === "host" ? "Starting OpenCode Engine..." : "Searching for Host..."} + {mode() === "host" + ? "Starting OpenCode Engine..." + : "Searching for Host..."}

- {mode() === "host" ? `Initializing ${localHostLabel()}` : "Verifying secure handshake"} + {mode() === "host" + ? `Initializing ${localHostLabel()}` + : "Verifying secure handshake"}

@@ -1802,16 +1978,21 @@ export default function App() {
-

Authorized Workspaces

+

+ Authorized Workspaces +

- OpenWork runs locally. Select which folders it is allowed to access. + OpenWork runs locally. Select which folders it is allowed to + access.

-
Project folder
+
+ Project folder +
{ try { - const selection = await pickDirectory({ title: "Select project folder" }); + const selection = await pickDirectory({ + title: "Select project folder", + }); const path = typeof selection === "string" ? selection @@ -1836,7 +2019,9 @@ export default function App() { setProjectDir(path); } } catch (e) { - setError(e instanceof Error ? e.message : "Unknown error"); + setError( + e instanceof Error ? e.message : "Unknown error", + ); } }} disabled={busy()} @@ -1858,7 +2043,9 @@ export default function App() {
- {folder} + + {folder} +
- +
-
Install one of these:
-
- brew install anomalyco/tap/opencode +
+ Install one of these:
+ +
+ npm install -g opencode-ai +
+
+ +
+ brew install anomalyco/tap/opencode +
+
curl -fsSL https://opencode.ai/install | bash
@@ -1986,18 +2201,24 @@ export default function App() { try { const result = await engineInstall(); - const combined = `${result.stdout}${result.stderr ? `\n${result.stderr}` : ""}`.trim(); + const combined = + `${result.stdout}${result.stderr ? `\n${result.stderr}` : ""}`.trim(); setEngineInstallLogs(combined || null); if (!result.ok) { setError( - result.stderr.trim() || "OpenCode install failed. See logs above.", + result.stderr.trim() || + "OpenCode install failed. See logs above.", ); } await refreshEngineDoctor(); } catch (e) { - setError(e instanceof Error ? e.message : safeStringify(e)); + setError( + e instanceof Error + ? e.message + : safeStringify(e), + ); } finally { setBusy(false); setBusyLabel(null); @@ -2011,7 +2232,8 @@ export default function App() {
@@ -2094,7 +2321,9 @@ export default function App() {
-

Connect to Host

+

+ Connect to Host +

Pair with an existing OpenCode server (LAN or tunnel).

@@ -2122,7 +2351,9 @@ export default function App() { const ok = await connectToServer( baseUrl().trim(), - clientDirectory().trim() ? clientDirectory().trim() : undefined, + clientDirectory().trim() + ? clientDirectory().trim() + : undefined, ); if (!ok) { @@ -2169,7 +2400,9 @@ export default function App() {

OpenWork

-

How would you like to run OpenWork today?

+

+ How would you like to run OpenWork today? +

@@ -2187,7 +2420,9 @@ export default function App() {
-

Start Host Engine

+

+ Start Host Engine +

Run OpenCode locally. Best for your primary computer.

@@ -2204,7 +2439,9 @@ export default function App() {
-
Engine already running
+
+ Engine already running +
{engine()?.baseUrl}
@@ -2272,7 +2509,9 @@ export default function App() {
-
{headerStatus()}
+
+ {headerStatus()} +
@@ -2315,7 +2554,9 @@ export default function App() { return ( )} @@ -2390,14 +2637,18 @@ export default function App() {
-

Recent Sessions

+

+ Recent Sessions +

{(s, idx) => (
-
{s.title}
+
+ {s.title} +
- {formatRelativeTime(s.time.updated)} + {" "} + {formatRelativeTime(s.time.updated)}
@@ -2436,14 +2690,18 @@ export default function App() {
-

All Sessions

+

+ All Sessions +

{(s, idx) => (
-
{s.title}
+
+ {s.title} +
- {formatRelativeTime(s.time.updated)} + {" "} + {formatRelativeTime(s.time.updated)}
@@ -2482,7 +2743,9 @@ export default function App() {
-

Templates

+

+ Templates +

-
@@ -2538,15 +2815,23 @@ export default function App() {
-

Skills

-
-
Install from OpenPackage
+
+ Install from OpenPackage +
Host mode only
@@ -2568,11 +2853,14 @@ export default function App() {
- Installs OpenPackage packages into the current workspace. Skills should land in `.opencode/skill`. + Installs OpenPackage packages into the current workspace. Skills + should land in `.opencode/skill`.
-
Import local skill
+
+ Import local skill +
-
@@ -2739,21 +3051,31 @@ export default function App() {
-
Suggested plugins
+
+ Suggested plugins +
{(plugin) => { const isGuided = () => plugin.installMode === "guided"; const isInstalled = () => - isPluginInstalled(plugin.packageName, plugin.aliases ?? []); - const isGuideOpen = () => activePluginGuide() === plugin.packageName; + isPluginInstalled( + plugin.packageName, + plugin.aliases ?? [], + ); + const isGuideOpen = () => + activePluginGuide() === plugin.packageName; return (
-
{plugin.name}
-
{plugin.description}
+
+ {plugin.name} +
+
+ {plugin.description} +
{plugin.packageName} @@ -2765,20 +3087,25 @@ export default function App() {
@@ -2845,8 +3182,12 @@ export default function App() { {(pluginName) => (
-
{pluginName}
-
Enabled
+
+ {pluginName} +
+
+ Enabled +
)}
@@ -2888,7 +3229,10 @@ export default function App() {
{headerStatus()}
{baseUrl()}
- @@ -2898,7 +3242,11 @@ export default function App() { - @@ -2907,12 +3255,18 @@ export default function App() {
Model
-
Default model for new sessions.
+
+ Default model for new sessions. +
-
{formatModelLabel(defaultModel())}
-
{formatModelRef(defaultModel())}
+
+ {formatModelLabel(defaultModel())} +
+
+ {formatModelRef(defaultModel())} +
- {mode()} mode + + {mode()} mode +
-
@@ -2956,21 +3320,29 @@ export default function App() { }} > Reset default startup mode - +

- This clears your saved preference and shows mode selection on next launch. + This clears your saved preference and shows mode selection on + next launch.

-

Developer

+

+ Developer +

-
Pending permissions
+
+ Pending permissions +
                       {safeStringify(pendingPermissions())}
                     
@@ -3013,7 +3385,11 @@ export default function App() {
- {mode() === "host" ? : } + {mode() === "host" ? ( + + ) : ( + + )} {mode() === "host" ? "Local Engine" : "Client Mode"}
@@ -3028,17 +3404,29 @@ export default function App() { {client() ? "Connected" : "Disconnected"}
-
{baseUrl()}
+
+ {baseUrl()} +
- - @@ -3059,7 +3447,11 @@ export default function App() {
- @@ -3079,13 +3471,18 @@ export default function App() { New -
-
{content()}
+
+ {content()} +
@@ -3200,7 +3597,9 @@ export default function App() {
-

{selectedSession()?.title ?? "Session"}

+

+ {selectedSession()?.title ?? "Session"} +

- -
@@ -3247,7 +3655,8 @@ export default function App() {

Ready to work

- Describe a task. I’ll show progress and ask for permissions when needed. + Describe a task. I’ll show progress and ask for + permissions when needed.

@@ -3260,7 +3669,10 @@ export default function App() { return developerMode(); } - if (p.type === "step-start" || p.type === "step-finish") { + if ( + p.type === "step-start" || + p.type === "step-finish" + ) { // Too noisy for normal users. return developerMode(); } @@ -3274,7 +3686,9 @@ export default function App() { return ( 0}> -
+
{(p, idx) => ( -
+
)} @@ -3308,7 +3732,8 @@ export default function App() {
Execution Plan - {todos().filter((t) => t.status === "completed").length}/{todos().length} + {todos().filter((t) => t.status === "completed").length}/ + {todos().length}
@@ -3326,7 +3751,9 @@ export default function App() {
@@ -3395,7 +3822,9 @@ export default function App() { sendPrompt().catch(() => undefined); } }} - placeholder={busy() ? "Working..." : "Ask OpenWork to do something..."} + placeholder={ + busy() ? "Working..." : "Ask OpenWork to do something..." + } class="w-full bg-zinc-900 border border-zinc-800 rounded-2xl py-4 pl-5 pr-14 text-white placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-zinc-600 focus:border-zinc-600 transition-all disabled:opacity-50" />
-

Permission Required

+

+ Permission Required +

OpenCode is requesting permission to continue.

@@ -3429,7 +3860,9 @@ export default function App() {
Permission
-
{activePermission()!.permission}
+
+ {activePermission()!.permission} +
Scope @@ -3439,9 +3872,16 @@ export default function App() { {activePermission()!.patterns.join(", ")}
- 0}> + + 0 + } + >
- Details + + Details +
                           {safeStringify(activePermission()!.metadata)}
                         
@@ -3453,7 +3893,9 @@ export default function App() {
-
{opt.description}
+
+ {opt.description} +
{formatModelRef(opt)}
- }> - + } + > +
@@ -3569,7 +4027,10 @@ export default function App() {
-
@@ -3584,8 +4045,12 @@ export default function App() {
-

Save Template

-

Reuse a workflow with one tap.

+

+ Save Template +

+

+ Reuse a workflow with one tap. +