mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
chore: package the goose binary in the goose2 tauri app (#8615)
Co-authored-by: Lifei Zhou <lifei@squareup.com>
This commit is contained in:
parent
bd14186214
commit
75a41a34dc
21 changed files with 857 additions and 519 deletions
8
.github/workflows/goose2-ci.yml
vendored
8
.github/workflows/goose2-ci.yml
vendored
|
|
@ -111,6 +111,10 @@ jobs:
|
|||
- name: Build frontend
|
||||
run: pnpm build
|
||||
|
||||
- name: Mock goose binary
|
||||
working-directory: .
|
||||
run: mkdir -p target/release && touch target/release/goose-$(rustc --print host-tuple)
|
||||
|
||||
- name: Check Tauri
|
||||
run: cd src-tauri && cargo check
|
||||
|
||||
|
|
@ -167,6 +171,10 @@ jobs:
|
|||
ui/goose2/src-tauri/target
|
||||
key: ${{ runner.os }}-goose2-cargo-${{ hashFiles('ui/goose2/src-tauri/Cargo.lock') }}
|
||||
|
||||
- name: Mock goose binary
|
||||
working-directory: .
|
||||
run: mkdir -p target/release && touch target/release/goose-$(rustc --print host-tuple)
|
||||
|
||||
- name: Format check
|
||||
run: cd src-tauri && cargo fmt --check
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
members = [
|
||||
"crates/*",
|
||||
# Mainly for cargo-machete to not error out during inspection.
|
||||
"vendor/v8"
|
||||
"vendor/v8",
|
||||
]
|
||||
exclude = ["ui/goose2/src-tauri"]
|
||||
resolver = "2"
|
||||
|
|
|
|||
5
Justfile
5
Justfile
|
|
@ -484,3 +484,8 @@ build-test-tools:
|
|||
record-mcp-tests: build-test-tools
|
||||
GOOSE_RECORD_MCP=1 cargo test --package goose --test mcp_integration_test
|
||||
git add crates/goose/tests/mcp_replays/
|
||||
|
||||
bundle-goose2:
|
||||
cargo build --release --package goose-cli --bin goose
|
||||
cp target/release/goose target/release/goose-$(rustc --print host-tuple)
|
||||
@just goose2::bundle
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ Goose2 is a Tauri 2 + React 19 desktop app.
|
|||
bash/zsh: `source ./bin/activate-hermit`
|
||||
fish: `source ./bin/activate-hermit.fish`
|
||||
2. Install git hooks: `lefthook install`
|
||||
3. Install dependencies: `just setup`
|
||||
3. Prepare workspace dependencies: `just setup`
|
||||
4. Start the app: `just dev`
|
||||
|
||||
`just clean` removes Rust build artifacts, `dist`, and `node_modules`. Run `just setup` again before `just dev`.
|
||||
|
||||
`just setup` bootstraps a shared managed goose checkout in a home-level cache directory when it does not exist, fast-forwards it, builds a local `goose` binary, and stamps the exact branch/commit it used. `just dev` only does a lightweight preflight against that shared stamp; if the managed checkout is missing, stale, or built from the wrong branch, it warns and tells you to rerun `just setup`. By default the helper uses `~/Library/Caches/goose2-dev` on macOS, or `$XDG_CACHE_HOME/goose2-dev` / `~/.cache/goose2-dev` elsewhere. It prefers `origin/baxen/goose2` and falls back to `origin/main` when that branch does not exist yet.
|
||||
|
||||
Override the shared cache root or branch with `GOOSE_DEV_ROOT=/path/to/cache` and `GOOSE_DEV_BRANCH=my/integration-branch`. You can also override the checkout path directly with `GOOSE_DEV_REPO=/path/to/goose`, or the clone source with `GOOSE_DEV_CLONE_URL=...`.
|
||||
`just setup` installs UI workspace dependencies, builds the SDK package, and builds the local debug `goose` CLI binary. `just dev` exports `GOOSE_BIN` to that local binary and loads `src-tauri/tauri.dev.conf.json`, which clears the production `externalBin` requirement during development.
|
||||
|
||||
Run `just` to list available commands, or see [justfile](./justfile) for the full recipe definitions.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ default:
|
|||
|
||||
# Install dependencies and build workspace packages
|
||||
setup:
|
||||
pnpm install
|
||||
cd ../ && pnpm install
|
||||
cd ../sdk && pnpm build
|
||||
cd src-tauri && cargo build
|
||||
cargo build --manifest-path ../../Cargo.toml -p goose-cli --bin goose
|
||||
|
||||
# ── Build & Check ────────────────────────────────────────────
|
||||
|
||||
|
|
@ -50,6 +50,9 @@ tauri-check:
|
|||
# Full CI gate
|
||||
ci: check clippy test build tauri-check
|
||||
|
||||
bundle:
|
||||
pnpm tauri build
|
||||
|
||||
# ── Test ─────────────────────────────────────────────────────
|
||||
|
||||
# Run unit/component tests
|
||||
|
|
@ -82,7 +85,9 @@ dev:
|
|||
VITE_PORT={{ vite_port }}
|
||||
export VITE_PORT
|
||||
PROJECT_DIR=$(pwd)
|
||||
TAURI_CONFIG="{\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"cd ${PROJECT_DIR} && exec pnpm exec vite --port ${VITE_PORT} --strictPort\",\"cwd\":\".\",\"wait\":false}}}"
|
||||
GOOSE_BIN="${PROJECT_DIR}/../../target/debug/goose"
|
||||
export GOOSE_BIN
|
||||
EXTRA_CONFIG_ARGS=(--config "{\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"cd ${PROJECT_DIR} && exec pnpm exec vite --port ${VITE_PORT} --strictPort\",\"cwd\":\".\",\"wait\":false}}}")
|
||||
|
||||
# In worktrees, generate a labeled icon so you can tell instances apart
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
|
|
@ -97,12 +102,12 @@ dev:
|
|||
|
||||
if swift scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$WORKTREE_LABEL"; then
|
||||
echo "🌳 Worktree: ${WORKTREE_LABEL}"
|
||||
TAURI_CONFIG=$(python3 -c "import json,sys; a=json.loads(sys.argv[1]); a['bundle']={'icon':['$DEV_ICON']}; print(json.dumps(a))" "$TAURI_CONFIG")
|
||||
EXTRA_CONFIG_ARGS+=(--config "{\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
pnpm tauri dev --features app-test-driver --config "$TAURI_CONFIG"
|
||||
pnpm tauri dev --features app-test-driver --config src-tauri/tauri.dev.conf.json "${EXTRA_CONFIG_ARGS[@]}"
|
||||
|
||||
# Start the desktop app with dev config
|
||||
dev-debug:
|
||||
|
|
@ -111,7 +116,10 @@ dev-debug:
|
|||
|
||||
VITE_PORT={{ vite_port }}
|
||||
export VITE_PORT
|
||||
EXTRA_CONFIG="--config {\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"exec ./node_modules/.bin/vite --port ${VITE_PORT} --strictPort\",\"cwd\":\"..\",\"wait\":false}}}"
|
||||
PROJECT_DIR=$(pwd)
|
||||
GOOSE_BIN="${PROJECT_DIR}/../../target/debug/goose"
|
||||
export GOOSE_BIN
|
||||
EXTRA_CONFIG_ARGS=(--config "{\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"exec ./node_modules/.bin/vite --port ${VITE_PORT} --strictPort\",\"cwd\":\"..\",\"wait\":false}}}")
|
||||
|
||||
# In worktrees, generate a labeled icon so you can tell instances apart
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
|
|
@ -126,12 +134,12 @@ dev-debug:
|
|||
|
||||
if swift scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$WORKTREE_LABEL"; then
|
||||
echo "🌳 Worktree: ${WORKTREE_LABEL}"
|
||||
EXTRA_CONFIG="$EXTRA_CONFIG --config {\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}"
|
||||
EXTRA_CONFIG_ARGS+=(--config "{\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
pnpm tauri dev --config src-tauri/tauri.dev.conf.json $EXTRA_CONFIG
|
||||
pnpm tauri dev --config src-tauri/tauri.dev.conf.json "${EXTRA_CONFIG_ARGS[@]}"
|
||||
|
||||
# Start only the frontend dev server
|
||||
dev-frontend:
|
||||
|
|
@ -166,8 +174,3 @@ clean:
|
|||
cd src-tauri && cargo clean
|
||||
rm -rf dist
|
||||
rm -rf node_modules
|
||||
|
||||
# Cherry-pick commits from the goose2 repo into ui/goose2/
|
||||
cherry-pick-goose2 *ARGS:
|
||||
git fetch goose2
|
||||
git format-patch -1 {{ARGS}} --stdout | git am --directory=ui/goose2
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@
|
|||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2.6.0",
|
||||
"@tauri-apps/plugin-dialog": "~2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
"@tauri-apps/plugin-shell": "~2.3.5",
|
||||
"@xyflow/react": "^12.10.2",
|
||||
"ai": "^6.0.142",
|
||||
"ansi-to-react": "^6.2.6",
|
||||
|
|
|
|||
|
|
@ -1,262 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ensure-local-goose.sh [--print-bin | --check-bin]
|
||||
|
||||
Syncs and builds a dedicated local goose checkout for goose2 development.
|
||||
|
||||
Environment variables:
|
||||
GOOSE_DEV_MODE auto|required (default: auto)
|
||||
GOOSE_DEV_ROOT path to the shared goose2 dev cache root
|
||||
(default: platform cache dir under home)
|
||||
GOOSE_DEV_REPO path to the managed goose checkout
|
||||
(default: $GOOSE_DEV_ROOT/goose)
|
||||
GOOSE_DEV_STAMP_FILE path to the shared build stamp file
|
||||
(default: $GOOSE_DEV_ROOT/stamp.env)
|
||||
GOOSE_DEV_CLONE_URL git clone URL for the managed goose checkout
|
||||
(default: https://github.com/block/goose.git)
|
||||
GOOSE_DEV_REMOTE git remote to sync from (default: origin)
|
||||
GOOSE_DEV_BRANCH preferred branch to use (default: baxen/goose2)
|
||||
GOOSE_DEV_FALLBACK_BRANCH fallback branch when the preferred branch does
|
||||
not exist remotely (default: main)
|
||||
GOOSE_DEV_ALLOW_DIRTY 1 to allow syncing/building a dirty checkout
|
||||
EOF
|
||||
}
|
||||
|
||||
action="build"
|
||||
print_bin=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--print-bin)
|
||||
print_bin=1
|
||||
shift
|
||||
;;
|
||||
--check-bin)
|
||||
action="check"
|
||||
print_bin=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mode="${GOOSE_DEV_MODE:-auto}"
|
||||
clone_url="${GOOSE_DEV_CLONE_URL:-https://github.com/block/goose.git}"
|
||||
remote="${GOOSE_DEV_REMOTE:-origin}"
|
||||
preferred_branch="${GOOSE_DEV_BRANCH:-baxen/goose2}"
|
||||
fallback_branch="${GOOSE_DEV_FALLBACK_BRANCH:-main}"
|
||||
allow_dirty="${GOOSE_DEV_ALLOW_DIRTY:-0}"
|
||||
|
||||
log() {
|
||||
echo "[goose-dev] $*" >&2
|
||||
}
|
||||
|
||||
fail_or_skip() {
|
||||
local message="$1"
|
||||
if [[ "${mode}" == "required" ]]; then
|
||||
echo "${message}" >&2
|
||||
exit 1
|
||||
fi
|
||||
log "${message}"
|
||||
# In check mode, exit 2 so callers (e.g. just dev) can detect "not ready"
|
||||
# and block instead of silently continuing without a goose binary.
|
||||
if [[ "${action}" == "check" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
default_goose_dev_root() {
|
||||
if [[ -n "${XDG_CACHE_HOME:-}" ]]; then
|
||||
printf '%s/goose2-dev\n' "${XDG_CACHE_HOME}"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
printf '%s/Library/Caches/goose2-dev\n' "${HOME}"
|
||||
;;
|
||||
*)
|
||||
printf '%s/.cache/goose2-dev\n' "${HOME}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
goose_dev_root="${GOOSE_DEV_ROOT:-$(default_goose_dev_root)}"
|
||||
goose_repo="${GOOSE_DEV_REPO:-${goose_dev_root}/goose}"
|
||||
stamp_file="${GOOSE_DEV_STAMP_FILE:-${goose_dev_root}/stamp.env}"
|
||||
bin_path="${goose_repo}/target/debug/goose"
|
||||
|
||||
resolve_remote_head() {
|
||||
local branch_name="$1"
|
||||
git -C "${goose_repo}" ls-remote --heads "${remote}" "${branch_name}" 2>/dev/null | awk 'NR == 1 { print $1 }'
|
||||
}
|
||||
|
||||
resolve_branch() {
|
||||
local resolved_branch="${preferred_branch}"
|
||||
local resolved_head
|
||||
resolved_head="$(resolve_remote_head "${resolved_branch}")"
|
||||
|
||||
if [[ -z "${resolved_head}" && "${resolved_branch}" != "${fallback_branch}" ]]; then
|
||||
log "Remote branch ${remote}/${resolved_branch} not found; falling back to ${remote}/${fallback_branch}."
|
||||
resolved_branch="${fallback_branch}"
|
||||
resolved_head="$(resolve_remote_head "${resolved_branch}")"
|
||||
fi
|
||||
|
||||
if [[ -z "${resolved_head}" ]]; then
|
||||
if [[ "${mode}" == "required" ]]; then
|
||||
echo "Could not resolve ${remote}/${resolved_branch} for managed goose checkout at ${goose_repo}." >&2
|
||||
return 1
|
||||
fi
|
||||
log "Could not resolve ${remote}/${resolved_branch} for managed goose checkout at ${goose_repo}."
|
||||
return 2
|
||||
fi
|
||||
|
||||
RESOLVED_BRANCH="${resolved_branch}"
|
||||
RESOLVED_REMOTE_HEAD="${resolved_head}"
|
||||
return 0
|
||||
}
|
||||
|
||||
write_stamp() {
|
||||
local branch_name="$1"
|
||||
local commit_sha="$2"
|
||||
|
||||
mkdir -p "$(dirname "${stamp_file}")"
|
||||
{
|
||||
printf 'STAMP_REPO=%q\n' "${goose_repo}"
|
||||
printf 'STAMP_BRANCH=%q\n' "${branch_name}"
|
||||
printf 'STAMP_COMMIT=%q\n' "${commit_sha}"
|
||||
printf 'STAMP_BIN=%q\n' "${bin_path}"
|
||||
} >"${stamp_file}"
|
||||
}
|
||||
|
||||
ensure_checkout_exists() {
|
||||
if [[ -d "${goose_repo}/.git" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${action}" == "check" ]]; then
|
||||
fail_or_skip "Managed goose checkout not found at ${goose_repo}. Rerun just setup."
|
||||
fi
|
||||
|
||||
log "Cloning managed goose checkout into ${goose_repo}."
|
||||
mkdir -p "$(dirname "${goose_repo}")"
|
||||
git clone "${clone_url}" "${goose_repo}" >/dev/null 2>&1 || {
|
||||
fail_or_skip "Failed to clone managed goose checkout from ${clone_url} into ${goose_repo}."
|
||||
}
|
||||
}
|
||||
|
||||
ensure_checkout_exists
|
||||
|
||||
if [[ "${allow_dirty}" != "1" ]]; then
|
||||
if [[ -n "$(git -C "${goose_repo}" status --porcelain)" ]]; then
|
||||
fail_or_skip "Managed goose checkout at ${goose_repo} is dirty. Use a dedicated checkout or set GOOSE_DEV_ALLOW_DIRTY=1."
|
||||
fi
|
||||
fi
|
||||
|
||||
if resolve_branch; then
|
||||
branch="${RESOLVED_BRANCH}"
|
||||
remote_head="${RESOLVED_REMOTE_HEAD}"
|
||||
else
|
||||
resolve_branch_status=$?
|
||||
case "${resolve_branch_status}" in
|
||||
1)
|
||||
exit 1
|
||||
;;
|
||||
2)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected resolve_branch status: ${resolve_branch_status}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ "${action}" == "check" ]]; then
|
||||
if [[ ! -f "${stamp_file}" ]]; then
|
||||
fail_or_skip "Managed goose checkout is configured, but no local goose build stamp was found. Rerun just setup."
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "${stamp_file}"
|
||||
|
||||
if [[ "${STAMP_REPO:-}" != "${goose_repo}" ]]; then
|
||||
fail_or_skip "Managed goose checkout changed since the last local goose build. Rerun just setup."
|
||||
fi
|
||||
|
||||
if [[ "${STAMP_BRANCH:-}" != "${branch}" ]]; then
|
||||
fail_or_skip "Managed goose branch is now ${branch}, but the local goose build was prepared for ${STAMP_BRANCH:-unknown}. Rerun just setup."
|
||||
fi
|
||||
|
||||
if [[ ! -x "${STAMP_BIN:-}" ]]; then
|
||||
fail_or_skip "Local goose binary was not found at ${STAMP_BIN:-unknown}. Rerun just setup."
|
||||
fi
|
||||
|
||||
local_head="$(git -C "${goose_repo}" rev-parse HEAD)"
|
||||
if [[ "${STAMP_COMMIT:-}" != "${local_head}" ]]; then
|
||||
fail_or_skip "Managed goose checkout changed after the last local build. Rerun just setup."
|
||||
fi
|
||||
|
||||
if [[ "${STAMP_COMMIT:-}" != "${remote_head}" ]]; then
|
||||
fail_or_skip "Managed goose checkout is behind ${remote}/${branch}. Rerun just setup."
|
||||
fi
|
||||
|
||||
if [[ "${print_bin}" == "1" ]]; then
|
||||
printf '%s\n' "${STAMP_BIN}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git -C "${goose_repo}" fetch "${remote}" "${branch}" >/dev/null 2>&1
|
||||
|
||||
remote_ref="refs/remotes/${remote}/${branch}"
|
||||
if ! git -C "${goose_repo}" show-ref --verify --quiet "${remote_ref}"; then
|
||||
fail_or_skip "Fetched ${remote}/${branch}, but ${remote_ref} is not available in ${goose_repo}."
|
||||
fi
|
||||
|
||||
if git -C "${goose_repo}" show-ref --verify --quiet "refs/heads/${branch}"; then
|
||||
git -C "${goose_repo}" checkout "${branch}" >/dev/null 2>&1
|
||||
else
|
||||
git -C "${goose_repo}" checkout -b "${branch}" --track "${remote}/${branch}" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Reset to the remote head. This is a managed build-only checkout, so we
|
||||
# always want to match the remote exactly. A plain `pull --ff-only` would
|
||||
# fail when the remote branch has been force-pushed (rebased/amended).
|
||||
git -C "${goose_repo}" reset --hard "${remote}/${branch}" >/dev/null 2>&1
|
||||
|
||||
log "Building goose from ${goose_repo} on ${branch}."
|
||||
(
|
||||
cd "${goose_repo}"
|
||||
cargo build -p goose-cli --bin goose
|
||||
)
|
||||
|
||||
if [[ -n "$(git -C "${goose_repo}" status --porcelain -- Cargo.lock)" ]]; then
|
||||
# Cargo may refresh the lockfile while compiling a freshly synced checkout.
|
||||
# This managed repo is only a build source for goose2, so restore the tracked
|
||||
# lockfile to keep the checkout clean for later preflight checks.
|
||||
git -C "${goose_repo}" checkout -- Cargo.lock
|
||||
fi
|
||||
|
||||
if [[ ! -x "${bin_path}" ]]; then
|
||||
echo "Expected goose binary at ${bin_path}, but it was not built successfully." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
write_stamp "${branch}" "$(git -C "${goose_repo}" rev-parse HEAD)"
|
||||
|
||||
log "Local goose binary ready at ${bin_path}."
|
||||
if [[ "${print_bin}" == "1" ]]; then
|
||||
printf '%s\n' "${bin_path}"
|
||||
fi
|
||||
77
ui/goose2/src-tauri/Cargo.lock
generated
77
ui/goose2/src-tauri/Cargo.lock
generated
|
|
@ -1079,6 +1079,15 @@ version = "1.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
|
|
@ -1675,6 +1684,7 @@ dependencies = [
|
|||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
|
@ -2920,6 +2930,16 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
|
|
@ -4085,12 +4105,44 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sigchld",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "sigchld"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
|
|
@ -4541,9 +4593,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
|
||||
checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
|
|
@ -4625,6 +4677,27 @@ dependencies = [
|
|||
"zbus 5.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
"open",
|
||||
"os_pipe",
|
||||
"regex",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared_child",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.4.1"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ authors = ["you"]
|
|||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "Goose"
|
||||
name = "goose-tauri"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
|
|
@ -36,6 +36,7 @@ doctor = { git = "https://github.com/block/builderbot", rev = "8e1c3ec145edc0df5
|
|||
ignore = "0.4.25"
|
||||
base64 = "0.22"
|
||||
mime_guess = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
keyring = { version = "3", features = ["apple-native"] }
|
||||
|
|
|
|||
|
|
@ -13,17 +13,39 @@
|
|||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [
|
||||
{ "path": "$HOME/**" },
|
||||
{ "path": "$HOME/.goose/**" },
|
||||
{ "path": "$HOME/.goose/artifacts/**" },
|
||||
{ "path": "$TEMP/**" },
|
||||
{ "path": "/Volumes/**" },
|
||||
{ "path": "/mnt/**" },
|
||||
{ "path": "/workspace/**" },
|
||||
{ "path": "/workspaces/**" },
|
||||
{ "path": "/opt/**" },
|
||||
{ "path": "/srv/**" },
|
||||
{ "path": "*:/**" }
|
||||
{
|
||||
"path": "$HOME/**"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.goose/**"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.goose/artifacts/**"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/**"
|
||||
},
|
||||
{
|
||||
"path": "/Volumes/**"
|
||||
},
|
||||
{
|
||||
"path": "/mnt/**"
|
||||
},
|
||||
{
|
||||
"path": "/workspace/**"
|
||||
},
|
||||
{
|
||||
"path": "/workspaces/**"
|
||||
},
|
||||
{
|
||||
"path": "/opt/**"
|
||||
},
|
||||
{
|
||||
"path": "/srv/**"
|
||||
},
|
||||
{
|
||||
"path": "*:/**"
|
||||
}
|
||||
]
|
||||
},
|
||||
"window-state:allow-restore-state",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -302,6 +302,216 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "shell:default",
|
||||
"markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-execute",
|
||||
"markdownDescription": "Enables the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-kill",
|
||||
"markdownDescription": "Enables the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-open",
|
||||
"markdownDescription": "Enables the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-spawn",
|
||||
"markdownDescription": "Enables the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-stdin-write",
|
||||
"markdownDescription": "Enables the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-execute",
|
||||
"markdownDescription": "Denies the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-kill",
|
||||
"markdownDescription": "Denies the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-open",
|
||||
"markdownDescription": "Denies the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-spawn",
|
||||
"markdownDescription": "Denies the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write",
|
||||
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "ShellScopeEntry",
|
||||
"description": "Shell scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cmd": {
|
||||
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"sidecar"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
},
|
||||
"sidecar": {
|
||||
"description": "If this command is a sidecar command.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "ShellScopeEntry",
|
||||
"description": "Shell scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cmd": {
|
||||
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"sidecar"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
},
|
||||
"sidecar": {
|
||||
"description": "If this command is a sidecar command.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"identifier": {
|
||||
|
|
@ -2331,22 +2541,22 @@
|
|||
"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`",
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "dialog:default",
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the ask command without any pre-configured scope.",
|
||||
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-ask",
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the confirm command without any pre-configured scope.",
|
||||
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-confirm",
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the message command without any pre-configured scope.",
|
||||
|
|
@ -2367,16 +2577,16 @@
|
|||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ask command without any pre-configured scope.",
|
||||
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-ask",
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the confirm command without any pre-configured scope.",
|
||||
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-confirm",
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the message command without any pre-configured scope.",
|
||||
|
|
@ -2462,6 +2672,72 @@
|
|||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "shell:default",
|
||||
"markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-execute",
|
||||
"markdownDescription": "Enables the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-kill",
|
||||
"markdownDescription": "Enables the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-open",
|
||||
"markdownDescription": "Enables the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-spawn",
|
||||
"markdownDescription": "Enables the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-stdin-write",
|
||||
"markdownDescription": "Enables the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-execute",
|
||||
"markdownDescription": "Denies the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-kill",
|
||||
"markdownDescription": "Denies the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-open",
|
||||
"markdownDescription": "Denies the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-spawn",
|
||||
"markdownDescription": "Denies the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write",
|
||||
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
|
@ -2616,6 +2892,50 @@
|
|||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArg": {
|
||||
"description": "A command argument allowed to be executed by the webview API.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "A variable that is set while calling the command from the webview API.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"validator"
|
||||
],
|
||||
"properties": {
|
||||
"raw": {
|
||||
"description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"validator": {
|
||||
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: <https://docs.rs/regex/latest/regex/#syntax>",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArgs": {
|
||||
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,6 +302,216 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "shell:default",
|
||||
"markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-execute",
|
||||
"markdownDescription": "Enables the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-kill",
|
||||
"markdownDescription": "Enables the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-open",
|
||||
"markdownDescription": "Enables the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-spawn",
|
||||
"markdownDescription": "Enables the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-stdin-write",
|
||||
"markdownDescription": "Enables the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-execute",
|
||||
"markdownDescription": "Denies the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-kill",
|
||||
"markdownDescription": "Denies the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-open",
|
||||
"markdownDescription": "Denies the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-spawn",
|
||||
"markdownDescription": "Denies the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write",
|
||||
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "ShellScopeEntry",
|
||||
"description": "Shell scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cmd": {
|
||||
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"sidecar"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
},
|
||||
"sidecar": {
|
||||
"description": "If this command is a sidecar command.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "ShellScopeEntry",
|
||||
"description": "Shell scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cmd": {
|
||||
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"sidecar"
|
||||
],
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The allowed arguments for the command execution.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
|
||||
"type": "string"
|
||||
},
|
||||
"sidecar": {
|
||||
"description": "If this command is a sidecar command.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"identifier": {
|
||||
|
|
@ -2331,22 +2541,22 @@
|
|||
"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`",
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "dialog:default",
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the ask command without any pre-configured scope.",
|
||||
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-ask",
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the confirm command without any pre-configured scope.",
|
||||
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-confirm",
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the message command without any pre-configured scope.",
|
||||
|
|
@ -2367,16 +2577,16 @@
|
|||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ask command without any pre-configured scope.",
|
||||
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-ask",
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the confirm command without any pre-configured scope.",
|
||||
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-confirm",
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the message command without any pre-configured scope.",
|
||||
|
|
@ -2462,6 +2672,72 @@
|
|||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "shell:default",
|
||||
"markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-execute",
|
||||
"markdownDescription": "Enables the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-kill",
|
||||
"markdownDescription": "Enables the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-open",
|
||||
"markdownDescription": "Enables the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-spawn",
|
||||
"markdownDescription": "Enables the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:allow-stdin-write",
|
||||
"markdownDescription": "Enables the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-execute",
|
||||
"markdownDescription": "Denies the execute command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the kill command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-kill",
|
||||
"markdownDescription": "Denies the kill command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-open",
|
||||
"markdownDescription": "Denies the open command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the spawn command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-spawn",
|
||||
"markdownDescription": "Denies the spawn command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stdin_write command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write",
|
||||
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
|
@ -2616,6 +2892,50 @@
|
|||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArg": {
|
||||
"description": "A command argument allowed to be executed by the webview API.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "A variable that is set while calling the command from the webview API.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"validator"
|
||||
],
|
||||
"properties": {
|
||||
"raw": {
|
||||
"description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"validator": {
|
||||
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: <https://docs.rs/regex/latest/regex/#syntax>",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArgs": {
|
||||
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ShellScopeEntryAllowedArg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
use crate::services::acp::GooseServeProcess;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_goose_serve_url() -> Result<String, String> {
|
||||
GooseServeProcess::start().await?;
|
||||
let process = GooseServeProcess::get()?;
|
||||
pub async fn get_goose_serve_url(app_handle: tauri::AppHandle) -> Result<String, String> {
|
||||
let process = GooseServeProcess::get(app_handle).await?;
|
||||
Ok(process.ws_url())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
use crate::services::acp::resolve_goose_binary;
|
||||
use crate::services::acp::goose_serve::get_goose_command;
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -20,9 +20,9 @@ pub async fn authenticate_model_provider(
|
|||
return Err("Native Goose sign-in is not supported on Windows yet".to_string());
|
||||
}
|
||||
|
||||
let goose_binary = resolve_goose_binary()?;
|
||||
let goose_command = get_goose_command(&app_handle)?;
|
||||
let quoted_label = shell_quote(&provider_label);
|
||||
let quoted_binary = shell_quote(&goose_binary.to_string_lossy());
|
||||
let quoted_binary = shell_quote(&goose_command.as_std().get_program().to_string_lossy());
|
||||
|
||||
let command = if cfg!(target_os = "linux") {
|
||||
format!(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use tauri_plugin_window_state::StateFlags;
|
|||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Debug)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use tokio::process::{Child, Command};
|
||||
|
|
@ -7,12 +9,6 @@ use tokio::sync::OnceCell;
|
|||
const GOOSE_SERVE_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const GOOSE_SERVE_CONNECT_RETRY_DELAY: Duration = Duration::from_millis(100);
|
||||
const LOCALHOST: &str = "127.0.0.1";
|
||||
const COMMON_GOOSE_PATHS: &[&str] = &[
|
||||
"/opt/homebrew/bin",
|
||||
"/usr/local/bin",
|
||||
"/usr/bin",
|
||||
"/home/linuxbrew/.linuxbrew/bin",
|
||||
];
|
||||
// ---------------------------------------------------------------------------
|
||||
// GooseServeProcess — singleton that owns the long-lived `goose serve` child
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -36,29 +32,15 @@ impl GooseServeProcess {
|
|||
format!("ws://{LOCALHOST}:{}/acp", self.port)
|
||||
}
|
||||
|
||||
/// Start the singleton `goose serve` process.
|
||||
///
|
||||
/// This is called once from `lib.rs` during app startup. Subsequent calls
|
||||
/// are no-ops (the `OnceCell` ensures single initialisation). The process
|
||||
/// is spawned with `kill_on_drop(true)` so it is automatically terminated
|
||||
/// when the Tauri app exits.
|
||||
pub async fn start() -> Result<(), String> {
|
||||
GOOSE_SERVE
|
||||
.get_or_try_init(|| async { Self::spawn().await })
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Get a reference to the running process, or an error if it was never
|
||||
/// started (should not happen in normal operation).
|
||||
pub fn get() -> Result<&'static GooseServeProcess, String> {
|
||||
pub async fn get(app_handle: tauri::AppHandle) -> Result<&'static GooseServeProcess, String> {
|
||||
GOOSE_SERVE
|
||||
.get()
|
||||
.ok_or_else(|| "Goose serve process has not been started".to_string())
|
||||
.get_or_try_init(|| async { Self::spawn(app_handle).await })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn spawn() -> Result<GooseServeProcess, String> {
|
||||
let binary_path = resolve_goose_binary()?;
|
||||
async fn spawn(app_handle: tauri::AppHandle) -> Result<GooseServeProcess, String> {
|
||||
let port = reserve_free_port()?;
|
||||
|
||||
// Use a stable working directory for the long-lived server process.
|
||||
|
|
@ -71,7 +53,9 @@ impl GooseServeProcess {
|
|||
)
|
||||
})?;
|
||||
|
||||
let mut command = Command::new(&binary_path);
|
||||
let mut command: Command = get_goose_command(&app_handle)?;
|
||||
let binary_display = command.as_std().get_program().to_string_lossy().to_string();
|
||||
|
||||
command
|
||||
.arg("serve")
|
||||
.arg("--host")
|
||||
|
|
@ -85,16 +69,13 @@ impl GooseServeProcess {
|
|||
.kill_on_drop(true);
|
||||
|
||||
log::info!(
|
||||
"Spawning long-lived goose serve: binary={} port={} cwd={}",
|
||||
binary_path.display(),
|
||||
port,
|
||||
"Spawning long-lived goose serve: binary={binary_display} port={port} cwd={}",
|
||||
working_dir.display(),
|
||||
);
|
||||
|
||||
let mut child = command.spawn().map_err(|error| {
|
||||
format!(
|
||||
"Failed to spawn goose serve (binary: {}, cwd: {}): {error}",
|
||||
binary_path.display(),
|
||||
"Failed to spawn goose serve (binary: {binary_display}, cwd: {}): {error}",
|
||||
working_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
|
@ -110,6 +91,19 @@ impl GooseServeProcess {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_goose_command(app_handle: &tauri::AppHandle) -> Result<Command, String> {
|
||||
if let Ok(override_path) = std::env::var("GOOSE_BIN") {
|
||||
Ok(Command::new(override_path))
|
||||
} else {
|
||||
let tauri_command = app_handle
|
||||
.shell()
|
||||
.sidecar("goose")
|
||||
.map_err(|e| format!("could not resolve goose binary: {e}"))?;
|
||||
let std_command: std::process::Command = tauri_command.into();
|
||||
Ok(std_command.into())
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_server_ready(port: u16, child: &mut Child) -> Result<(), String> {
|
||||
let deadline = Instant::now() + GOOSE_SERVE_CONNECT_TIMEOUT;
|
||||
let addr = format!("{LOCALHOST}:{port}");
|
||||
|
|
@ -144,164 +138,6 @@ fn default_serve_working_dir() -> PathBuf {
|
|||
.join("artifacts")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Binary resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub(crate) fn resolve_goose_binary() -> Result<PathBuf, String> {
|
||||
let binary_path = if let Ok(override_path) = std::env::var("GOOSE_BIN") {
|
||||
let path = PathBuf::from(&override_path);
|
||||
if !path.exists() {
|
||||
return Err(format!(
|
||||
"GOOSE_BIN points to non-existent path: {override_path}"
|
||||
));
|
||||
}
|
||||
if !goose_binary_supports_serve(&path)? {
|
||||
return Err(format!(
|
||||
"GOOSE_BIN points to a goose binary without `serve` support: {}",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
log::info!("Using GOOSE_BIN override: {override_path}");
|
||||
path
|
||||
} else {
|
||||
let path = find_goose_binary()
|
||||
.ok_or_else(|| "Unknown or unavailable agent provider: goose".to_string())?;
|
||||
|
||||
if !goose_binary_supports_serve(&path)? {
|
||||
return Err(format!(
|
||||
"Resolved goose binary does not support `serve`: {}. Set GOOSE_BIN to a newer goose binary.",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Resolved goose binary via local discovery: {}",
|
||||
path.display()
|
||||
);
|
||||
path
|
||||
};
|
||||
|
||||
// Log the binary version for debugging.
|
||||
match std::process::Command::new(&binary_path)
|
||||
.env("GOOSE_PATH_ROOT", goose_probe_root())
|
||||
.arg("--version")
|
||||
.output()
|
||||
{
|
||||
Ok(output) => {
|
||||
let version = String::from_utf8_lossy(&output.stdout);
|
||||
log::info!(
|
||||
"Goose binary version: {} (path: {})",
|
||||
version.trim(),
|
||||
binary_path.display()
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Could not determine goose binary version at {}: {err}",
|
||||
binary_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(binary_path)
|
||||
}
|
||||
|
||||
fn find_goose_binary() -> Option<PathBuf> {
|
||||
find_goose_via_login_shell().or_else(find_goose_in_common_paths)
|
||||
}
|
||||
|
||||
fn find_goose_via_login_shell() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
for shell in ["/bin/zsh", "/bin/bash"] {
|
||||
let Ok(output) = std::process::Command::new(shell)
|
||||
.args(["-l", "-c", "which goose"])
|
||||
.output()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if let Some(path_str) = stdout.lines().rfind(|line| !line.trim().is_empty()) {
|
||||
let path = PathBuf::from(path_str.trim());
|
||||
if path.is_file() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_goose_in_common_paths() -> Option<PathBuf> {
|
||||
COMMON_GOOSE_PATHS
|
||||
.iter()
|
||||
.map(|dir| Path::new(dir).join(goose_binary_name()))
|
||||
.find(|path| path.is_file())
|
||||
}
|
||||
|
||||
fn goose_binary_name() -> &'static str {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
"goose.exe"
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
"goose"
|
||||
}
|
||||
}
|
||||
|
||||
fn goose_binary_supports_serve(binary_path: &PathBuf) -> Result<bool, String> {
|
||||
let output = std::process::Command::new(binary_path)
|
||||
.env("GOOSE_PATH_ROOT", goose_probe_root())
|
||||
.arg("serve")
|
||||
.arg("--help")
|
||||
.output()
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"Failed to probe goose binary {}: {error}",
|
||||
binary_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if output.status.success() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
log::warn!(
|
||||
"Goose binary probe failed for {}: status={} stderr={} stdout={}",
|
||||
binary_path.display(),
|
||||
output
|
||||
.status
|
||||
.code()
|
||||
.map(|code| code.to_string())
|
||||
.unwrap_or_else(|| "signal".to_string()),
|
||||
stderr.trim(),
|
||||
stdout.trim(),
|
||||
);
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn goose_probe_root() -> PathBuf {
|
||||
std::env::temp_dir().join("block-goose2-goose-probe")
|
||||
}
|
||||
|
||||
fn reserve_free_port() -> Result<u16, String> {
|
||||
let listener = std::net::TcpListener::bind((LOCALHOST, 0))
|
||||
.map_err(|error| format!("Failed to reserve Goose serve port: {error}"))?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
pub(crate) mod goose_serve;
|
||||
|
||||
pub(crate) use goose_serve::resolve_goose_binary;
|
||||
pub(crate) use goose_serve::GooseServeProcess;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
],
|
||||
"externalBin": ["../../../target/release/goose"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"identifier": "com.goose.app.dev",
|
||||
"productName": "Goose Dev"
|
||||
"productName": "Goose Dev",
|
||||
"bundle": {
|
||||
"externalBin": []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
ui/pnpm-lock.yaml
generated
20
ui/pnpm-lock.yaml
generated
|
|
@ -468,11 +468,14 @@ importers:
|
|||
specifier: ^2
|
||||
version: 2.10.1
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ~2.6.0
|
||||
version: 2.6.0
|
||||
specifier: ~2.7.0
|
||||
version: 2.7.0
|
||||
'@tauri-apps/plugin-opener':
|
||||
specifier: ^2.5.3
|
||||
version: 2.5.3
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: ~2.3.5
|
||||
version: 2.3.5
|
||||
'@xyflow/react':
|
||||
specifier: ^12.10.2
|
||||
version: 12.10.2(@types/react@19.2.14)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -3495,12 +3498,15 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.6.0':
|
||||
resolution: {integrity: sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==}
|
||||
'@tauri-apps/plugin-dialog@2.7.0':
|
||||
resolution: {integrity: sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==}
|
||||
|
||||
'@tauri-apps/plugin-opener@2.5.3':
|
||||
resolution: {integrity: sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==}
|
||||
|
||||
'@tauri-apps/plugin-shell@2.3.5':
|
||||
resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==}
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -11794,7 +11800,7 @@ snapshots:
|
|||
'@tauri-apps/cli-win32-ia32-msvc': 2.10.1
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.10.1
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.6.0':
|
||||
'@tauri-apps/plugin-dialog@2.7.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
|
|
@ -11802,6 +11808,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@tauri-apps/plugin-shell@2.3.5':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue