Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

747 lines
21 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DESKTOP_APP_DIR="${ROOT_DIR}/apps/desktop"
TUTTID_DIR="${ROOT_DIR}/services/tuttid"
TUTTID_BIN_DIR="${DESKTOP_APP_DIR}/build/tuttid"
NODE_VERSION_FILE="${ROOT_DIR}/.node-version"
GO_MOD_FILE="${TUTTID_DIR}/go.mod"
PACKAGE_JSON_FILE="${ROOT_DIR}/package.json"
export TUTTI_ENV="${DEV_GUI_TUTTI_ENV:-development}"
GO_BIN=""
DEV_GUI_CHILD_PID=""
DEV_GUI_PID_PATH=""
DEV_GUI_INITIAL_TUTTID_PID=""
DEV_GUI_DESKTOP_STARTED=0
DEV_GUI_SHUTDOWN_STARTED=0
DEV_GUI_SHUTDOWN_TIMEOUT_SECONDS="${DEV_GUI_SHUTDOWN_TIMEOUT_SECONDS:-10}"
log() {
printf '[dev-gui] %s\n' "$*" >&2
}
fail() {
printf '[dev-gui] %s\n' "$*" >&2
exit 1
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
prepend_command_dir() {
local command_path="$1"
local command_dir
command_dir="$(dirname "${command_path}")"
export PATH="${command_dir}:${PATH}"
hash -r
}
resolve_corepack_command() {
local node_exec_path
local node_bin_dir
local node_version
local node_platform
local node_arch
local local_node_corepack
local corepack_candidate
if command_exists corepack; then
command -v corepack
return
fi
if command_exists node; then
node_exec_path="$(node -p 'process.execPath' 2>/dev/null || true)"
if [[ -n "${node_exec_path}" ]]; then
node_bin_dir="$(dirname "${node_exec_path}")"
if [[ -x "${node_bin_dir}/corepack" ]]; then
printf '%s\n' "${node_bin_dir}/corepack"
return
fi
fi
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
node_platform="$(node -p 'process.platform' 2>/dev/null || true)"
node_arch="$(node -p 'process.arch' 2>/dev/null || true)"
if [[ -n "${node_version}" && -n "${node_platform}" && -n "${node_arch}" ]]; then
local_node_corepack="${HOME}/.local/node-v${node_version}-${node_platform}-${node_arch}/bin/corepack"
if [[ -x "${local_node_corepack}" ]]; then
printf '%s\n' "${local_node_corepack}"
return
fi
fi
fi
while IFS= read -r corepack_candidate; do
if [[ -x "${corepack_candidate}" ]]; then
printf '%s\n' "${corepack_candidate}"
return
fi
done < <(find "${HOME}/.local" -maxdepth 3 -path '*/bin/corepack' 2>/dev/null | sort -r)
return 1
}
run_go() {
env -u GOROOT "${GO_BIN}" "$@"
}
is_process_running() {
local pid="$1"
local stat
[[ -n "${pid}" ]] || return 1
kill -0 "${pid}" >/dev/null 2>&1 || return 1
stat="$(ps -p "${pid}" -o stat= 2>/dev/null | tr -d '[:space:]' || true)"
[[ "${stat}" != Z* ]]
}
wait_for_process_exit() {
local pid="$1"
local timeout_seconds="$2"
local deadline=$((SECONDS + timeout_seconds))
while (( SECONDS < deadline )); do
if ! is_process_running "${pid}"; then
return 0
fi
sleep 0.2
done
return 1
}
resolve_dev_gui_process_group_id() {
ps -p "$$" -o pgid= 2>/dev/null | tr -d '[:space:]'
}
restore_terminal_foreground() {
local pgid
[[ -t 0 && -e /dev/tty ]] || return
pgid="$(resolve_dev_gui_process_group_id)"
[[ "${pgid}" =~ ^[0-9]+$ ]] || return
(
trap '' TTOU
perl -MPOSIX=tcsetpgrp -e 'tcsetpgrp(STDIN, int($ARGV[0]))' "${pgid}" \
< /dev/tty >/dev/null 2>&1 || true
)
}
wait_for_desktop_dev_exit() {
local pid="$1"
local status
while is_process_running "${pid}"; do
restore_terminal_foreground
sleep 0.5
done
restore_terminal_foreground
set +e
wait "${pid}" >/dev/null 2>&1
status="$?"
set -e
return "${status}"
}
collect_child_processes() {
local parent_pid="$1"
local child_pid
while IFS= read -r child_pid; do
[[ "${child_pid}" =~ ^[0-9]+$ ]] || continue
collect_child_processes "${child_pid}"
printf '%s\n' "${child_pid}"
done < <(pgrep -P "${parent_pid}" 2>/dev/null || true)
}
stop_process_tree() {
local pid="$1"
local signal="$2"
local pids=()
if [[ -z "${pid}" ]] || ! is_process_running "${pid}"; then
return
fi
while IFS= read -r child_pid; do
[[ -n "${child_pid}" ]] || continue
pids+=("${child_pid}")
done < <(
{
collect_child_processes "${pid}"
printf '%s\n' "${pid}"
} | awk '!seen[$0]++'
)
if (( ${#pids[@]} == 0 )); then
return
fi
kill "-${signal}" "${pids[@]}" >/dev/null 2>&1 || true
}
read_process_command() {
local pid="$1"
ps -p "${pid}" -o comm= -o args= 2>/dev/null | tr -d '\n'
}
is_likely_tuttid_process() {
local command="$1"
local part
[[ -n "${command//[[:space:]]/}" ]] || return 1
for part in ${command}; do
case "${part##*/}" in
tuttid|tuttid.exe)
return 0
;;
esac
done
return 1
}
stop_tuttid_from_pid_file() {
local pid_path="${DEV_GUI_PID_PATH}"
local raw_pid
local pid
local command
[[ -n "${pid_path}" ]] || return
[[ -f "${pid_path}" ]] || return
raw_pid="$(tr -d '[:space:]' < "${pid_path}" 2>/dev/null || true)"
[[ "${raw_pid}" =~ ^[0-9]+$ ]] || return
pid="${raw_pid}"
if [[ -n "${DEV_GUI_INITIAL_TUTTID_PID}" ]] && [[ "${pid}" == "${DEV_GUI_INITIAL_TUTTID_PID}" ]]; then
log "leaving pre-existing tuttid pid ${pid} alone"
return
fi
if ! is_process_running "${pid}"; then
rm -f "${pid_path}"
return
fi
command="$(read_process_command "${pid}" || true)"
if ! is_likely_tuttid_process "${command}"; then
log "leaving pid ${pid} alone because it is not a tuttid process"
return
fi
log "stopping managed tuttid pid ${pid}"
stop_process_tree "${pid}" TERM
if ! wait_for_process_exit "${pid}" "${DEV_GUI_SHUTDOWN_TIMEOUT_SECONDS}"; then
log "force stopping managed tuttid pid ${pid}"
stop_process_tree "${pid}" KILL
wait_for_process_exit "${pid}" 2 >/dev/null 2>&1 || true
fi
}
cleanup_dev_gui() {
if [[ "${DEV_GUI_SHUTDOWN_STARTED}" == "1" ]]; then
return
fi
DEV_GUI_SHUTDOWN_STARTED=1
if [[ "${DEV_GUI_DESKTOP_STARTED}" == "1" ]] && [[ -n "${DEV_GUI_CHILD_PID}" ]]; then
log "stopping desktop dev process tree ${DEV_GUI_CHILD_PID}"
stop_process_tree "${DEV_GUI_CHILD_PID}" TERM
if ! wait_for_process_exit "${DEV_GUI_CHILD_PID}" "${DEV_GUI_SHUTDOWN_TIMEOUT_SECONDS}"; then
log "force stopping desktop dev process tree ${DEV_GUI_CHILD_PID}"
stop_process_tree "${DEV_GUI_CHILD_PID}" KILL
fi
wait "${DEV_GUI_CHILD_PID}" >/dev/null 2>&1 || true
fi
if [[ "${DEV_GUI_DESKTOP_STARTED}" == "1" ]]; then
stop_tuttid_from_pid_file
fi
}
read_tuttid_pid_file() {
local pid_path="$1"
[[ -n "${pid_path}" ]] || return 0
[[ -f "${pid_path}" ]] || return 0
tr -d '[:space:]' < "${pid_path}" 2>/dev/null || true
}
resolve_tuttid_pid_path() {
node - "${ROOT_DIR}/config/tutti.defaults.json" <<'NODE'
const { readFileSync } = require("node:fs");
const { homedir } = require("node:os");
const { join } = require("node:path");
const defaults = JSON.parse(readFileSync(process.argv[2], "utf8"));
const explicitPIDPath = process.env.TUTTID_PID_PATH?.trim();
if (explicitPIDPath) {
console.log(explicitPIDPath);
process.exit(0);
}
const envValue = process.env.TUTTI_ENV?.trim().toLowerCase();
const isDevelopment =
envValue === "" ||
envValue === undefined ||
envValue === "dev" ||
envValue === "development" ||
envValue === "local";
const stateDir =
process.env.TUTTI_STATE_DIR?.trim() ||
join(
homedir(),
isDevelopment
? defaults.state.developmentDirName
: defaults.state.productionDirName
);
const runDir =
process.env.TUTTID_RUN_DIR?.trim() ||
join(stateDir, defaults.state.runDirName);
console.log(join(runDir, defaults.state.pidFileName));
NODE
}
prepare_dev_gui_runtime() {
DEV_GUI_PID_PATH="$(resolve_tuttid_pid_path)"
DEV_GUI_INITIAL_TUTTID_PID="$(read_tuttid_pid_file "${DEV_GUI_PID_PATH}")"
prepare_managed_posix_shell
prepare_bundled_rtk
if [[ "$(uname -s)" == "Darwin" ]]; then
node "${ROOT_DIR}/tools/scripts/prepare-dev-login-protocol.mjs"
fi
}
prepare_bundled_rtk() {
if [[ -n "${TUTTI_BUNDLED_RTK_PATH:-}" ]]; then
[[ -f "${TUTTI_BUNDLED_RTK_PATH}" ]] || fail \
"TUTTI_BUNDLED_RTK_PATH does not point to a file: ${TUTTI_BUNDLED_RTK_PATH}"
return
fi
local platform
platform="$(node -e 'const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"; const arch = process.arch === "arm64" ? "arm64" : "amd64"; process.stdout.write(`${os}-${arch}`)')"
log "preparing bundled RTK for ${platform}"
node "${DESKTOP_APP_DIR}/scripts/vendor-rtk.mjs" --platform="${platform}"
if [[ "${platform}" == windows-* ]]; then
export TUTTI_BUNDLED_RTK_PATH="${DESKTOP_APP_DIR}/build/rtk/rtk.exe"
else
export TUTTI_BUNDLED_RTK_PATH="${DESKTOP_APP_DIR}/build/rtk/rtk"
fi
[[ -f "${TUTTI_BUNDLED_RTK_PATH}" ]] || fail \
"bundled RTK executable is missing after preparation: ${TUTTI_BUNDLED_RTK_PATH}"
}
prepare_managed_posix_shell() {
# Workspace app packages keep a stable bootstrap.sh contract. On Windows the
# daemon executes that contract through the vendored MSYS2 shell; packaged
# launches discover it from runtime.json, while dev launches need to inject
# the same absolute path explicitly.
if [[ "$(node -p 'process.platform')" != "win32" ]]; then
return
fi
if [[ -n "${TUTTI_MANAGED_POSIX_SHELL:-}" ]]; then
[[ -f "${TUTTI_MANAGED_POSIX_SHELL}" ]] || fail \
"TUTTI_MANAGED_POSIX_SHELL does not point to a file: ${TUTTI_MANAGED_POSIX_SHELL}"
log "using managed POSIX shell from TUTTI_MANAGED_POSIX_SHELL=${TUTTI_MANAGED_POSIX_SHELL}"
return
fi
local runtime_root="${DESKTOP_APP_DIR}/build/managed-posix-shell"
local metadata_path="${runtime_root}/runtime.json"
local shell_path=""
if [[ -f "${metadata_path}" ]]; then
shell_path="$(node - "${metadata_path}" <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const metadataPath = process.argv[2];
const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
if (metadata.schemaVersion !== "tutti.managed-posix-shell.v1" || typeof metadata.executable !== "string") {
process.exit(1);
}
process.stdout.write(path.resolve(path.dirname(metadataPath), metadata.executable));
NODE
)" || shell_path=""
fi
if [[ -z "${shell_path}" ]] || [[ ! -f "${shell_path}" ]]; then
log "preparing managed POSIX shell for Windows dev GUI"
node "${DESKTOP_APP_DIR}/scripts/vendor-managed-posix-shell.mjs" \
--platform=windows-amd64
shell_path="$(node - "${metadata_path}" <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const metadataPath = process.argv[2];
const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
if (metadata.schemaVersion !== "tutti.managed-posix-shell.v1" || typeof metadata.executable !== "string") {
throw new Error("managed POSIX shell runtime metadata is invalid");
}
process.stdout.write(path.resolve(path.dirname(metadataPath), metadata.executable));
NODE
)"
fi
[[ -f "${shell_path}" ]] || fail \
"managed POSIX shell executable is missing after preparation: ${shell_path}"
export TUTTI_MANAGED_POSIX_SHELL="${shell_path}"
log "using managed POSIX shell for Windows dev GUI: ${TUTTI_MANAGED_POSIX_SHELL}"
}
resolve_required_node_major() {
tr -d '[:space:]' < "${NODE_VERSION_FILE}"
}
resolve_required_go_minor() {
sed -nE 's/^go ([0-9]+\.[0-9]+).*/\1/p' "${GO_MOD_FILE}" | head -n 1
}
resolve_required_pnpm_version() {
sed -nE 's/.*"packageManager":[[:space:]]*"pnpm@([^"]+)".*/\1/p' \
"${PACKAGE_JSON_FILE}" | head -n 1
}
ensure_node_runtime() {
local current_node_major=""
local required_node_major
required_node_major="$(resolve_required_node_major)"
if command_exists node; then
current_node_major="$(node -p 'process.versions.node.split(".")[0]')"
fi
if [[ -n "${current_node_major}" ]] && (( current_node_major >= required_node_major )); then
log "node $(node --version)"
return
fi
if [[ -s "${HOME}/.nvm/nvm.sh" ]]; then
log "installing Node.js ${required_node_major}.x with nvm"
# shellcheck disable=SC1090
source "${HOME}/.nvm/nvm.sh"
nvm install "${required_node_major}"
nvm use "${required_node_major}" >/dev/null
hash -r
log "node $(node --version)"
return
fi
fail "Node.js ${required_node_major}+ is required. Install nvm or Node.js ${required_node_major}+ and rerun make dev-gui."
}
ensure_pnpm() {
local required_pnpm_version
local required_pnpm_semver
local current_pnpm_version=""
local corepack_command=""
local resolved_pnpm_version
required_pnpm_version="$(resolve_required_pnpm_version)"
required_pnpm_semver="${required_pnpm_version%%+*}"
if corepack_command="$(resolve_corepack_command)"; then
prepend_command_dir "${corepack_command}"
fi
if command_exists pnpm; then
current_pnpm_version="$(pnpm --version 2>/dev/null || true)"
fi
if [[ "${current_pnpm_version}" == "${required_pnpm_semver}" ]]; then
log "pnpm ${current_pnpm_version}"
return
fi
if [[ -z "${corepack_command}" ]] && ! corepack_command="$(resolve_corepack_command)"; then
fail "corepack is required to install pnpm ${required_pnpm_version} automatically."
fi
log "installing pnpm ${required_pnpm_version} with corepack"
"${corepack_command}" enable
"${corepack_command}" prepare "pnpm@${required_pnpm_version}" --activate
prepend_command_dir "${corepack_command}"
resolved_pnpm_version="$(pnpm --version 2>/dev/null || true)"
if [[ "${resolved_pnpm_version}" != "${required_pnpm_semver}" ]]; then
fail "pnpm ${required_pnpm_version} installation did not succeed; found ${resolved_pnpm_version:-unknown version}."
fi
log "pnpm ${resolved_pnpm_version}"
}
ensure_go_runtime() {
local required_go_minor
local current_go_minor=""
local brew_formula
local brew_prefix
required_go_minor="$(resolve_required_go_minor)"
if command_exists go; then
current_go_minor="$(
env -u GOROOT go version 2>/dev/null | sed -nE 's/^go version go([0-9]+\.[0-9]+).*/\1/p'
)"
if [[ "${current_go_minor}" == "${required_go_minor}" ]]; then
GO_BIN="$(command -v go)"
log "$(run_go version)"
return
fi
fi
if [[ "$(uname -s)" != "Darwin" ]] || ! command_exists brew; then
fail "Go ${required_go_minor}.x is required. Install it and rerun make dev-gui."
fi
brew_formula="go@${required_go_minor}"
if [[ -z "$(brew list --versions "${brew_formula}" 2>/dev/null)" ]]; then
log "installing ${brew_formula} with Homebrew"
brew install "${brew_formula}"
else
log "using installed ${brew_formula}"
fi
brew_prefix="$(brew --prefix "${brew_formula}")"
GO_BIN="${brew_prefix}/bin/go"
export PATH="${brew_prefix}/bin:${PATH}"
hash -r
current_go_minor="$(
env -u GOROOT "${GO_BIN}" version 2>/dev/null | sed -nE 's/^go version go([0-9]+\.[0-9]+).*/\1/p'
)"
if [[ "${current_go_minor}" != "${required_go_minor}" ]]; then
fail "expected Go ${required_go_minor}.x after installing ${brew_formula}, found $("${GO_BIN}" version)"
fi
log "$(run_go version)"
}
check_runtime_prerequisites() {
log "checking runtime prerequisites"
ensure_node_runtime
ensure_pnpm
ensure_go_runtime
}
configure_desktop_dev_version() {
if [[ -n "${TUTTI_APP_VERSION:-}" ]]; then
log "desktop version ${TUTTI_APP_VERSION} (environment override)"
return
fi
TUTTI_APP_VERSION="$(
node "${DESKTOP_APP_DIR}/scripts/resolve-build-version.mjs"
)"
export TUTTI_APP_VERSION
log "desktop version ${TUTTI_APP_VERSION}"
}
configure_agent_extension_sources() {
local package_dir="${DEV_GUI_KIMI_CODE_PACKAGE_DIR:-}"
local helper_executable="${DEV_GUI_KIMI_CODE_ACCOUNT_USAGE_EXECUTABLE:-}"
local helper_candidate=""
local account_usage_profile=""
if [[ -z "${package_dir}" ]]; then
# A stale shell or launchd override must not silently shadow the signed
# remote release used by the shipped product.
unset TUTTI_AGENT_EXTENSION_KIMI_CODE_PACKAGE_DIR
unset TUTTI_AGENT_EXTENSION_KIMI_CODE_ACCOUNT_USAGE_EXECUTABLE
return
fi
[[ -d "${package_dir}" ]] || fail \
"local kimi-code agent extension directory does not exist: ${package_dir}"
[[ -f "${package_dir}/tutti.agent.json" ]] || fail \
"local kimi-code agent extension is not an unpacked package (missing tutti.agent.json): ${package_dir}"
package_dir="$(cd "${package_dir}" && pwd -P)"
export TUTTI_AGENT_EXTENSION_KIMI_CODE_PACKAGE_DIR="${package_dir}"
log "using local kimi-code agent extension at ${package_dir}"
if [[ -z "${helper_executable}" ]]; then
helper_candidate="${package_dir}/../../../packages/account-usage-probe/dist/cli.cjs"
if [[ -f "${helper_candidate}" && -x "${helper_candidate}" ]]; then
helper_executable="${helper_candidate}"
fi
fi
if [[ -z "${helper_executable}" ]]; then
account_usage_profile="$(
node -e '
const fs = require("node:fs");
const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
process.stdout.write(manifest.profiles?.accountUsage ?? "");
' "${package_dir}/tutti.agent.json"
)" || fail "could not inspect local kimi-code account usage profile"
if [[ -n "${account_usage_profile}" ]]; then
fail "local kimi-code account usage helper is missing; build it or set DEV_GUI_KIMI_CODE_ACCOUNT_USAGE_EXECUTABLE"
fi
unset TUTTI_AGENT_EXTENSION_KIMI_CODE_ACCOUNT_USAGE_EXECUTABLE
return
fi
[[ -f "${helper_executable}" && -x "${helper_executable}" ]] || fail \
"local kimi-code account usage helper is not executable: ${helper_executable}"
helper_executable="$(cd "$(dirname "${helper_executable}")" && pwd -P)/$(basename "${helper_executable}")"
export TUTTI_AGENT_EXTENSION_KIMI_CODE_ACCOUNT_USAGE_EXECUTABLE="${helper_executable}"
log "using local kimi-code account usage helper"
}
resolve_tuttid_binary_name() {
case "$(uname -s)" in
CYGWIN*|MINGW*|MSYS*)
printf 'tuttid.exe\n'
;;
*)
printf 'tuttid\n'
;;
esac
}
resolve_node_process_path() {
local candidate_path="$1"
if [[ "$(node -p 'process.platform')" != "win32" ]]; then
printf '%s\n' "${candidate_path}"
return
fi
if ! command_exists cygpath; then
fail "cygpath is required to launch Windows processes from the managed POSIX shell."
fi
cygpath -w -- "${candidate_path}"
}
ensure_workspace_dependencies() {
local installed_lockfile="${ROOT_DIR}/node_modules/.pnpm/lock.yaml"
local workspace_lockfile="${ROOT_DIR}/pnpm-lock.yaml"
if [[ -d "${ROOT_DIR}/node_modules" ]] && [[ -f "${installed_lockfile}" ]]; then
if cmp -s "${workspace_lockfile}" "${installed_lockfile}"; then
log "workspace dependencies already installed"
return
fi
log "workspace dependency lockfile changed; refreshing with pnpm"
else
log "installing workspace dependencies with pnpm"
fi
(
cd "${ROOT_DIR}"
pnpm install
)
}
prepare_tuttid_binary() {
local binary_name="$1"
local binary_path="${TUTTID_BIN_DIR}/${binary_name}"
mkdir -p "${TUTTID_BIN_DIR}"
log "generating builtin workspace apps"
(
cd "${ROOT_DIR}"
pnpm generate:builtin-apps
)
log "downloading daemon Go modules"
(
cd "${TUTTID_DIR}"
run_go mod download
)
log "building daemon dev binary at ${binary_path}"
(
cd "${TUTTID_DIR}"
run_go build -buildvcs=false -o "${binary_path}" .
)
TUTTID_BINARY_PATH="${binary_path}"
}
install_dev_cli() {
log "installing tutti-dev CLI"
(
cd "${ROOT_DIR}"
GO_BIN="${GO_BIN}" node ./tools/scripts/install-dev-cli.mjs
)
}
start_desktop_dev() {
local tuttid_bin_path="$1"
local node_tuttid_bin_path
local status
# why-did-you-render instruments every React component and hook. On a real
# workspace that work can block the renderer for seconds while AgentGUI
# restores its session directory, which also keeps the Workbench hydration
# barrier (and therefore the Dock) non-interactive. Keep it opt-in so the
# normal development path has production-like scheduling characteristics.
local why_did_you_render="${VITE_TUTTI_WHY_DID_YOU_RENDER:-0}"
node_tuttid_bin_path="$(resolve_node_process_path "${tuttid_bin_path}")"
log "starting desktop dev with prebuilt tuttid"
if [[ "${why_did_you_render}" == "1" ]]; then
log "why-did-you-render diagnostics enabled"
fi
prepare_dev_gui_runtime
DEV_GUI_DESKTOP_STARTED=1
(
cd "${ROOT_DIR}"
TUTTID_BIN="${node_tuttid_bin_path}" \
TUTTID_LOG_OUTPUT="${TUTTID_LOG_OUTPUT:-tee}" \
VITE_TUTTI_WHY_DID_YOU_RENDER="${why_did_you_render}" \
pnpm dev:desktop < /dev/null
) &
DEV_GUI_CHILD_PID="$!"
set +e
wait_for_desktop_dev_exit "${DEV_GUI_CHILD_PID}"
status="$?"
set -e
DEV_GUI_CHILD_PID=""
return "${status}"
}
main() {
local binary_name
check_runtime_prerequisites
configure_agent_extension_sources
configure_desktop_dev_version
ensure_workspace_dependencies
binary_name="$(resolve_tuttid_binary_name)"
prepare_tuttid_binary "${binary_name}"
install_dev_cli
if [[ "${DEV_GUI_SKIP_START:-0}" == "1" ]]; then
log "skipping desktop launch because DEV_GUI_SKIP_START=1"
return
fi
start_desktop_dev "${TUTTID_BINARY_PATH}"
}
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'cleanup_dev_gui' EXIT
main "$@"