diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..63df3afa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +** +!deploy/ +!deploy/remote-pwa/ +!deploy/remote-pwa/Dockerfile +!deploy/remote-pwa/nginx.conf +!frontend/ +!frontend/dist/ +!frontend/dist/** diff --git a/.github/workflows/deploy-remote-pwa-preview.yml b/.github/workflows/deploy-remote-pwa-preview.yml new file mode 100644 index 00000000..f7244e1b --- /dev/null +++ b/.github/workflows/deploy-remote-pwa-preview.yml @@ -0,0 +1,96 @@ +name: Deploy Remote PWA Preview + +on: + push: + branches: [nightly] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +env: + GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID || 'pane-pwa-preview' }} + GCP_REGION: ${{ vars.GCP_REGION || 'us-central1' }} + GAR_REPOSITORY: ${{ vars.GAR_REPOSITORY || 'pane-preview' }} + CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE || 'pane-remote-pwa-preview' }} + CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT: ${{ vars.CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT || '' }} + +jobs: + deploy: + name: Build and deploy preview + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.15.1' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build remote PWA assets + run: pnpm run build:frontend + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.GCP_DEPLOY_SERVICE_ACCOUNT }} + + - name: Setup Google Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure Artifact Registry Docker auth + run: gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet --project="${GCP_PROJECT_ID}" + + - name: Build and push container + env: + IMAGE_TAG: ${{ github.sha }} + run: | + set -euo pipefail + + image="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${GAR_REPOSITORY}/${CLOUD_RUN_SERVICE}:${IMAGE_TAG}" + docker build \ + --file deploy/remote-pwa/Dockerfile \ + --tag "$image" \ + . + docker push "$image" + echo "IMAGE=$image" >> "$GITHUB_ENV" + + - name: Deploy to Cloud Run + run: | + set -euo pipefail + + runtime_service_account="${CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT:-pane-remote-pwa-runtime@${GCP_PROJECT_ID}.iam.gserviceaccount.com}" + + gcloud run deploy "$CLOUD_RUN_SERVICE" \ + --project="$GCP_PROJECT_ID" \ + --region="$GCP_REGION" \ + --platform=managed \ + --image="$IMAGE" \ + --service-account="$runtime_service_account" \ + --no-invoker-iam-check \ + --min-instances=0 \ + --max-instances=1 \ + --cpu=1 \ + --memory=256Mi \ + --port=8080 \ + --set-env-vars="PANE_REMOTE_PWA_PREVIEW_COMMIT=${GITHUB_SHA},PANE_REMOTE_PWA_PREVIEW_REF=${GITHUB_REF_NAME}" \ + --quiet + + - name: Print service URL + run: | + gcloud run services describe "$CLOUD_RUN_SERVICE" \ + --project="$GCP_PROJECT_ID" \ + --region="$GCP_REGION" \ + --platform=managed \ + --format='value(status.url)' diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml new file mode 100644 index 00000000..6ca54413 --- /dev/null +++ b/.github/workflows/nightly-release.yml @@ -0,0 +1,239 @@ +name: Nightly / Canary Release + +on: + workflow_dispatch: + inputs: + channel: + description: 'Release channel' + type: choice + required: true + default: nightly + options: + - nightly + - canary + source_ref: + description: 'Branch, tag, or SHA to build' + type: string + required: true + default: nightly + +permissions: + contents: write + +jobs: + metadata: + runs-on: ubuntu-latest + outputs: + channel: ${{ steps.meta.outputs.channel }} + source_ref: ${{ steps.meta.outputs.source_ref }} + source_sha: ${{ steps.meta.outputs.source_sha }} + short_sha: ${{ steps.meta.outputs.short_sha }} + version: ${{ steps.meta.outputs.version }} + tag: ${{ steps.meta.outputs.tag }} + title: ${{ steps.meta.outputs.title }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.source_ref }} + fetch-depth: 0 + + - id: meta + shell: bash + run: | + set -euo pipefail + + channel="${{ inputs.channel }}" + source_ref="${{ inputs.source_ref }}" + base_version="$(node -p "require('./package.json').version.replace(/-.*/, '')")" + source_sha="$(git rev-parse HEAD)" + short_sha="$(git rev-parse --short=7 HEAD)" + timestamp="$(date -u +%Y%m%d%H%M)" + version="${base_version}-${channel}.${timestamp}.${short_sha}.${{ github.run_number }}.${{ github.run_attempt }}" + tag="v${version}" + title="Pane ${channel^} ${version}" + + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]]; then + echo "Generated version is not valid semver: $version" >&2 + exit 1 + fi + + { + echo "channel=$channel" + echo "source_ref=$source_ref" + echo "source_sha=$source_sha" + echo "short_sha=$short_sha" + echo "version=$version" + echo "tag=$tag" + echo "title=$title" + } >> "$GITHUB_OUTPUT" + + build: + needs: metadata + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + command: build:mac:ci + artifact_name: pane-macos + artifact_path: | + dist-electron/*.dmg + - os: ubuntu-latest + command: build:linux:ci + artifact_name: pane-linux + artifact_path: | + dist-electron/*.deb + dist-electron/*.AppImage + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.metadata.outputs.source_sha }} + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22.15.1' + cache: 'pnpm' + + - uses: actions/cache@v4 + with: + path: | + ~/.cache/electron + ~/.cache/electron-builder + key: ${{ runner.os }}-electron-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: ${{ runner.os }}-electron- + + - run: pnpm install --frozen-lockfile + + - name: Stamp prerelease version + env: + NIGHTLY_VERSION: ${{ needs.metadata.outputs.version }} + run: | + node - <<'NODE' + const fs = require('fs'); + const path = require('path'); + const packagePath = path.resolve('package.json'); + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + packageJson.version = process.env.NIGHTLY_VERSION; + fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); + NODE + + - name: Build ${{ matrix.os }} package + env: + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + CSC_DISABLE: 'true' + run: pnpm run ${{ matrix.command }} + + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }}-${{ needs.metadata.outputs.channel }}-${{ needs.metadata.outputs.tag }} + path: ${{ matrix.artifact_path }} + if-no-files-found: error + retention-days: 30 + + build-windows: + needs: metadata + strategy: + fail-fast: false + matrix: + arch: + - x64 + - arm64 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.metadata.outputs.source_sha }} + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22.15.1' + cache: 'pnpm' + + - uses: actions/cache@v4 + with: + path: | + ~/AppData/Local/electron/Cache + ~/AppData/Local/electron-builder/Cache + key: windows-${{ matrix.arch }}-electron-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: windows-${{ matrix.arch }}-electron- + + - run: pnpm install --frozen-lockfile + + - name: Stamp prerelease version + env: + NIGHTLY_VERSION: ${{ needs.metadata.outputs.version }} + shell: bash + run: | + node - <<'NODE' + const fs = require('fs'); + const path = require('path'); + const packagePath = path.resolve('package.json'); + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + packageJson.version = process.env.NIGHTLY_VERSION; + fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); + NODE + + - name: Build Windows ${{ matrix.arch }} package + run: node scripts/build-win.js ${{ matrix.arch }} + + - uses: actions/upload-artifact@v4 + with: + name: pane-windows-${{ matrix.arch }}-${{ needs.metadata.outputs.channel }}-${{ needs.metadata.outputs.tag }} + path: | + dist-electron/*-Windows-${{ matrix.arch }}.exe + if-no-files-found: error + retention-days: 30 + + publish: + needs: + - metadata + - build + - build-windows + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + path: release-assets + merge-multiple: true + + - name: Publish prerelease + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + CHANNEL: ${{ needs.metadata.outputs.channel }} + SOURCE_REF: ${{ needs.metadata.outputs.source_ref }} + SOURCE_SHA: ${{ needs.metadata.outputs.source_sha }} + SHORT_SHA: ${{ needs.metadata.outputs.short_sha }} + VERSION: ${{ needs.metadata.outputs.version }} + TAG: ${{ needs.metadata.outputs.tag }} + TITLE: ${{ needs.metadata.outputs.title }} + shell: bash + run: | + set -euo pipefail + + find release-assets -maxdepth 1 -type f | sort + cat > release-notes.md < + - @git-diff-view/core (0.0.40) - MrWangJustToDo + - @git-diff-view/lowlight (0.0.40) - MrWangJustToDo + - @git-diff-view/shiki (0.0.40) - MrWangJustToDo + - @headless-tree/core (1.6.3) - Lukas Bach - @hono/node-server (1.19.9) - Yusuke Wada (https://github.com/yusukebe) - @iconify/types (2.0.0) - Vjacheslav Trushkin - @iconify/utils (3.0.2) - Vjacheslav Trushkin @@ -77,43 +70,35 @@ The following packages are licensed under the MIT license: - @jridgewell/sourcemap-codec (1.5.4) - Justin Ridgewell - @jridgewell/trace-mapping (0.3.29) - Justin Ridgewell - @lydell/node-pty (1.2.0-beta.3) - Simon Lydell - - @lydell/node-pty-win32-arm64 (1.2.0-beta.3) + - @lydell/node-pty-linux-arm64 (1.2.0-beta.3) - @lydell/node-pty-win32-x64 (1.2.0-beta.3) - @malept/flatpak-bundler (0.4.0) - Matt Watson - @mermaid-js/parser (0.6.2) - Yokozuna59 - @modelcontextprotocol/sdk (1.26.0) - Anthropic, PBC (https://anthropic.com) - @modelcontextprotocol/sdk (1.16.0) - Anthropic, PBC (https://anthropic.com) - @monaco-editor/loader (1.5.0) - Suren Atoyan - - @monaco-editor/react (4.7.0) - Suren Atoyan - - @msgpackr-extract/msgpackr-extract-win32-x64 (3.0.3) - Kris Zyp + - @msgpackr-extract/msgpackr-extract-linux-arm64 (3.0.3) - Kris Zyp - @nodelib/fs.scandir (2.1.5) - @nodelib/fs.stat (2.0.5) - @nodelib/fs.walk (1.2.8) - @npmcli/move-file (2.0.1) - GitHub Inc. - @pkgjs/parseargs (0.11.0) - @posthog/core (1.10.0) - - @posthog/core (1.5.2) + - @posthog/core (1.23.1) + - @posthog/types (1.356.1) - @radix-ui/number (1.1.1) - @radix-ui/primitive (1.1.2) - - @radix-ui/react-arrow (1.1.7) - - @radix-ui/react-checkbox (1.3.2) - - @radix-ui/react-collection (1.1.7) - - @radix-ui/react-context (1.1.2) - - @radix-ui/react-dialog (1.1.14) - - @radix-ui/react-direction (1.1.1) - - @radix-ui/react-label (2.1.7) - - @radix-ui/react-popper (1.2.7) - - @radix-ui/react-portal (1.1.9) - - @radix-ui/react-presence (1.1.4) - - @radix-ui/react-primitive (2.1.3) - - @radix-ui/react-select (2.2.5) - - @radix-ui/react-switch (1.2.5) - - @radix-ui/react-tabs (1.1.12) - - @radix-ui/react-use-rect (1.1.1) - - @radix-ui/react-use-size (1.1.1) - @radix-ui/rect (1.1.1) - @rolldown/pluginutils (1.0.0-beta.27) - - @rollup/rollup-win32-x64-msvc (4.45.1) - Lukas Taegert-Atkinson + - @rollup/rollup-linux-arm64-gnu (4.45.1) - Lukas Taegert-Atkinson + - @rollup/rollup-linux-arm64-musl (4.45.1) - Lukas Taegert-Atkinson + - @shikijs/core (3.23.0) - Pine Wu ; Anthony Fu + - @shikijs/engine-javascript (3.23.0) - Anthony Fu + - @shikijs/engine-oniguruma (3.23.0) - Anthony Fu + - @shikijs/langs (3.23.0) - Anthony Fu + - @shikijs/themes (3.23.0) - Anthony Fu + - @shikijs/types (3.23.0) - Anthony Fu + - @shikijs/vscode-textmate (10.0.2) - Microsoft Corporation - @sindresorhus/is (4.6.0) - Sindre Sorhus - @standard-schema/spec (1.1.0) - Colin McDonnell - @standard-schema/spec (1.0.0) - Colin McDonnell @@ -125,10 +110,16 @@ The following packages are licensed under the MIT license: - @vitest/snapshot (2.1.9) - @vitest/spy (2.1.9) - @vitest/utils (2.1.9) + - @vue/reactivity (3.5.29) - Evan You + - @vue/shared (3.5.29) - Evan You - @xmldom/xmldom (0.8.11) - @xmldom/xmldom (0.8.10) + - @xterm/addon-fit (0.11.0) - The xterm.js authors + - @xterm/addon-serialize (0.14.0) - The xterm.js authors + - @xterm/addon-unicode11 (0.9.0) - The xterm.js authors + - @xterm/addon-web-links (0.12.0) - The xterm.js authors - @xterm/addon-webgl (0.19.0) - The xterm.js authors - - @xterm/xterm (5.5.0) + - @xterm/xterm (6.0.0) - 7zip-bin (5.2.0) - accepts (2.0.0) - acorn (8.15.0) @@ -158,7 +149,6 @@ The following packages are licensed under the MIT license: - asynckit (0.4.0) - Alex Indigo - axios (1.13.5) - Matt Zabriskie - axios (1.12.2) - Matt Zabriskie - - babel-plugin-macros (3.1.0) - Kent C. Dodds (https://kentcdodds.com) - bail (2.0.2) - Titus Wormer (https://wooorm.com) - balanced-match (1.0.2) - Julian Gruber - base64-js (1.5.1) - T. Jameson Little @@ -199,10 +189,10 @@ The following packages are licensed under the MIT license: - check-error (2.1.1) - Jake Luer (http://alogicalparadox.com) - chevrotain-allstar (0.3.1) - TypeFox - chokidar (3.6.0) - Paul Miller (https://paulmillr.com) + - chokidar (5.0.0) - Paul Miller (https://paulmillr.com) - chromium-pickle-js (0.2.0) - ci-info (4.4.0) - Thomas Watson Steen (https://twitter.com/wa7son) - ci-info (4.3.0) - Thomas Watson Steen (https://twitter.com/wa7son) - - classnames (2.5.1) - Jed Watson - clean-stack (2.2.0) - Sindre Sorhus - cli-cursor (3.1.0) - Sindre Sorhus - cli-spinners (2.9.2) - Sindre Sorhus @@ -225,15 +215,14 @@ The following packages are licensed under the MIT license: - content-disposition (1.0.1) - Douglas Christopher Wilson - content-disposition (1.0.0) - Douglas Christopher Wilson - content-type (1.0.5) - Douglas Christopher Wilson - - convert-source-map (1.9.0) - Thorsten Lorenz - convert-source-map (2.0.0) - Thorsten Lorenz - cookie (0.7.2) - Roman Shtylman - cookie-signature (1.2.2) - TJ Holowaychuk + - core-js (3.48.0) - Denis Pushkarev - cors (2.8.6) - Troy Goode (https://github.com/troygoode/) - cors (2.8.5) - Troy Goode (https://github.com/troygoode/) - cose-base (1.0.3) - cose-base (2.2.0) - - cosmiconfig (7.1.0) - David Clark - cron-parser (4.9.0) - Harri Siirak - cross-dirname (0.1.0) - cross-spawn (7.0.6) - André Cruz @@ -286,7 +275,6 @@ The following packages are licensed under the MIT license: - env-paths (2.2.1) - Sindre Sorhus - env-paths (3.0.0) - Sindre Sorhus - err-code (2.0.3) - IndigoUnited (http://indigounited.com) - - error-ex (1.3.2) - es-define-property (1.0.1) - Jordan Harband - es-errors (1.3.0) - Jordan Harband - es-module-lexer (1.7.0) - Guy Bedford @@ -319,12 +307,12 @@ The following packages are licensed under the MIT license: - fd-slicer (1.1.0) - Andrew Kelley - fdir (6.5.0) - thecodrr - fdir (6.4.6) - thecodrr + - fflate (0.4.8) - Arjun Barrett - file-entry-cache (8.0.0) - Jared Wray - file-uri-to-path (1.0.0) - Nathan Rajlich (http://n8.io/) - fill-range (7.1.1) - Jon Schlinkert (https://github.com/jonschlinkert) - finalhandler (2.1.1) - Douglas Christopher Wilson - finalhandler (2.1.0) - Douglas Christopher Wilson - - find-root (1.1.0) - jsdnxx - find-up (5.0.0) - Sindre Sorhus - flat-cache (4.0.1) - Jared Wray - follow-redirects (1.15.11) - Ruben Verborgh (https://ruben.verborgh.org/) @@ -358,11 +346,13 @@ The following packages are licensed under the MIT license: - has-symbols (1.1.0) - Jordan Harband - has-tostringtag (1.0.2) - Jordan Harband - hasown (2.0.2) - Jordan Harband + - hast-util-to-html (9.0.5) - Titus Wormer (https://wooorm.com) - hast-util-to-jsx-runtime (2.3.6) - Titus Wormer (https://wooorm.com) - hast-util-whitespace (3.0.0) - Titus Wormer (https://wooorm.com) - hono (4.11.9) - Yusuke Wada (https://github.com/yusukebe) - html-escaper (2.0.2) - Andrea Giammarchi - html-url-attributes (3.0.1) - Titus Wormer (https://wooorm.com) + - html-void-elements (3.0.0) - Titus Wormer (https://wooorm.com) - http-errors (2.0.1) - Jonathan Ong (http://jongleberry.com) - http-errors (2.0.0) - Jonathan Ong (http://jongleberry.com) - http-proxy-agent (7.0.2) - Nathan Rajlich (http://n8.io/) @@ -389,7 +379,6 @@ The following packages are licensed under the MIT license: - ipaddr.js (1.9.1) - whitequark - is-alphabetical (2.0.1) - Titus Wormer (https://wooorm.com) - is-alphanumerical (2.0.1) - Titus Wormer (https://wooorm.com) - - is-arrayish (0.2.1) - Qix (http://github.com/qix-) - is-binary-path (2.1.0) - Sindre Sorhus - is-core-module (2.16.1) - Jordan Harband - is-decimal (2.0.1) - Titus Wormer (https://wooorm.com) @@ -417,7 +406,6 @@ The following packages are licensed under the MIT license: - jsbn (1.1.0) - Tom Wu - jsesc (3.1.0) - Mathias Bynens - json-buffer (3.0.1) - Dominic Tarr (http://dominictarr.com) - - json-parse-even-better-errors (2.3.1) - Kat Marchán - json-schema-traverse (1.0.0) - Evgeny Poberezkin - json-schema-traverse (0.4.1) - Evgeny Poberezkin - json-stable-stringify-without-jsonify (1.0.1) - James Halliday @@ -446,9 +434,9 @@ The following packages are licensed under the MIT license: - lodash.merge (4.6.2) - John-David Dalton - log-symbols (4.1.0) - Sindre Sorhus - longest-streak (3.1.0) - Titus Wormer (https://wooorm.com) - - loose-envify (1.4.0) - Andres Suarez - loupe (3.1.4) - Veselin Todorov - lowercase-keys (2.0.0) - Sindre Sorhus + - lowlight (3.3.0) - Titus Wormer (https://wooorm.com) - luxon (3.7.2) - Isaac Cambron - luxon (3.7.1) - Isaac Cambron - magic-string (0.30.17) - Rich Harris @@ -474,7 +462,6 @@ The following packages are licensed under the MIT license: - mdast-util-to-markdown (2.1.2) - Titus Wormer (https://wooorm.com) - mdast-util-to-string (4.0.0) - Titus Wormer (https://wooorm.com) - media-typer (1.1.0) - Douglas Christopher Wilson - - memoize-one (6.0.0) - Alex Reardon - merge-descriptors (2.0.0) - merge2 (1.4.1) - mermaid (11.11.0) - Knut Sveidqvist @@ -552,6 +539,8 @@ The following packages are licensed under the MIT license: - object-keys (1.1.1) - Jordan Harband - on-finished (2.4.1) - onetime (5.1.2) - Sindre Sorhus + - oniguruma-parser (0.12.1) - Steven Levithan + - oniguruma-to-es (4.3.4) - Steven Levithan - optionator (0.9.4) - George Zahariev - ora (5.4.1) - Sindre Sorhus - p-cancelable (2.1.1) - Sindre Sorhus @@ -564,7 +553,6 @@ The following packages are licensed under the MIT license: - package-manager-detector (1.3.0) - Anthony Fu - parent-module (1.0.1) - Sindre Sorhus - parse-entities (4.0.2) - Titus Wormer (https://wooorm.com) - - parse-json (5.2.0) - Sindre Sorhus - parseurl (1.3.3) - path-data-parser (0.1.0) - Preet Shihn - path-exists (4.0.0) - Sindre Sorhus @@ -574,7 +562,6 @@ The following packages are licensed under the MIT license: - path-parse (1.0.7) - Javier Blanco - path-to-regexp (8.3.0) - path-to-regexp (8.2.0) - - path-type (4.0.0) - Sindre Sorhus - pathe (1.1.2) - pathe (2.0.3) - pathval (2.0.1) - Veselin Todorov @@ -594,13 +581,12 @@ The following packages are licensed under the MIT license: - pngjs (7.0.0) - points-on-curve (0.2.0) - Preet Shihn - points-on-path (0.2.1) - Preet Shihn - - posthog-node (5.11.2) - PostHog - postject (1.0.0-alpha.6) + - preact (10.28.4) - prebuild-install (7.1.3) - Mathias Buus (@mafintosh) - prelude-ls (1.2.1) - George Zahariev - progress (2.0.3) - TJ Holowaychuk - promise-retry (2.0.1) - IndigoUnited (http://indigounited.com) - - prop-types (15.8.1) - proper-lockfile (4.1.2) - André Cruz - property-information (7.1.0) - Titus Wormer (https://wooorm.com) - proxy-addr (2.0.7) - Douglas Christopher Wilson @@ -608,6 +594,7 @@ The following packages are licensed under the MIT license: - pump (3.0.3) - Mathias Buus Madsen - punycode (2.3.1) - Mathias Bynens - quansync (0.2.10) - Anthony Fu + - query-selector-shadow-dom (1.0.1) - George Griffiths - queue-microtask (1.2.3) - Feross Aboukhadijeh - quick-lru (5.1.1) - Sindre Sorhus - range-parser (1.2.1) - TJ Holowaychuk (http://tjholowaychuk.com) @@ -616,16 +603,19 @@ The following packages are licensed under the MIT license: - react (19.1.0) - react-dom (19.1.0) - react-error-boundary (6.0.0) - Brian Vaughn - - react-is (16.13.1) - react-json-view-lite (1.5.0) - AnyRoad - react-refresh (0.17.0) - - react-remove-scroll-bar (2.3.8) - Anton Korzunov + - reactivity-store (0.3.12) - MrWangJustToDo - read-binary-file-arch (1.0.6) - Samuel Maddock - read-cache (1.0.0) - Bogdan Chadkin - readable-stream (3.6.2) - readdirp (3.6.0) - Thorsten Lorenz (thlorenz.com) + - readdirp (5.0.0) - Thorsten Lorenz (thlorenz.com) - redis-errors (1.2.0) - Ruben Bridgewater - redis-parser (3.0.0) - Ruben Bridgewater + - regex (6.1.0) - Steven Levithan + - regex-recursion (6.0.2) - Steven Levithan + - regex-utilities (2.3.0) - Steven Levithan - remark-gfm (4.0.1) - Titus Wormer (https://wooorm.com) - remark-parse (11.0.0) - Titus Wormer (https://wooorm.com) - remark-rehype (11.1.2) - Titus Wormer (https://wooorm.com) @@ -658,6 +648,7 @@ The following packages are licensed under the MIT license: - shebang-regex (3.0.0) - Sindre Sorhus - shebang-regex (1.0.0) - Sindre Sorhus - shell-quote (1.8.3) - James Halliday + - shiki (3.23.0) - Pine Wu ; Anthony Fu - shx (0.4.0) - side-channel (1.1.0) - Jordan Harband - side-channel-list (1.0.0) - Jordan Harband @@ -691,7 +682,6 @@ The following packages are licensed under the MIT license: - strip-json-comments (3.1.1) - Sindre Sorhus - style-to-js (1.1.17) - Mark - style-to-object (1.0.9) - Mark - - stylis (4.2.0) - Sultan Tarimo - stylis (4.3.6) - Sultan Tarimo - sucrase (3.35.0) - Alan Pierce - supports-color (8.1.1) - Sindre Sorhus @@ -741,6 +731,7 @@ The following packages are licensed under the MIT license: - universalify (2.0.1) - Ryan Zimmerman - unpipe (1.0.0) - Douglas Christopher Wilson - update-browserslist-db (1.1.3) - Andrey Sitnik + - use-sync-external-store (1.6.0) - util-deprecate (1.0.2) - Nathan Rajlich (http://n8.io/) - uuid (8.3.2) - uuid (11.1.0) @@ -761,6 +752,7 @@ The following packages are licensed under the MIT license: - word-wrap (1.2.5) - Jon Schlinkert (https://github.com/jonschlinkert) - wrap-ansi (7.0.0) - Sindre Sorhus - wrap-ansi (8.1.0) - Sindre Sorhus + - ws (8.20.1) - Einar Otto Stangvik (http://2x.io) - xmlbuilder (15.1.1) - Ozgur Ozcitak - yargs (17.7.2) - yauzl (2.10.0) - Josh Wolfe @@ -907,7 +899,6 @@ The following packages are licensed under the ISC license: - y18n (5.0.8) - Ben Coe - yallist (4.0.0) - Isaac Z. Schlueter (http://blog.izs.me/) - yallist (3.1.1) - Isaac Z. Schlueter (http://blog.izs.me/) - - yaml (1.10.2) - Eemeli Aro - yaml (2.8.0) - Eemeli Aro - yargs-parser (21.1.1) - Ben Coe - zod-to-json-schema (3.25.1) - Stefan Terdell @@ -988,7 +979,7 @@ Package: @hapi/pinpoint Version: 2.0.1 Repository: git://github.com/hapijs/pinpoint -Copyright (c) 2019-2020, Sideway. Inc, and project contributors +Copyright (c) 2019-2020, Sideway. Inc, and project contributors All rights reserved. @@ -1051,6 +1042,346 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -------------------------------------------------------------------------------- +Package: @protobufjs/aspromise +Version: 1.1.2 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/base64 +Version: 1.1.2 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/codegen +Version: 2.0.4 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/eventemitter +Version: 1.1.0 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/fetch +Version: 1.1.0 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/float +Version: 1.0.2 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/inquire +Version: 1.1.0 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/path +Version: 1.1.2 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/pool +Version: 1.1.0 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Package: @protobufjs/utf8 +Version: 1.1.0 +Author: Daniel Wirtz +Repository: https://github.com/dcodeIO/protobuf.js.git + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + Package: d3-array Version: 2.12.1 Author: Mike Bostock @@ -1227,42 +1558,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- -Package: diff -Version: 5.2.0 -Repository: git://github.com/kpdecker/jsdiff.git - -BSD 3-Clause License - -Copyright (c) 2009-2015, Kevin Decker -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------------------- - Package: esquery Version: 1.6.0 Author: Joel Feenstra @@ -1405,6 +1700,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- +Package: highlight.js +Version: 11.11.1 +Author: Josh Goebel +Homepage: https://highlightjs.org/ + +BSD 3-Clause License + +Copyright (c) 2006, Ivan Sagalaev. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + Package: ieee754 Version: 1.2.1 Author: Feross Aboukhadijeh @@ -1588,6 +1920,53 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -------------------------------------------------------------------------------- +Package: protobufjs +Version: 7.5.4 +Author: Daniel Wirtz +Homepage: https://protobufjs.github.io/protobuf.js/ + +This license applies to all parts of protobuf.js except those files +either explicitly including or referencing a different license or +located in a directory containing a different LICENSE file. + +--- + +Copyright (c) 2016, Daniel Wirtz All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +Code generated by the command line utilities is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +-------------------------------------------------------------------------------- + Package: qs Version: 6.14.1 Homepage: https://github.com/ljharb/qs @@ -1797,41 +2176,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- -Package: source-map -Version: 0.5.7 -Author: Nick Fitzgerald -Homepage: https://github.com/mozilla/source-map - -Copyright (c) 2009-2011, Mozilla Foundation and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the names of the Mozilla Foundation nor the names of project - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------------------- - Package: source-map-js Version: 1.2.1 Author: Valentin 7rulnik Semirulnik @@ -1916,7 +2260,14 @@ The following packages are licensed under the Apache-2.0 license: - @humanwhocodes/module-importer (1.0.1) - Nicholas C. Zaks - @humanwhocodes/retry (0.3.1) - Nicholas C. Zaks - @humanwhocodes/retry (0.4.3) - Nicholas C. Zaks + - @img/sharp-linux-arm64 (0.33.5) - Lovell Fuller + - @img/sharp-linux-arm64 (0.34.5) - Lovell Fuller + - @img/sharp-linuxmusl-arm64 (0.33.5) - Lovell Fuller + - @img/sharp-linuxmusl-arm64 (0.34.5) - Lovell Fuller - @malept/cross-spawn-promise (2.0.0) - Mark Lee + - @opentelemetry/api (1.9.0) - OpenTelemetry Authors + - @opentelemetry/api-logs (0.208.0) - OpenTelemetry Authors + - @opentelemetry/semantic-conventions (1.40.0) - OpenTelemetry Authors - chevrotain (11.0.3) - Shahar Soel - class-variance-authority (0.7.1) - Joe Bell (https://joebell.co.uk) - cluster-key-slot (1.1.2) - Mike Diarmid @@ -1928,15 +2279,18 @@ The following packages are licensed under the Apache-2.0 license: - expect-type (1.2.2) - exponential-backoff (3.1.3) - Sami Sayegh - exponential-backoff (3.1.2) - Sami Sayegh + - fast-diff (1.3.0) - Jason Chen - filelist (1.0.4) - Matthew Eernisse (http://fleegix.org) - jake (10.9.4) - Matthew Eernisse (http://fleegix.org) - jake (10.9.2) - Matthew Eernisse (http://fleegix.org) + - long (5.3.2) - Daniel Wirtz - openai (5.10.1) - OpenAI - rxjs (7.8.2) - Ben Lesh - sharp (0.34.5) - Lovell Fuller - sumchecker (3.0.1) - Mark Lee - ts-interface-checker (0.1.13) - Dmitry S, Grist Labs - tunnel-agent (0.6.0) - Mikeal Rogers (http://www.futurealoof.com) + - web-vitals (5.1.0) - Philip Walton Apache License Version 2.0, January 2004 @@ -3041,18 +3395,58 @@ software or this license, under any kind of legal claim.*** -------------------------------------------------------------------------------- ================================================================================ -## Unknown LICENSE +## LGPL-3.0-or-later LICENSE ================================================================================ -Package: atomically -Version: 2.0.3 -Repository: github:fabiospampinato/atomically +Package: @img/sharp-libvips-linux-arm64 +Version: 1.0.4 +Author: Lovell Fuller +Homepage: https://sharp.pixelplumbing.com -The MIT License (MIT) +License: LGPL-3.0-or-later -Copyright (c) 2020-present Fabio Spampinato +-------------------------------------------------------------------------------- -Permission is hereby granted, free of charge, to any person obtaining a +Package: @img/sharp-libvips-linux-arm64 +Version: 1.2.4 +Author: Lovell Fuller +Homepage: https://sharp.pixelplumbing.com + +License: LGPL-3.0-or-later + +-------------------------------------------------------------------------------- + +Package: @img/sharp-libvips-linuxmusl-arm64 +Version: 1.0.4 +Author: Lovell Fuller +Homepage: https://sharp.pixelplumbing.com + +License: LGPL-3.0-or-later + +-------------------------------------------------------------------------------- + +Package: @img/sharp-libvips-linuxmusl-arm64 +Version: 1.2.4 +Author: Lovell Fuller +Homepage: https://sharp.pixelplumbing.com + +License: LGPL-3.0-or-later + +-------------------------------------------------------------------------------- + +================================================================================ +## Unknown LICENSE +================================================================================ + +Package: atomically +Version: 2.0.3 +Repository: github:fabiospampinato/atomically + +The MIT License (MIT) + +Copyright (c) 2020-present Fabio Spampinato + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, @@ -3158,466 +3552,6 @@ DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- -================================================================================ -## Apache-2.0 AND LGPL-3.0-or-later LICENSE -================================================================================ - -Package: @img/sharp-win32-x64 -Version: 0.33.5 -Author: Lovell Fuller -Homepage: https://sharp.pixelplumbing.com - -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --------------------------------------------------------------------------------- - -Package: @img/sharp-win32-x64 -Version: 0.34.5 -Author: Lovell Fuller -Homepage: https://sharp.pixelplumbing.com - -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --------------------------------------------------------------------------------- - -================================================================================ -## SEE LICENSE IN README.md LICENSE -================================================================================ - -Package: @anthropic-ai/claude-code -Version: 2.1.39 -Author: Anthropic -Homepage: https://github.com/anthropics/claude-code - -© Anthropic PBC. All rights reserved. Use is subject to the Legal Agreements outlined here: https://code.claude.com/docs/en/legal-and-compliance. - --------------------------------------------------------------------------------- - -Package: @anthropic-ai/claude-code -Version: 2.0.0 -Author: Anthropic -Homepage: https://github.com/anthropics/claude-code - -© Anthropic PBC. All rights reserved. Use is subject to Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms). - --------------------------------------------------------------------------------- - -================================================================================ -## (BSD-2-Clause OR MIT OR Apache-2.0) LICENSE -================================================================================ - -Package: rc -Version: 1.2.8 -Author: Dominic Tarr (dominictarr.com) -Repository: https://github.com/dominictarr/rc.git - -The MIT License - -Copyright (c) 2011 Dominic Tarr - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - --------------------------------------------------------------------------------- - ================================================================================ ## (MPL-2.0 OR Apache-2.0) LICENSE ================================================================================ @@ -4197,116 +4131,749 @@ Exhibit B - “Incompatible With Secondary Licenses” Notice -------------------------------------------------------------------------------- -================================================================================ -## CC-BY-4.0 LICENSE -================================================================================ - -Package: caniuse-lite -Version: 1.0.30001727 -Author: Ben Briggs -Repository: browserslist/caniuse-lite - -Attribution 4.0 International +Package: dompurify +Version: 3.3.1 +Author: Dr.-Ing. Mario Heiderich, Cure53 (https://cure53.de/) +Homepage: https://github.com/cure53/DOMPurify -======================================================================= +DOMPurify +Copyright 2025 Dr.-Ing. Mario Heiderich, Cure53 -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. +DOMPurify is free software; you can redistribute it and/or modify it under the +terms of either: -Using Creative Commons Public Licenses +a) the Apache License Version 2.0, or +b) the Mozilla Public License Version 2.0 -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. +----------------------------------------------------------------------------- - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -======================================================================= + 1. Definitions. -Creative Commons Attribution 4.0 International Public License + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution 4.0 International Public License ("Public License"). To the -extent this Public License may be interpreted as a contract, You are -granted the Licensed Rights in consideration of Your acceptance of -these terms and conditions, and the Licensor grants You such rights in -consideration of benefits the Licensor receives from making the -Licensed Material available under these terms and conditions. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -Section 1 -- Definitions. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. - c. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). - d. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. - e. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +----------------------------------------------------------------------------- +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + +-------------------------------------------------------------------------------- + +================================================================================ +## SEE LICENSE IN README.md LICENSE +================================================================================ + +Package: @anthropic-ai/claude-code +Version: 2.1.39 +Author: Anthropic +Homepage: https://github.com/anthropics/claude-code + +© Anthropic PBC. All rights reserved. Use is subject to the Legal Agreements outlined here: https://code.claude.com/docs/en/legal-and-compliance. + +-------------------------------------------------------------------------------- + +Package: @anthropic-ai/claude-code +Version: 2.0.0 +Author: Anthropic +Homepage: https://github.com/anthropics/claude-code + +© Anthropic PBC. All rights reserved. Use is subject to Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms). + +-------------------------------------------------------------------------------- + +================================================================================ +## (BSD-2-Clause OR MIT OR Apache-2.0) LICENSE +================================================================================ + +Package: rc +Version: 1.2.8 +Author: Dominic Tarr (dominictarr.com) +Repository: https://github.com/dominictarr/rc.git + +The MIT License + +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +================================================================================ +## CC-BY-4.0 LICENSE +================================================================================ + +Package: caniuse-lite +Version: 1.0.30001727 +Author: Ben Briggs +Repository: browserslist/caniuse-lite + +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public @@ -4869,6 +5436,317 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- +================================================================================ +## SEE LICENSE IN LICENSE LICENSE +================================================================================ + +Package: posthog-js +Version: 1.356.1 +Author: hey@posthog.com +Homepage: https://posthog.com/docs/libraries/js + +Copyright 2020 Posthog / Hiberly, Inc. + +Copyright 2015 Mixpanel, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this work except in compliance with the License. +You may obtain a copy of the License below, or at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- + +Some files in this codebase contain code from getsentry/sentry-javascript or getsentry/sentry-react-native by Software, Inc. dba Sentry. +In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases. + +MIT License + +Copyright (c) 2012 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Some files in this codebase contain code from facebook/metro by Meta Platforms, Inc. and affiliates. +In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases. + +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Some files in this codebase contain code from expo/expo by 650 Industries, Inc. (aka Expo). +In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases. + +The MIT License (MIT) + +Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + ================================================================================ ## Unlicense LICENSE ================================================================================ @@ -4906,11 +5784,11 @@ For more information, please refer to -------------------------------------------------------------------------------- ================================================================================ -## FOOZOL LICENSE +## PANE LICENSE ================================================================================ -Package: foozol -Version: 0.0.1 +Package: Pane +Version: 1.1.146 Author: [object Object] License: AGPL-3.0 diff --git a/cloud/scripts/setup-cloud.sh b/cloud/scripts/setup-cloud.sh index 30bd52e4..e4f6bc45 100755 --- a/cloud/scripts/setup-cloud.sh +++ b/cloud/scripts/setup-cloud.sh @@ -64,6 +64,19 @@ prompt_yes_no() { [[ "$yn" =~ ^[Yy] ]] } +normalize_bool() { + local value + value="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" + case "$value" in + 1|true|yes|on) + echo "true" + ;; + *) + echo "false" + ;; + esac +} + # ============================================================ # Resolve script directory (works from any cwd) # ============================================================ @@ -113,6 +126,27 @@ get_pane_config_dir() { # Set config path early so it's available throughout the script PANE_CONFIG=$(get_pane_config_path) PANE_CONFIG_DIR=$(get_pane_config_dir) +HOSTED_TUNNEL_PORT=8080 +HOSTED_DAEMON_BASE_URL="http://127.0.0.1:${HOSTED_TUNNEL_PORT}/daemon/" +HOSTED_DAEMON_PORT=42137 +HOSTED_REMOTE_PROFILE_ID="" +HOSTED_REMOTE_PROFILE_LABEL="Pane Cloud Workspace" +HOSTED_REMOTE_PROFILE_TOKEN="" +HOSTED_DAEMON_STATUS="unknown" +HOSTED_ALLOW_NOVNC_FALLBACK="false" +CURRENT_GCP_TOKEN="" + +read_existing_remote_profile_token() { + local profile_id="$1" + + if [ ! -f "$PANE_CONFIG" ] || ! command -v jq &>/dev/null || [ -z "$profile_id" ]; then + return + fi + + jq -r --arg profileId "$profile_id" ' + .remoteDaemon.client.profiles[]? | select(.id == $profileId) | .token + ' "$PANE_CONFIG" 2>/dev/null | head -1 +} # ============================================================ # Incremental config save function @@ -145,6 +179,28 @@ save_cloud_config() { region=$(echo "$zone" | sed 's/-[a-z]$//') fi + local allow_novnc_json="false" + if [ "$HOSTED_ALLOW_NOVNC_FALLBACK" = "true" ]; then + allow_novnc_json="true" + fi + + local remote_profile_json='null' + if [ -n "$HOSTED_REMOTE_PROFILE_ID" ] && [ -n "$HOSTED_REMOTE_PROFILE_TOKEN" ] && [ -n "$HOSTED_DAEMON_BASE_URL" ]; then + remote_profile_json="$(jq -cn \ + --arg id "$HOSTED_REMOTE_PROFILE_ID" \ + --arg label "$HOSTED_REMOTE_PROFILE_LABEL" \ + --arg baseUrl "$HOSTED_DAEMON_BASE_URL" \ + --arg token "$HOSTED_REMOTE_PROFILE_TOKEN" \ + '{ + id: $id, + label: $label, + baseUrl: $baseUrl, + token: $token, + transport: "http+sse" + }' + )" + fi + # Update config with current values (only non-empty ones) # Use canonical key names (projectId, zone) to match what the app reads local tmp_config="${PANE_CONFIG}.tmp" @@ -154,15 +210,44 @@ save_cloud_config() { --arg region "$region" \ --arg serverId "$server_id" \ --arg vncPassword "$vnc_password" \ - --arg tunnelPort "$tunnel_port" \ - '.cloud = (.cloud // {}) | .cloud.provider = $provider + --arg daemonBaseUrl "$HOSTED_DAEMON_BASE_URL" \ + --arg linkedRemoteProfileId "$HOSTED_REMOTE_PROFILE_ID" \ + --arg daemonStatus "$HOSTED_DAEMON_STATUS" \ + --arg apiToken "$CURRENT_GCP_TOKEN" \ + --argjson tunnelPort "${tunnel_port:-8080}" \ + --argjson allowNoVncFallback "$allow_novnc_json" \ + --argjson remoteProfile "$remote_profile_json" \ + ' + .cloud = (.cloud // {}) | .cloud.provider = $provider | if $projectId != "" then .cloud.projectId = $projectId else . end | if $zone != "" then .cloud.zone = $zone else . end | if $region != "" then .cloud.region = $region else . end | if $serverId != "" then .cloud.serverId = $serverId else . end | if $vncPassword != "" then .cloud.vncPassword = $vncPassword else . end + | if $apiToken != "" then .cloud.apiToken = $apiToken else . end + | if $daemonBaseUrl != "" then .cloud.daemonBaseUrl = $daemonBaseUrl else . end + | if $linkedRemoteProfileId != "" then .cloud.linkedRemoteProfileId = $linkedRemoteProfileId else . end + | if $daemonStatus != "" then .cloud.daemonStatus = $daemonStatus else . end + | .cloud.preferredAccess = "daemon" + | .cloud.allowNoVncFallback = $allowNoVncFallback | .cloud.tunnelPort = $tunnelPort - | .cloud.serverIp = ""' \ + | .cloud.serverIp = "" + | .remoteDaemon = (.remoteDaemon // {}) + | .remoteDaemon.client = (.remoteDaemon.client // { + profiles: [], + activeProfileId: null, + mode: "local" + }) + | if $remoteProfile != null + then .remoteDaemon.client.profiles = ( + [((.remoteDaemon.client.profiles // [])[] | select(.id != $remoteProfile.id))] + + [$remoteProfile] + ) + else . + end + | .remoteDaemon.client.activeProfileId = (.remoteDaemon.client.activeProfileId // null) + | .remoteDaemon.client.mode = (.remoteDaemon.client.mode // "local") + ' \ "$PANE_CONFIG" > "$tmp_config" && mv "$tmp_config" "$PANE_CONFIG" } @@ -358,6 +443,17 @@ if [ "$DESTROY_MODE" = true ]; then # Get project ID from state for cleanup PROJECT_ID=$(terraform -chdir="$TERRAFORM_DIR" output -raw project_id 2>/dev/null || echo "") + INSTANCE_NAME=$(terraform -chdir="$TERRAFORM_DIR" output -raw instance_name 2>/dev/null || echo "") + STATE_USER_ID="${INSTANCE_NAME#pane-}" + if [ -z "$STATE_USER_ID" ] || [ "$STATE_USER_ID" = "$INSTANCE_NAME" ]; then + STATE_USER_ID="${PROJECT_ID#pane-cloud-}" + fi + VNC_PASSWORD=$(terraform -chdir="$TERRAFORM_DIR" output -raw vnc_password 2>/dev/null || echo "destroy-placeholder") + HOSTED_REMOTE_PROFILE_ID=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_id 2>/dev/null || echo "cloud-${STATE_USER_ID}") + HOSTED_REMOTE_PROFILE_LABEL=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_label 2>/dev/null || echo "Pane Cloud Workspace") + HOSTED_REMOTE_PROFILE_TOKEN=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_token 2>/dev/null || echo "destroy-placeholder") + HOSTED_DAEMON_PORT=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_daemon_port 2>/dev/null || echo "42137") + HOSTED_ALLOW_NOVNC_FALLBACK=$(normalize_bool "$(terraform -chdir="$TERRAFORM_DIR" output -raw novnc_fallback_enabled 2>/dev/null || echo "false")") if ! prompt_yes_no "Are you sure you want to destroy the cloud infrastructure?" "n"; then info "Aborted." @@ -367,7 +463,16 @@ if [ "$DESTROY_MODE" = true ]; then info "Running terraform destroy..." cd "$TERRAFORM_DIR" terraform init -input=false >/dev/null 2>&1 - terraform destroy -auto-approve + terraform destroy \ + -var="project_id=${PROJECT_ID}" \ + -var="user_id=${STATE_USER_ID}" \ + -var="vnc_password=${VNC_PASSWORD}" \ + -var="remote_client_id=${HOSTED_REMOTE_PROFILE_ID}" \ + -var="remote_client_label=${HOSTED_REMOTE_PROFILE_LABEL}" \ + -var="remote_client_token=${HOSTED_REMOTE_PROFILE_TOKEN}" \ + -var="remote_daemon_port=${HOSTED_DAEMON_PORT}" \ + -var="enable_novnc_fallback=${HOSTED_ALLOW_NOVNC_FALLBACK}" \ + -auto-approve success "Infrastructure destroyed." @@ -462,7 +567,21 @@ if [ -f "${TERRAFORM_DIR}/terraform.tfstate" ]; then # Read remaining terraform outputs PROJECT_ID=$(terraform -chdir="$TERRAFORM_DIR" output -raw project_id 2>/dev/null || echo "") GCP_ZONE=$(terraform -chdir="$TERRAFORM_DIR" output -raw zone 2>/dev/null || echo "") - TUNNEL_PORT=8080 + HOSTED_DAEMON_PORT=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_daemon_port 2>/dev/null || echo "42137") + HOSTED_REMOTE_PROFILE_ID=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_id 2>/dev/null || echo "") + HOSTED_REMOTE_PROFILE_LABEL=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_label 2>/dev/null || echo "Pane Cloud Workspace") + HOSTED_REMOTE_PROFILE_TOKEN=$(terraform -chdir="$TERRAFORM_DIR" output -raw remote_client_token 2>/dev/null || echo "") + HOSTED_ALLOW_NOVNC_FALLBACK=$(normalize_bool "$(terraform -chdir="$TERRAFORM_DIR" output -raw novnc_fallback_enabled 2>/dev/null || echo "false")") + TUNNEL_PORT=$HOSTED_TUNNEL_PORT + HOSTED_DAEMON_BASE_URL="$(terraform -chdir="$TERRAFORM_DIR" output -raw daemon_base_url 2>/dev/null || echo "http://127.0.0.1:${TUNNEL_PORT}/daemon/")" + + if [ -z "$HOSTED_REMOTE_PROFILE_ID" ]; then + HOSTED_REMOTE_PROFILE_ID="cloud-${INSTANCE_NAME#pane-}" + fi + + if [ -z "$HOSTED_REMOTE_PROFILE_TOKEN" ]; then + HOSTED_REMOTE_PROFILE_TOKEN="$(read_existing_remote_profile_token "$HOSTED_REMOTE_PROFILE_ID")" + fi # If project_id output doesn't exist (older state), extract from tunnel command if [ -z "$PROJECT_ID" ]; then @@ -546,7 +665,18 @@ if [ -f "${TERRAFORM_DIR}/terraform.tfstate" ]; then fi echo "" - # Step 4: Get VNC password (from terraform state first, then VM as fallback) + # Step 4: Check hosted daemon readiness from inside the VM before opening the tunnel + info "Checking hosted daemon health..." + HOSTED_DAEMON_STATUS=$(gcloud compute ssh "$INSTANCE_NAME" \ + --zone="$GCP_ZONE" \ + --project="$PROJECT_ID" \ + --tunnel-through-iap \ + --command="curl -fsS http://127.0.0.1/health >/dev/null 2>&1 && echo 'ready' || echo 'bootstrapping'" \ + 2>/dev/null || echo "unknown") + success "Hosted daemon status: ${HOSTED_DAEMON_STATUS}" + echo "" + + # Step 5: Get VNC password (from terraform state first, then VM as fallback) info "Retrieving VNC password..." # Try terraform state first (we now store it there) @@ -584,51 +714,24 @@ if [ -f "${TERRAFORM_DIR}/terraform.tfstate" ]; then fi echo "" - # Step 5: Update Pane config + # Step 6: Update Pane config info "Updating Pane config..." mkdir -p "$PANE_CONFIG_DIR" + CURRENT_GCP_TOKEN="$GCP_TOKEN" if command -v jq &>/dev/null; then - # Read existing config or start with empty object - EXISTING_CONFIG='{}' - if [ -f "$PANE_CONFIG" ]; then - EXISTING_CONFIG=$(cat "$PANE_CONFIG" 2>/dev/null || echo '{}') - fi - - # Update config using jq and capture to variable (avoids Windows path issues with temp files) - NEW_CONFIG=$(echo "$EXISTING_CONFIG" | jq --arg provider "gcp" \ - --arg token "$GCP_TOKEN" \ - --arg serverId "$INSTANCE_NAME" \ - --arg vncPw "$VNC_PASSWORD" \ - --arg projectId "$PROJECT_ID" \ - --arg zone "$GCP_ZONE" \ - --argjson port "$TUNNEL_PORT" \ - '.cloud = { - provider: $provider, - apiToken: $token, - serverId: $serverId, - vncPassword: $vncPw, - projectId: $projectId, - zone: $zone, - tunnelPort: $port - }') - - if [ -n "$NEW_CONFIG" ] && [ "$NEW_CONFIG" != "null" ]; then - printf '%s\n' "$NEW_CONFIG" > "$PANE_CONFIG" - success "Config updated: ${PANE_CONFIG}" - else - warn "jq produced empty output — config not updated." - fi + save_cloud_config "$PROJECT_ID" "$GCP_ZONE" "$INSTANCE_NAME" "$VNC_PASSWORD" "$TUNNEL_PORT" + success "Config updated: ${PANE_CONFIG}" else warn "jq not found — skipping config update." fi echo "" - # Step 6: Start IAP tunnel + # Step 7: Start IAP tunnel header "Starting IAP Tunnel" echo -e "The tunnel will connect your local port ${BOLD}${TUNNEL_PORT}${NC} to the VM." - echo -e "Once connected, open Pane and click the ${BOLD}Cloud${NC} button to view your VM." + echo -e "Once connected, open Pane and click ${BOLD}Connect Cloud Runtime${NC}." echo "" echo -e "${YELLOW}Press Ctrl+C to disconnect the tunnel.${NC}" echo "" @@ -687,6 +790,8 @@ if ! gcloud auth application-default print-access-token &>/dev/null 2>&1; then gcloud auth application-default login fi +CURRENT_GCP_TOKEN=$(gcloud auth print-access-token 2>/dev/null || echo "") + # ============================================================ # Step 2: Choose or create a GCP project # ============================================================ @@ -704,6 +809,11 @@ done # Sanitize: lowercase, alphanumeric + hyphens only, strip carriage returns (WSL fix) USER_ID=$(echo "$USER_ID" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g') PROJECT_ID="pane-cloud-${USER_ID}" +HOSTED_REMOTE_PROFILE_ID="cloud-${USER_ID}" +HOSTED_REMOTE_PROFILE_TOKEN="$(read_existing_remote_profile_token "$HOSTED_REMOTE_PROFILE_ID")" +if [ -z "$HOSTED_REMOTE_PROFILE_TOKEN" ]; then + HOSTED_REMOTE_PROFILE_TOKEN="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" +fi info "Project ID will be: ${BOLD}${PROJECT_ID}${NC}" @@ -781,6 +891,11 @@ GCP_REGION=$(echo "$GCP_ZONE" | sed 's/-[a-z]$//') prompt_input MACHINE_TYPE "Machine type" "e2-highmem-2" prompt_input DISK_SIZE "Boot disk size (GB)" "128" +if prompt_yes_no "Enable noVNC fallback/debug desktop access?" "n"; then + HOSTED_ALLOW_NOVNC_FALLBACK="true" +else + HOSTED_ALLOW_NOVNC_FALLBACK="false" +fi echo "" info "Configuration summary:" @@ -791,6 +906,8 @@ echo " Region: ${GCP_REGION}" echo " Machine: ${MACHINE_TYPE}" echo " Disk: ${DISK_SIZE} GB" echo " Security: IAP-only (no public IP)" +echo " Runtime: Headless Pane daemon" +echo " noVNC debug: ${HOSTED_ALLOW_NOVNC_FALLBACK}" echo "" if ! prompt_yes_no "Proceed with Terraform apply?"; then @@ -817,7 +934,11 @@ else success "VNC password generated." fi echo -e "\n ${BOLD}VNC Password: ${YELLOW}${VNC_PASSWORD}${NC}\n" -echo -e " ${CYAN}Save this password — you'll need it to connect to the noVNC display.${NC}\n" +if [ "$HOSTED_ALLOW_NOVNC_FALLBACK" = "true" ]; then + echo -e " ${CYAN}Save this password — you'll need it for optional noVNC fallback access.${NC}\n" +else + echo -e " ${CYAN}noVNC fallback is disabled by default; this password is retained only for future recovery use.${NC}\n" +fi # Save progress: zone and VNC password now known save_cloud_config "$PROJECT_ID" "$GCP_ZONE" "" "$VNC_PASSWORD" @@ -848,6 +969,11 @@ terraform apply \ -var="machine_type=${MACHINE_TYPE}" \ -var="disk_size_gb=${DISK_SIZE}" \ -var="vnc_password=${VNC_PASSWORD}" \ + -var="remote_client_id=${HOSTED_REMOTE_PROFILE_ID}" \ + -var="remote_client_label=${HOSTED_REMOTE_PROFILE_LABEL}" \ + -var="remote_client_token=${HOSTED_REMOTE_PROFILE_TOKEN}" \ + -var="remote_daemon_port=${HOSTED_DAEMON_PORT}" \ + -var="enable_novnc_fallback=${HOSTED_ALLOW_NOVNC_FALLBACK}" \ -auto-approve success "Infrastructure provisioned!" @@ -855,8 +981,15 @@ success "Infrastructure provisioned!" # Capture outputs INSTANCE_NAME=$(terraform output -raw instance_name 2>/dev/null) SSH_CMD=$(terraform output -raw ssh_command 2>/dev/null) -TUNNEL_CMD=$(terraform output -raw novnc_tunnel_command 2>/dev/null) +TUNNEL_CMD=$(terraform output -raw daemon_tunnel_command 2>/dev/null) NOVNC_URL=$(terraform output -raw novnc_url 2>/dev/null) +HOSTED_DAEMON_PORT=$(terraform output -raw remote_daemon_port 2>/dev/null || echo "$HOSTED_DAEMON_PORT") +HOSTED_REMOTE_PROFILE_ID=$(terraform output -raw remote_client_id 2>/dev/null || echo "$HOSTED_REMOTE_PROFILE_ID") +HOSTED_REMOTE_PROFILE_LABEL=$(terraform output -raw remote_client_label 2>/dev/null || echo "$HOSTED_REMOTE_PROFILE_LABEL") +HOSTED_REMOTE_PROFILE_TOKEN=$(terraform output -raw remote_client_token 2>/dev/null || echo "$HOSTED_REMOTE_PROFILE_TOKEN") +HOSTED_ALLOW_NOVNC_FALLBACK=$(normalize_bool "$(terraform output -raw novnc_fallback_enabled 2>/dev/null || echo "$HOSTED_ALLOW_NOVNC_FALLBACK")") +HOSTED_DAEMON_BASE_URL=$(terraform output -raw daemon_base_url 2>/dev/null || echo "$HOSTED_DAEMON_BASE_URL") +HOSTED_DAEMON_STATUS="bootstrapping" # Save progress: instance name now known - this is the critical save! save_cloud_config "$PROJECT_ID" "$GCP_ZONE" "$INSTANCE_NAME" "$VNC_PASSWORD" @@ -870,7 +1003,7 @@ header "Step 7: Waiting for VM Setup" info "The VM is running the setup script (installs packages, Node.js, Pane, etc.)" info "This typically takes 3-5 minutes on a fresh VM.\n" -# Poll for setup completion by checking if supervisor is running +# Poll for setup completion by checking the daemon health endpoint through SSH MAX_WAIT=600 # 10 minutes max ELAPSED=0 INTERVAL=15 @@ -878,17 +1011,19 @@ INTERVAL=15 while [ $ELAPSED -lt $MAX_WAIT ]; do echo -ne "\r Waiting... (${ELAPSED}s / ${MAX_WAIT}s)" - # Try to SSH in and check if setup is done (supervisor running = setup complete) + # Try to SSH in and check if setup is done (daemon health endpoint ready) SETUP_DONE=$(gcloud compute ssh "$INSTANCE_NAME" \ --zone="$GCP_ZONE" \ --project="$PROJECT_ID" \ --tunnel-through-iap \ - --command="systemctl is-active supervisor 2>/dev/null || echo 'not-ready'" \ + --command="curl -fsS http://127.0.0.1/health >/dev/null 2>&1 && echo 'ready' || echo 'not-ready'" \ 2>/dev/null || echo "ssh-failed") - if [ "$SETUP_DONE" = "active" ]; then + if [ "$SETUP_DONE" = "ready" ]; then echo "" - success "VM setup complete! All services are running." + HOSTED_DAEMON_STATUS="ready" + save_cloud_config "$PROJECT_ID" "$GCP_ZONE" "$INSTANCE_NAME" "$VNC_PASSWORD" + success "VM setup complete! Hosted daemon health check passed." break fi @@ -912,47 +1047,11 @@ mkdir -p "$PANE_CONFIG_DIR" # Get GCP access token for API calls GCP_TOKEN=$(gcloud auth print-access-token 2>/dev/null || echo "") -TUNNEL_PORT=8080 +CURRENT_GCP_TOKEN="$GCP_TOKEN" +TUNNEL_PORT="$HOSTED_TUNNEL_PORT" if command -v jq &>/dev/null; then - if [ -f "$PANE_CONFIG" ]; then - # Merge cloud settings into existing config - jq --arg provider "gcp" \ - --arg token "$GCP_TOKEN" \ - --arg serverId "$INSTANCE_NAME" \ - --arg vncPw "$VNC_PASSWORD" \ - --arg projectId "$PROJECT_ID" \ - --arg zone "$GCP_ZONE" \ - --argjson port "$TUNNEL_PORT" \ - '.cloud = { - provider: $provider, - apiToken: $token, - serverId: $serverId, - vncPassword: $vncPw, - projectId: $projectId, - zone: $zone, - tunnelPort: $port - }' "$PANE_CONFIG" > "${PANE_CONFIG}.tmp" \ - && mv "${PANE_CONFIG}.tmp" "$PANE_CONFIG" - else - # Create new config with cloud settings - echo '{}' | jq --arg provider "gcp" \ - --arg token "$GCP_TOKEN" \ - --arg serverId "$INSTANCE_NAME" \ - --arg vncPw "$VNC_PASSWORD" \ - --arg projectId "$PROJECT_ID" \ - --arg zone "$GCP_ZONE" \ - --argjson port "$TUNNEL_PORT" \ - '.cloud = { - provider: $provider, - apiToken: $token, - serverId: $serverId, - vncPassword: $vncPw, - projectId: $projectId, - zone: $zone, - tunnelPort: $port - }' > "$PANE_CONFIG" - fi + save_cloud_config "$PROJECT_ID" "$GCP_ZONE" "$INSTANCE_NAME" "$VNC_PASSWORD" "$TUNNEL_PORT" success "Pane configured with cloud settings." info "Settings written to ${PANE_CONFIG}" info "Note: The GCP access token expires in ~1 hour. Pane auto-refreshes it via gcloud." @@ -968,17 +1067,26 @@ header "Setup Complete!" echo -e "${GREEN}${BOLD}Your Pane Cloud VM is ready!${NC}\n" -echo -e "${BOLD}Connect to your VM:${NC}" +echo -e "${BOLD}Connect to your hosted workspace:${NC}" echo "" -echo -e " ${CYAN}1. Start the IAP tunnel (run in a separate terminal):${NC}" +echo -e " ${CYAN}1. Start the daemon IAP tunnel (run in a separate terminal):${NC}" echo -e " ${BOLD}${TUNNEL_CMD}${NC}" echo "" -echo -e " ${CYAN}2. Open noVNC in your browser:${NC}" -echo -e " ${BOLD}${NOVNC_URL}${NC}" +echo -e " ${CYAN}2. Open Pane locally and click ${BOLD}Connect Cloud Runtime${NC}${CYAN}.${NC}" echo "" -echo -e " ${CYAN}3. Enter the VNC password when prompted${NC}" +echo -e " ${CYAN}3. Pane will connect to:${NC} ${BOLD}${HOSTED_DAEMON_BASE_URL}${NC}" echo "" +if [ "$HOSTED_ALLOW_NOVNC_FALLBACK" = "true" ] && [ -n "$NOVNC_URL" ]; then + echo -e "${BOLD}Optional noVNC fallback/debug access:${NC}" + echo -e " ${BOLD}${NOVNC_URL}${NC}" + echo -e " Password: ${BOLD}${VNC_PASSWORD}${NC}" + echo -e " Desktop fallback is manual so it does not compete with the hosted daemon:" + echo -e " ${BOLD}sudo supervisorctl stop pane-cloud:pane-daemon${NC}" + echo -e " ${BOLD}sudo supervisorctl start pane-cloud:PaneDesktop${NC}" + echo "" +fi + echo -e "${BOLD}SSH access:${NC}" echo -e " ${BOLD}${SSH_CMD}${NC}" echo "" @@ -992,11 +1100,12 @@ echo "" echo -e "${BOLD}Cost management:${NC}" echo -e " Stop VM: gcloud compute instances stop ${INSTANCE_NAME} --zone=${GCP_ZONE} --project=${PROJECT_ID}" echo -e " Start VM: gcloud compute instances start ${INSTANCE_NAME} --zone=${GCP_ZONE} --project=${PROJECT_ID}" -echo -e " Delete VM: cd ${TERRAFORM_DIR} && terraform destroy -var=\"project_id=${PROJECT_ID}\" -var=\"user_id=${USER_ID}\"" +echo -e " Delete VM: bash cloud/scripts/setup-cloud.sh --destroy" echo "" echo -e "${BOLD}Security:${NC}" echo -e " - No public IP — VM is only accessible via GCP IAP tunnel" echo -e " - All traffic authenticated through your Google account" +echo -e " - Pane daemon requests require the generated bearer token in your linked local profile" echo -e " - Daily snapshots with 7-day retention for backups" echo "" diff --git a/cloud/scripts/setup-vm.sh b/cloud/scripts/setup-vm.sh index 600f6155..503fb91a 100755 --- a/cloud/scripts/setup-vm.sh +++ b/cloud/scripts/setup-vm.sh @@ -1,6 +1,7 @@ #!/bin/bash # Pane Cloud VM Setup Script -# Installs all dependencies and configures the noVNC display stack +# Installs all dependencies and configures a daemon-first hosted workspace +# with optional noVNC fallback/debug access. # Run on a fresh Ubuntu 24.04 VM as root # # Usage: sudo bash setup-vm.sh [--pane-version VERSION] @@ -26,7 +27,7 @@ echo "" # ============================================================ # 1. System packages # ============================================================ -echo "[1/8] Installing system packages..." +echo "[1/10] Installing system packages..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq @@ -77,15 +78,56 @@ echo " Done." hash -r # ============================================================ -# 2. Node.js 20 LTS +# 2. Read instance metadata for hosted workspace bootstrap # ============================================================ -echo "[2/8] Installing Node.js 20 LTS..." +metadata_value() { + local key="$1" + curl -sf -H "Metadata-Flavor: Google" \ + "http://metadata.google.internal/computeMetadata/v1/instance/attributes/${key}" 2>/dev/null || true +} + +normalize_bool() { + local value + value="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" + case "$value" in + 1|true|yes|on) + echo "true" + ;; + *) + echo "false" + ;; + esac +} + +REMOTE_CLIENT_ID="$(metadata_value remote-client-id)" +REMOTE_CLIENT_LABEL="$(metadata_value remote-client-label)" +REMOTE_CLIENT_TOKEN="$(metadata_value remote-client-token)" +REMOTE_DAEMON_PORT="$(metadata_value remote-daemon-port)" +ENABLE_NOVNC_FALLBACK="$(normalize_bool "$(metadata_value enable-novnc-fallback)")" + +if ! [[ "$REMOTE_DAEMON_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_DAEMON_PORT" -lt 1 ] || [ "$REMOTE_DAEMON_PORT" -gt 65535 ]; then + REMOTE_DAEMON_PORT=42137 +fi + +if [ -z "$REMOTE_CLIENT_LABEL" ]; then + REMOTE_CLIENT_LABEL="Pane Cloud Workspace" +fi -# Always ensure we have Node 20+ (Ubuntu 24.04 ships with Node 18) +echo "[2/10] Hosted workspace metadata" +echo " Daemon port: ${REMOTE_DAEMON_PORT}" +echo " Remote client id: ${REMOTE_CLIENT_ID:-}" +echo " noVNC fallback enabled: ${ENABLE_NOVNC_FALLBACK}" + +# ============================================================ +# 3. Node.js 22 LTS +# ============================================================ +echo "[3/10] Installing Node.js 22 LTS..." + +# Always ensure we have Node 22+ (the repo requires >= 22.14) NODE_MAJOR=$(node --version 2>/dev/null | sed 's/v\([0-9]*\).*/\1/' || echo "0") -if [ "$NODE_MAJOR" -lt 20 ]; then - echo " Current Node version: $(node --version 2>/dev/null || echo 'none'). Upgrading to Node 20..." - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +if [ "$NODE_MAJOR" -lt 22 ]; then + echo " Current Node version: $(node --version 2>/dev/null || echo 'none'). Upgrading to Node 22..." + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt-get install -y -qq nodejs > /dev/null fi @@ -101,9 +143,9 @@ fi echo " Node $(node --version), pnpm $(pnpm --version 2>/dev/null || echo 'not installed')" # ============================================================ -# 3. GitHub CLI +# 4. GitHub CLI # ============================================================ -echo "[3/8] Installing GitHub CLI..." +echo "[4/10] Installing GitHub CLI..." if ! command -v gh &> /dev/null; then (type -p wget >/dev/null || apt-get install wget -y -qq) \ && mkdir -p -m 755 /etc/apt/keyrings \ @@ -118,9 +160,9 @@ fi echo " Done." # ============================================================ -# 4. Claude Code CLI +# 5. Claude Code CLI # ============================================================ -echo "[4/8] Installing Claude Code CLI..." +echo "[5/10] Installing Claude Code CLI..." if ! command -v claude &> /dev/null; then # Install latest Claude Code (intentionally unpinned — VM should have newest version) npm install -g @anthropic-ai/claude-code > /dev/null 2>&1 || true @@ -128,9 +170,9 @@ fi echo " Done." # ============================================================ -# 5. Install Pane +# 6. Install Pane # ============================================================ -echo "[5/9] Installing Pane..." +echo "[6/10] Installing Pane..." ARCH=$(dpkg --print-architecture) if [ ! -f /usr/bin/Pane ]; then # Download the latest Pane AppImage from GitHub Releases @@ -168,9 +210,9 @@ if [ ! -f /usr/bin/Pane ] && ! command -v Pane &>/dev/null; then fi # ============================================================ -# 6. Create Pane user +# 7. Create Pane user # ============================================================ -echo "[6/9] Setting up Pane user..." +echo "[7/10] Setting up Pane user..." if ! id "${PANE_USER}" &>/dev/null; then useradd -m -s /bin/bash "${PANE_USER}" fi @@ -220,9 +262,9 @@ chown "${PANE_USER}:${PANE_USER}" "${FLUXBOX_DIR}/init" echo " Fluxbox configured (no title bar, no toolbar)" # ============================================================ -# 7. Get or generate VNC password +# 8. Get or generate optional VNC password # ============================================================ -echo "[7/9] Setting up VNC password..." +echo "[8/10] Setting up optional VNC password..." VNC_PASSWORD_FILE="/home/${PANE_USER}/.vnc_password" # Try to get VNC password from instance metadata (set by Terraform) @@ -243,17 +285,78 @@ chown "${PANE_USER}:${PANE_USER}" "${VNC_PASSWORD_FILE}" echo " VNC password saved to ${VNC_PASSWORD_FILE}" # ============================================================ -# 8. Configure supervisord +# 9. Prepare Pane daemon config and supervisord # ============================================================ -echo "[8/9] Configuring supervisord..." +echo "[9/10] Preparing Pane daemon config..." # Get Pane user's UID for XDG_RUNTIME_DIR PANE_UID=$(id -u "${PANE_USER}") +PANE_CONFIG_FILE="/home/${PANE_USER}/.pane/config.json" +REMOTE_CLIENT_TOKEN_HASH="" + +if [ -n "$REMOTE_CLIENT_TOKEN" ]; then + REMOTE_CLIENT_TOKEN_HASH="$(printf '%s' "$REMOTE_CLIENT_TOKEN" | sha256sum | awk '{print $1}')" +fi + +HOST_CLIENTS_JSON='[]' +if [ -n "$REMOTE_CLIENT_ID" ] && [ -n "$REMOTE_CLIENT_TOKEN_HASH" ]; then + HOST_CLIENTS_JSON="$(jq -cn \ + --arg id "$REMOTE_CLIENT_ID" \ + --arg label "$REMOTE_CLIENT_LABEL" \ + --arg createdAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg tokenHash "$REMOTE_CLIENT_TOKEN_HASH" \ + '[{ + id: $id, + label: $label, + createdAt: $createdAt, + tokenHash: $tokenHash + }]')" +else + echo " WARNING: remote client metadata is incomplete; hosted daemon auth will rely on any existing local config." +fi + +EXISTING_PANE_CONFIG='{}' +if [ -f "$PANE_CONFIG_FILE" ]; then + EXISTING_PANE_CONFIG="$(cat "$PANE_CONFIG_FILE" 2>/dev/null || echo '{}')" +fi + +UPDATED_PANE_CONFIG="$( + printf '%s' "$EXISTING_PANE_CONFIG" | jq \ + --argjson daemonPort "$REMOTE_DAEMON_PORT" \ + --argjson hostClients "$HOST_CLIENTS_JSON" \ + ' + .remoteDaemon = (.remoteDaemon // {}) + | .remoteDaemon.host = (.remoteDaemon.host // {}) + | .remoteDaemon.host.config = { + enabled: true, + listenHost: "127.0.0.1", + listenPort: $daemonPort, + pairingRequired: true, + allowInsecureHttpOnLoopback: true + } + | if ($hostClients | length) > 0 + then .remoteDaemon.host.clients = $hostClients + else .remoteDaemon.host.clients = (.remoteDaemon.host.clients // []) + end + | .remoteDaemon.client = (.remoteDaemon.client // { + profiles: [], + activeProfileId: null, + mode: "local" + }) + ' +)" + +printf '%s\n' "$UPDATED_PANE_CONFIG" > "$PANE_CONFIG_FILE" +chown "${PANE_USER}:${PANE_USER}" "$PANE_CONFIG_FILE" +chmod 600 "$PANE_CONFIG_FILE" + +echo " Pane daemon config written to ${PANE_CONFIG_FILE}" +echo " Configuring supervisord..." cat > /etc/supervisor/conf.d/pane-stack.conf << SUPERVISOR_EOF -; ============================================================= -; Pane Cloud Display Stack -; ============================================================= +; ============================================================ +; Pane Cloud Hosted Workspace Stack +; ============================================================ [program:xvfb] command=/usr/bin/Xvfb :99 -screen 0 1920x1080x24 -ac +extension GLX +render -noreset @@ -262,71 +365,112 @@ autorestart=true stdout_logfile=/var/log/supervisor/xvfb.log stderr_logfile=/var/log/supervisor/xvfb-error.log +[program:pane-daemon] +command=/usr/bin/Pane --no-sandbox --daemon-headless --pane-dir /home/${PANE_USER}/.pane +priority=20 +autorestart=true +environment=DISPLAY=":99",HOME="/home/${PANE_USER}",XDG_RUNTIME_DIR="/run/user/${PANE_UID}" +user=${PANE_USER} +directory=/home/${PANE_USER} +stdout_logfile=/var/log/supervisor/pane-daemon.log +stderr_logfile=/var/log/supervisor/pane-daemon-error.log +startsecs=5 +startretries=5 +SUPERVISOR_EOF + +SUPERVISOR_GROUP_PROGRAMS="xvfb,pane-daemon" + +if [ "$ENABLE_NOVNC_FALLBACK" = "true" ]; then + cat >> /etc/supervisor/conf.d/pane-stack.conf << SUPERVISOR_FALLBACK_EOF + [program:fluxbox] command=/usr/bin/fluxbox -priority=20 +priority=30 autorestart=true environment=DISPLAY=":99" -user=Pane +user=${PANE_USER} stdout_logfile=/var/log/supervisor/fluxbox.log stderr_logfile=/var/log/supervisor/fluxbox-error.log -[program:Pane] -command=/usr/bin/Pane --no-sandbox --start-fullscreen -priority=30 -autorestart=true -environment=DISPLAY=":99",HOME="/home/Pane",XDG_RUNTIME_DIR="/run/user/${PANE_UID}" -user=Pane -directory=/home/Pane +[program:PaneDesktop] +command=/usr/bin/Pane --no-sandbox --start-fullscreen --pane-dir /home/${PANE_USER}/.pane +priority=40 +autostart=false +autorestart=false +environment=DISPLAY=":99",HOME="/home/${PANE_USER}",XDG_RUNTIME_DIR="/run/user/${PANE_UID}" +user=${PANE_USER} +directory=/home/${PANE_USER} stdout_logfile=/var/log/supervisor/Pane.log stderr_logfile=/var/log/supervisor/pane-error.log -; Give Pane time to start before considering it failed startsecs=5 -; Restart up to 5 times if it crashes startretries=5 [program:x11vnc] command=/usr/bin/x11vnc -display :99 -passwd ${VNC_PASSWORD} -forever -rfbport 5900 -localhost -noxdamage -cursor arrow -noxfixes -priority=40 +priority=50 autorestart=true -user=Pane +user=${PANE_USER} stdout_logfile=/var/log/supervisor/x11vnc.log stderr_logfile=/var/log/supervisor/x11vnc-error.log [program:websockify] command=/usr/bin/websockify --web=/usr/share/novnc 6080 localhost:5900 -priority=50 +priority=60 autorestart=true stdout_logfile=/var/log/supervisor/websockify.log stderr_logfile=/var/log/supervisor/websockify-error.log +SUPERVISOR_FALLBACK_EOF + + SUPERVISOR_GROUP_PROGRAMS="xvfb,pane-daemon,fluxbox,PaneDesktop,x11vnc,websockify" +fi + +cat >> /etc/supervisor/conf.d/pane-stack.conf << SUPERVISOR_GROUP_EOF [group:pane-cloud] -programs=xvfb,fluxbox,Pane,x11vnc,websockify +programs=${SUPERVISOR_GROUP_PROGRAMS} priority=999 -SUPERVISOR_EOF +SUPERVISOR_GROUP_EOF echo " Done." # ============================================================ -# 9. Configure NGINX +# 10. Configure NGINX # ============================================================ -echo "[9/9] Configuring NGINX..." +echo "[10/10] Configuring NGINX..." -cat > /etc/nginx/sites-available/pane-cloud << 'NGINX_EOF' -# Pane Cloud - NGINX reverse proxy for noVNC -# TLS will be configured by certbot after domain is set up +cat > /etc/nginx/sites-available/pane-cloud << NGINX_EOF +# Pane Cloud - NGINX reverse proxy for the hosted workspace daemon +# noVNC stays optional and is only exposed when fallback is enabled. server { listen 80 default_server; server_name _; - # Health check endpoint - location /health { + location = /health { access_log off; - return 200 'ok'; - add_header Content-Type text/plain; + proxy_pass http://127.0.0.1:${REMOTE_DAEMON_PORT}/health; + proxy_http_version 1.1; + proxy_set_header Host \$host; + proxy_buffering off; } + location /daemon/ { + proxy_pass http://127.0.0.1:${REMOTE_DAEMON_PORT}/; + proxy_http_version 1.1; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } +NGINX_EOF + +if [ "$ENABLE_NOVNC_FALLBACK" = "true" ]; then + cat >> /etc/nginx/sites-available/pane-cloud << 'NGINX_FALLBACK_EOF' + # noVNC static files location /novnc/ { alias /usr/share/novnc/; @@ -346,12 +490,22 @@ server { proxy_buffering off; } - # Default: redirect to noVNC location / { return 301 /novnc/vnc.html?autoconnect=true&resize=scale&reconnect=true&reconnect_delay=1000; } } -NGINX_EOF +NGINX_FALLBACK_EOF +else + cat >> /etc/nginx/sites-available/pane-cloud << 'NGINX_DAEMON_ONLY_EOF' + + location / { + access_log off; + default_type text/plain; + return 200 'Pane hosted daemon ready. Use /daemon/ through your authenticated tunnel.'; + } +} +NGINX_DAEMON_ONLY_EOF +fi # Enable the site ln -sf /etc/nginx/sites-available/pane-cloud /etc/nginx/sites-enabled/pane-cloud @@ -379,14 +533,26 @@ systemctl restart nginx echo "" echo "=== Setup Complete ===" echo "" -echo "VNC password: ${VNC_PASSWORD}" +echo "Hosted daemon loopback port: ${REMOTE_DAEMON_PORT}" +echo "Hosted daemon health endpoint: http://127.0.0.1/health" +echo "Hosted daemon reverse-proxy path: http://127.0.0.1/daemon/" echo "" echo "Access via IAP tunnel:" echo " gcloud compute start-iap-tunnel 80 --local-host-port=localhost:8080 --zone= --project=" -echo " Then open: http://localhost:8080/novnc/vnc.html?autoconnect=true&resize=scale" +echo " Then connect your local Pane client to: http://127.0.0.1:8080/daemon/" echo "" -echo "First-run auth (do this in the noVNC session):" -echo " 1. gh auth login (GitHub)" -echo " 2. claude login (Claude Code)" +if [ "$ENABLE_NOVNC_FALLBACK" = "true" ]; then + echo "noVNC fallback is enabled." + echo " The desktop app is not auto-started so it does not compete with the headless daemon." + echo " To debug the desktop app:" + echo " sudo supervisorctl stop pane-cloud:pane-daemon" + echo " sudo supervisorctl start pane-cloud:PaneDesktop" + echo " VNC password: ${VNC_PASSWORD}" + echo " Browser URL: http://localhost:8080/novnc/vnc.html?autoconnect=true&resize=scale" + echo "" +fi +echo "First-run auth (SSH in via IAP, or use the optional noVNC fallback if enabled):" +echo " 1. gh auth login (GitHub)" +echo " 2. claude login (Claude Code)" echo " 3. Set API keys in Pane Settings" echo "" diff --git a/cloud/terraform/gcp/main.tf b/cloud/terraform/gcp/main.tf index 9c79fc31..4c255243 100644 --- a/cloud/terraform/gcp/main.tf +++ b/cloud/terraform/gcp/main.tf @@ -4,8 +4,13 @@ # # Usage: # terraform init -# terraform plan -var="project_id=YOUR_PROJECT" -var="user_id=user123" -# terraform apply -var="project_id=YOUR_PROJECT" -var="user_id=user123" +# terraform plan \ +# -var="project_id=YOUR_PROJECT" \ +# -var="user_id=user123" \ +# -var="vnc_password=RECOVERY_PASSWORD" \ +# -var="remote_client_id=cloud-user123" \ +# -var="remote_client_label=Pane Cloud Workspace" \ +# -var="remote_client_token=GENERATED_TOKEN" terraform { required_version = ">= 1.5" @@ -70,6 +75,34 @@ variable "vnc_password" { sensitive = true } +variable "remote_client_id" { + description = "Stable remote profile/client ID for the hosted workspace daemon" + type = string +} + +variable "remote_client_label" { + description = "Human-readable label for the hosted workspace daemon client" + type = string +} + +variable "remote_client_token" { + description = "Bearer token the hosted workspace daemon will accept from the linked client profile" + type = string + sensitive = true +} + +variable "remote_daemon_port" { + description = "Loopback port the hosted Pane daemon listens on inside the VM" + type = number + default = 42137 +} + +variable "enable_novnc_fallback" { + description = "Whether to keep the legacy noVNC desktop stack available for fallback/debug access" + type = bool + default = false +} + variable "snapshot_start_time" { description = "Daily snapshot start time (HH:MM format, UTC)" type = string @@ -197,9 +230,14 @@ resource "google_compute_instance" "pane" { # VM is only reachable via IAP tunnel } - # Pass VNC password via instance metadata so we have it immediately + # Pass hosted workspace bootstrap settings via instance metadata. metadata = { - vnc-password = var.vnc_password + vnc-password = var.vnc_password + remote-client-id = var.remote_client_id + remote-client-label = var.remote_client_label + remote-client-token = var.remote_client_token + remote-daemon-port = tostring(var.remote_daemon_port) + enable-novnc-fallback = tostring(var.enable_novnc_fallback) } metadata_startup_script = file("${path.module}/../../scripts/setup-vm.sh") @@ -280,13 +318,49 @@ output "ssh_command" { } output "novnc_tunnel_command" { - description = "Start IAP tunnel to access noVNC on localhost:8080" + description = "Legacy/fallback tunnel command; the same tunnel carries daemon and optional noVNC traffic" value = "gcloud compute start-iap-tunnel pane-${var.user_id} 80 --local-host-port=localhost:8080 --zone=${var.zone} --project=${var.project_id}" } +output "daemon_tunnel_command" { + description = "Start the IAP tunnel used by the hosted workspace daemon and optional noVNC fallback" + value = "gcloud compute start-iap-tunnel pane-${var.user_id} 80 --local-host-port=localhost:8080 --zone=${var.zone} --project=${var.project_id}" +} + +output "daemon_base_url" { + description = "Base URL the local Pane client should use while the IAP tunnel is running" + value = "http://127.0.0.1:8080/daemon/" +} + +output "remote_client_id" { + description = "Stable hosted workspace remote profile/client ID" + value = var.remote_client_id +} + +output "remote_client_label" { + description = "Human-readable hosted workspace remote profile/client label" + value = var.remote_client_label +} + +output "remote_client_token" { + description = "Bearer token the hosted workspace daemon accepts from the linked client" + value = var.remote_client_token + sensitive = true +} + +output "remote_daemon_port" { + description = "Loopback port the hosted Pane daemon listens on inside the VM" + value = var.remote_daemon_port +} + output "novnc_url" { - description = "Open this in browser AFTER starting the IAP tunnel" - value = "http://localhost:8080/novnc/vnc.html?autoconnect=true&resize=scale" + description = "Open this in browser AFTER starting the IAP tunnel when noVNC fallback is enabled" + value = var.enable_novnc_fallback ? "http://localhost:8080/novnc/vnc.html?autoconnect=true&resize=scale" : "" +} + +output "novnc_fallback_enabled" { + description = "Whether the hosted VM keeps the legacy noVNC desktop stack enabled" + value = var.enable_novnc_fallback } output "setup_log_command" { diff --git a/deploy/remote-pwa/Dockerfile b/deploy/remote-pwa/Dockerfile new file mode 100644 index 00000000..2e14de99 --- /dev/null +++ b/deploy/remote-pwa/Dockerfile @@ -0,0 +1,6 @@ +FROM nginxinc/nginx-unprivileged:1.27-alpine + +COPY deploy/remote-pwa/nginx.conf /etc/nginx/conf.d/default.conf +COPY frontend/dist/ /usr/share/nginx/html/ + +EXPOSE 8080 diff --git a/deploy/remote-pwa/nginx.conf b/deploy/remote-pwa/nginx.conf new file mode 100644 index 00000000..c776c4ca --- /dev/null +++ b/deploy/remote-pwa/nginx.conf @@ -0,0 +1,63 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + index remote.html; + + include /etc/nginx/mime.types; + types { + application/manifest+json webmanifest; + } + default_type application/octet-stream; + + server_tokens off; + absolute_redirect off; + + gzip on; + gzip_types + application/javascript + application/json + application/manifest+json + image/svg+xml + text/css + text/javascript + text/plain; + + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + location = /healthz { + access_log off; + add_header Content-Type "text/plain; charset=utf-8"; + return 200 "ok\n"; + } + + location = / { + return 308 /app/; + } + + location = /app { + return 308 /app/; + } + + location = /remote.html { + return 308 /app/; + } + + location ~ ^/app/assets/(.*)$ { + rewrite ^/app/(.*)$ /$1 break; + add_header Cache-Control "public, max-age=31536000, immutable" always; + try_files $uri =404; + } + + location ~ ^/app/(.+\.(ico|json|png|svg|txt|webmanifest))$ { + rewrite ^/app/(.*)$ /$1 break; + add_header Cache-Control "no-cache" always; + try_files $uri =404; + } + + location /app/ { + add_header Cache-Control "no-cache" always; + try_files /remote.html =404; + } +} diff --git a/docs/SELF_HOSTED_REMOTE_DAEMON.md b/docs/SELF_HOSTED_REMOTE_DAEMON.md new file mode 100644 index 00000000..3705b796 --- /dev/null +++ b/docs/SELF_HOSTED_REMOTE_DAEMON.md @@ -0,0 +1,191 @@ +# Self-Hosted Remote Daemon + +This guide covers running Pane on your own workstation, Mac mini, Linux box, or VM and connecting to it from the local Pane desktop app. + +The intended flow is: + +1. Run one setup command on the remote machine. +2. Copy the generated `pane-remote://...` connection code. +3. Paste it into local Pane under `Settings > Self-Hosted Remote Daemon > Import Remote Connection`. + +Pane saves the profile and attempts to connect immediately. Local desktop mode is unchanged until a remote profile is imported and activated. + +## One-Command Setup + +From a source checkout: + +```bash +pnpm remote:setup -- --label "VM" +``` + +For validation against a separate data directory: + +```bash +PANE_DIR=/tmp/pane-remote-vm pnpm remote:setup -- --label "VM" --print-only +``` + +Packaged Pane builds can run the same setup path without opening a window: + +```bash +pane --remote-setup --label "VM" +``` + +The setup command: + +- detects Linux, macOS, or Windows host behavior +- writes remote daemon config into one `PANE_DIR` (default `~/.pane_remote`) +- enables the loopback listener on `127.0.0.1:42137` +- creates a paired client record with a hashed token on the host +- emits the raw token only inside the one-time `pane-remote://...` import code +- attempts to install and start a user-level daemon service +- prints the manual daemon command if service setup is unavailable +- detects Tailscale Serve where possible and otherwise prints an SSH local-forward command + +Useful options: + +```bash +pnpm remote:setup -- --help +pnpm remote:setup -- --channel nightly +pnpm remote:setup -- --pane-dir "$HOME/.pane_remote" +pnpm remote:setup -- --prefer-tunnel ssh +pnpm remote:setup -- --no-install-service +pnpm remote:setup -- --no-tailscale-serve +``` + +## Import Locally + +On your local desktop machine: + +1. Open Pane. +2. Go to `Settings > Self-Hosted Remote Daemon`. +3. Paste the full `pane-remote://...` code into `Import Remote Connection`. +4. Click `Import & Connect`. + +If the tunnel is not reachable yet, Pane still saves the profile and shows the connection error. Start the printed SSH/Tailscale tunnel and click `Connect` on the saved profile. + +## Security Model + +- The daemon listener only supports loopback hosts: `127.0.0.1`, `::1`, or `localhost`. +- Direct public or LAN binding is intentionally rejected. +- Use SSH local forwarding, Tailscale Serve, or a trusted HTTPS reverse proxy that forwards to loopback. +- Treat the generated `pane-remote://...` code like a secret. It contains the bearer token needed by the local client. + +Tailscale Serve example generated by setup: + +```bash +tailscale serve --bg http://127.0.0.1:42137 +``` + +SSH fallback generated by setup: + +```bash +ssh -N -L 42137:127.0.0.1:42137 user@your-host +``` + +## Manual Advanced Flow + +The old manual flow still works and is useful for debugging. + +### 1. Choose the Host Data Directory + +```bash +export PANE_DIR="$HOME/.pane_remote" +mkdir -p "$PANE_DIR" +``` + +Pane stores config and database files under that directory. The setup command, desktop app, and headless daemon must use the same `PANE_DIR`. + +### 2. Configure the Remote Listener + +Launch Pane on the host against that directory: + +```bash +PANE_DIR="$HOME/.pane_remote" pnpm dev +``` + +In `Settings > Self-Hosted Remote Daemon`: + +1. Enable `Enable remote daemon listener`. +2. Keep `Listen Host` on `127.0.0.1`. +3. Keep or change `Listen Port`, default `42137`. +4. Leave `Require pairing / saved bearer tokens` enabled. +5. Leave `Allow direct HTTP on loopback` enabled. +6. Save host settings. + +### 3. Create a Paired Connection + +From the same settings section on the host: + +1. Enter a label such as `Office Mac mini`. +2. Enter the base URL the client will use after tunneling, for example `http://127.0.0.1:42137`. +3. Click `Create Paired Profile`. + +Pane adds a host-side allowed client record, adds a matching local profile, and shows the generated bearer token once. + +### 4. Start the Headless Daemon + +```bash +PANE_DIR="$HOME/.pane_remote" pnpm daemon:headless +``` + +On success: + +```text +[Pane daemon] Headless host ready on tcp:127.0.0.1:42137 +``` + +### 5. Connect the Desktop Client + +Use `Import Remote Connection` for a generated code, or use `Save Existing Remote Profile` with: + +- label +- base URL +- bearer token + +Then click `Connect` on the saved profile. + +## Validation + +Recommended checks after connecting: + +1. Verify projects and sessions load in the client. +2. Open a terminal-backed session and confirm output streaming works. +3. Send terminal input and verify the remote runtime receives it. +4. Resize a terminal and confirm the remote terminal resizes. +5. Open a file and confirm read/write works. +6. Check git status and commit/diff flows. +7. Run an approve-mode command and confirm the permission dialog appears on the client. + +## Troubleshooting + +### The headless daemon starts but remote connect fails + +Check: + +- the daemon uses the same `PANE_DIR` that setup wrote +- the tunnel/proxy forwards to the same loopback port as the host config +- the client profile base URL matches the client-side tunnel endpoint +- the `pane-remote://...` code was not truncated + +### I changed host settings but nothing happened + +The headless daemon watches config and starts or stops the remote transport based on saved host config. If behavior looks stale, restart the daemon once and verify the correct `PANE_DIR`. + +### Why can’t I bind to `0.0.0.0` or a LAN IP? + +That is intentionally blocked. The current security model is loopback plus a secure exposure layer. + +### Why are some actions disabled in remote mode? + +Some actions operate on the local desktop client machine rather than the remote workspace. Pane currently disables or keeps local-only behavior for: + +- opening a local IDE from the client +- revealing files in the client OS file manager +- the native clipboard-image fallback path + +## Current Limitations + +- No hosted relay, NAT traversal, or account-based multi-tenant auth +- No web/mobile client in this phase +- No direct non-loopback listener support +- No full live remote end-to-end CI harness yet diff --git a/docs/remote-daemon-lifecycle.md b/docs/remote-daemon-lifecycle.md new file mode 100644 index 00000000..19c86c97 --- /dev/null +++ b/docs/remote-daemon-lifecycle.md @@ -0,0 +1,33 @@ +# Remote Daemon Lifecycle + +This is the implementation checklist for Remote Pane setup, teardown, and runtime switching. It exists to keep config writes, runtime controller actions, and renderer refreshes in sync. + +## Runtime Roles + +- Host lifecycle is owned by `PaneRemoteTransportController` and `remoteHostRuntimeStateStore`. +- Client lifecycle is owned by `RemotePaneClientController`. +- IPC handlers in `main/src/ipc/remoteDaemon.ts` orchestrate config writes and controller calls. +- Renderer runtime changes are reconciled through `remote-daemon:resync-required`. + +## Lifecycle Matrix + +| Action | Main-process side effect | Renderer side effect | +| --- | --- | --- | +| Import connection and connect succeeds | Activate profile, save/dedupe profile, set remote mode | Resync config, sessions, panels | +| Import connection and connect fails | Save/dedupe profile, keep current active runtime | No runtime resync | +| Connect saved profile | Activate profile before persisting remote mode | Resync config, sessions, panels | +| Switch to local runtime | Disconnect active remote client, save local mode | Resync config, sessions, panels, clear stale active session | +| Delete inactive profile | Remove profile | No runtime resync | +| Delete active profile | Switch local, remove profile, save local mode | Resync config, sessions, panels | +| Enable or update host | Save host config, transport controller syncs to live or error | Host-state event updates UI | +| Stop host | Save disabled host config, transport controller stops server | Host-state event updates UI | +| Disconnect host clients | Drop matching SSE clients | Host-state event updates client count | +| Revoke host client | Remove saved client record, drop matching SSE clients | Host-state event updates client count | + +## Guardrails + +- Do not persist remote mode until the selected profile has successfully activated. +- Failed import-connect saves the profile but must not switch runtime or emit a renderer resync. +- Connected remote clients are runtime state, not saved client records. +- Current Pane Data hosting is live only while that Pane app is running. +- Isolated daemon data can install a background service; Current Pane Data should not. diff --git a/frontend/public/remote-manifest.webmanifest b/frontend/public/remote-manifest.webmanifest new file mode 100644 index 00000000..a3ee2955 --- /dev/null +++ b/frontend/public/remote-manifest.webmanifest @@ -0,0 +1,11 @@ +{ + "name": "Remote Pane", + "short_name": "Remote Pane", + "description": "Connect to a remote Pane host from a browser.", + "start_url": "/app/", + "scope": "/app/", + "display": "standalone", + "background_color": "#f7fafc", + "theme_color": "#f7fafc", + "orientation": "any" +} diff --git a/frontend/remote.html b/frontend/remote.html new file mode 100644 index 00000000..33f55411 --- /dev/null +++ b/frontend/remote.html @@ -0,0 +1,15 @@ + + + + + + + + + Remote Pane + + +
+ + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d6b9f50d..97c7eeee 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -36,10 +36,15 @@ import { CreateSessionDialog } from './components/CreateSessionDialog'; import { AddProjectDialog } from './components/AddProjectDialog'; import { useNavigationStore } from './stores/navigationStore'; import { initPostHog, capture, captureUnconditionally, posthog } from './services/posthog'; -import type { VersionUpdateInfo, PermissionInput } from './types/session'; +import type { VersionUpdateInfo } from './types/session'; import type { AnalyticsIdentity, TerminalShortcut } from './types/config'; import type { ResumableSession } from '../../shared/types/panels'; import type { Project } from './types/project'; +import type { + PanePermissionRequest, + PanePermissionResolvedEvent, + PanePermissionInput, +} from '../../shared/types/daemon'; import { isMac } from './utils/platformUtils'; // Stable empty array to avoid creating new references in render @@ -52,14 +57,6 @@ interface IPCResponse { error?: string; } -interface PermissionRequest { - id: string; - sessionId: string; - toolName: string; - input: PermissionInput; - timestamp: number; -} - function App() { const [isWelcomeOpen, setIsWelcomeOpen] = useState(false); const [isAnalyticsConsentOpen, setIsAnalyticsConsentOpen] = useState(false); @@ -67,7 +64,7 @@ function App() { const [isAboutOpen, setIsAboutOpen] = useState(false); const [isUpdateDialogOpen, setIsUpdateDialogOpen] = useState(false); const [updateVersionInfo, setUpdateVersionInfo] = useState(null); - const [currentPermissionRequest, setCurrentPermissionRequest] = useState(null); + const [currentPermissionRequest, setCurrentPermissionRequest] = useState(null); const [isDiscordOpen, setIsDiscordOpen] = useState(false); const [hasCheckedWelcome, setHasCheckedWelcome] = useState(false); const [isOnboardingOpen, setIsOnboardingOpen] = useState(false); @@ -204,6 +201,8 @@ function App() { // Detect unclean shutdown from previous session and notify user useEffect(() => { + if (!window.electronAPI?.events?.onUncleanShutdownDetected) return; + return window.electronAPI.events.onUncleanShutdownDetected(() => { showNotification( 'Pane didn\'t shut down cleanly', @@ -509,19 +508,41 @@ function App() { checkResumableSessions(); }, [isLoaded, isAnalyticsConsentOpen]); + const loadNextPendingPermission = useCallback(async () => { + try { + const result = await API.permissions.getPending(); + if (result.success) { + setCurrentPermissionRequest(result.data?.[0] ?? null); + } else { + console.error('Failed to fetch pending permission requests:', result.error); + } + } catch (error) { + console.error('Failed to load pending permission requests:', error); + } + }, []); + useEffect(() => { - // Set up permission request listener - const handlePermissionRequest = (...args: unknown[]) => { - const request = args[0] as PermissionRequest; + if (!window.electronAPI?.events) { + return; + } + + const removePermissionRequest = window.electronAPI.events.onPermissionRequest((request: PanePermissionRequest) => { setCurrentPermissionRequest(request); - }; + }); + const removePermissionResolved = window.electronAPI.events.onPermissionResolved((event: PanePermissionResolvedEvent) => { + setCurrentPermissionRequest((currentRequest) => ( + currentRequest?.id === event.request.id ? null : currentRequest + )); + void loadNextPendingPermission(); + }); - window.electron?.on('permission:request', handlePermissionRequest); + void loadNextPendingPermission(); return () => { - window.electron?.off('permission:request', handlePermissionRequest); + removePermissionRequest(); + removePermissionResolved(); }; - }, []); + }, [loadNextPendingPermission]); useEffect(() => { // Set up version update listener @@ -559,17 +580,26 @@ function App() { setIsUpdateDialogOpen(true); }; - const handlePermissionResponse = async (requestId: string, behavior: 'allow' | 'deny', _updatedInput?: PermissionInput, message?: string) => { + const handlePermissionResponse = useCallback(async ( + requestId: string, + behavior: 'allow' | 'deny', + updatedInput?: PanePermissionInput, + message?: string, + ) => { try { - await API.permissions.respond(requestId, { - allow: behavior === 'allow', - reason: message + const result = await API.permissions.respond(requestId, { + behavior, + updatedInput, + message, }); - setCurrentPermissionRequest(null); + if (!result.success) { + throw new Error(result.error || 'Failed to respond to permission request'); + } + await loadNextPendingPermission(); } catch (error) { console.error('Failed to respond to permission request:', error); } - }; + }, [loadNextPendingPermission]); return ( diff --git a/frontend/src/components/CloudWidget.tsx b/frontend/src/components/CloudWidget.tsx index fbcb3dfb..3f5eee2b 100644 --- a/frontend/src/components/CloudWidget.tsx +++ b/frontend/src/components/CloudWidget.tsx @@ -1,9 +1,12 @@ import { useEffect, useCallback, useState } from 'react'; import { Play, Square, Loader2, Cloud, Monitor, Terminal } from 'lucide-react'; +import { createDefaultCloudVmState } from '../../../shared/types/cloud'; import { useCloudStore } from '../stores/cloudStore'; import { useSessionStore } from '../stores/sessionStore'; import { panelApi } from '../services/panelApi'; +const DEFAULT_CLOUD_STATE = createDefaultCloudVmState(); + export function CloudWidget() { const { vmState, showCloudView, loading, setVmState, setLoading, setShowCloudView, toggleCloudView } = useCloudStore(); const activeSessionId = useSessionStore((state) => state.activeSessionId); @@ -56,7 +59,7 @@ export function CloudWidget() { } } catch (err) { setVmState({ - ...(vmState ?? { status: 'unknown', ip: null, noVncUrl: null, provider: null, serverId: null, lastChecked: null, tunnelStatus: 'off' as const }), + ...(vmState ?? DEFAULT_CLOUD_STATE), error: err instanceof Error ? err.message : 'Failed to start VM', status: 'unknown', }); @@ -75,7 +78,7 @@ export function CloudWidget() { } } catch (err) { setVmState({ - ...(vmState ?? { status: 'unknown', ip: null, noVncUrl: null, provider: null, serverId: null, lastChecked: null, tunnelStatus: 'off' as const }), + ...(vmState ?? DEFAULT_CLOUD_STATE), error: err instanceof Error ? err.message : 'Failed to stop VM', status: 'unknown', }); @@ -84,10 +87,56 @@ export function CloudWidget() { } }, [setLoading, setShowCloudView, setVmState, vmState]); + const handleConnectWorkspace = useCallback(async () => { + setLoading(true); + try { + const result = await window.electronAPI.cloud.connectWorkspace(); + if (result.success && result.data) { + setVmState(result.data); + return; + } + + setVmState({ + ...(vmState ?? DEFAULT_CLOUD_STATE), + error: result.error ?? 'Failed to connect to hosted workspace', + }); + } catch (err) { + setVmState({ + ...(vmState ?? DEFAULT_CLOUD_STATE), + error: err instanceof Error ? err.message : 'Failed to connect to hosted workspace', + }); + } finally { + setLoading(false); + } + }, [setLoading, setVmState, vmState]); + + const handleDisconnectWorkspace = useCallback(async () => { + setLoading(true); + try { + const result = await window.electronAPI.cloud.disconnectWorkspace(); + if (result.success && result.data) { + setVmState(result.data); + return; + } + + setVmState({ + ...(vmState ?? DEFAULT_CLOUD_STATE), + error: result.error ?? 'Failed to switch back to local runtime', + }); + } catch (err) { + setVmState({ + ...(vmState ?? DEFAULT_CLOUD_STATE), + error: err instanceof Error ? err.message : 'Failed to switch back to local runtime', + }); + } finally { + setLoading(false); + } + }, [setLoading, setVmState, vmState]); + const handleOpenSetupTerminal = useCallback(async () => { if (!activeSessionId) { setVmState({ - ...(vmState ?? { status: 'unknown', ip: null, noVncUrl: null, provider: null, serverId: null, lastChecked: null, tunnelStatus: 'off' as const }), + ...(vmState ?? DEFAULT_CLOUD_STATE), error: 'Select a session first to open the setup terminal', }); return; @@ -127,18 +176,49 @@ export function CloudWidget() { const isRunning = vmState.status === 'running'; const isOff = vmState.status === 'off'; const isUnknown = vmState.status === 'unknown'; // Usually means auth failed - const tunnelConnecting = isRunning && vmState.tunnelStatus === 'starting'; - const tunnelReady = isRunning && vmState.tunnelStatus === 'running'; - const tunnelError = isRunning && vmState.tunnelStatus === 'error'; - const tunnelDisconnected = isRunning && vmState.tunnelStatus === 'off'; + const hasDaemonMetadata = Boolean( + vmState.daemonBaseUrl + || vmState.linkedRemoteProfileId + || vmState.daemonStatus !== 'unknown', + ); + const noVncFallbackReady = vmState.allowNoVncFallback && Boolean(vmState.noVncUrl); + const daemonUnavailableWithFallback = + noVncFallbackReady && + (vmState.daemonStatus === 'unknown' || vmState.daemonStatus === 'error'); + const daemonAccess = + isRunning && + vmState.preferredAccess === 'daemon' && + hasDaemonMetadata && + !daemonUnavailableWithFallback; + const daemonBootstrapping = daemonAccess && vmState.daemonStatus === 'bootstrapping'; + const daemonReady = daemonAccess && vmState.daemonStatus === 'ready'; + const daemonConnected = daemonReady && vmState.remoteConnectionStatus === 'connected'; + const daemonConnectAvailable = daemonReady && vmState.remoteConnectionStatus === 'available'; + const daemonConnectionReconnecting = + daemonReady && + (vmState.remoteConnectionStatus === 'connecting' || vmState.remoteConnectionStatus === 'reconnecting'); + const daemonConnectionError = daemonReady && vmState.remoteConnectionStatus === 'error'; + const daemonConnectionUnavailable = daemonConnectionReconnecting || daemonConnectionError; + const daemonError = daemonAccess && vmState.daemonStatus === 'error'; + const canManageCloudVmLifecycle = isRunning && vmState.noVncUrl !== null; + const tunnelConnecting = isRunning && !daemonAccess && vmState.tunnelStatus === 'starting'; + const tunnelReady = isRunning && !daemonAccess && vmState.tunnelStatus === 'running'; + const tunnelError = isRunning && !daemonAccess && vmState.tunnelStatus === 'error'; + const tunnelDisconnected = isRunning && !daemonAccess && vmState.tunnelStatus === 'off'; // Show reconnect button for: tunnel issues OR unknown status (auth failed) - const needsReconnect = (tunnelError || tunnelDisconnected || isUnknown) && !loading && activeSessionId; + const needsReconnect = (daemonError || tunnelError || tunnelDisconnected || isUnknown) && !loading && activeSessionId; // Compute transitioning label const getTransitionLabel = () => { if (vmState.status === 'stopping') return 'Stopping...'; if (vmState.status === 'starting' || vmState.status === 'initializing') return 'Starting VM...'; + if (daemonBootstrapping) return 'Starting workspace daemon...'; + if (daemonConnectionReconnecting) { + return vmState.remoteConnectionStatus === 'connecting' + ? 'Connecting cloud runtime...' + : 'Reconnecting cloud runtime...'; + } if (tunnelConnecting) return 'Connecting tunnel...'; return 'Loading...'; }; @@ -156,7 +236,7 @@ export function CloudWidget() { )} {/* Transitioning state (VM starting/stopping or tunnel connecting) */} - {(isTransitioning || tunnelConnecting || loading) && ( + {(isTransitioning || daemonBootstrapping || daemonConnectionReconnecting || tunnelConnecting || loading) && (
@@ -181,7 +261,7 @@ export function CloudWidget() { {needsReconnect && ( <> {/* Only show stop button if VM is confirmed running */} - {isRunning && ( + {canManageCloudVmLifecycle && ( + )} + + + )} + + {daemonConnected && !loading && ( + <> + {canManageCloudVmLifecycle && ( + + )} + +
+ + Cloud Connected +
+ + )} + + {daemonConnectionUnavailable && !loading && ( + <> + +
+ + + {daemonConnectionError ? 'Cloud Connection Error' : 'Cloud Reconnecting'} + +
+ + )} + + {daemonReady && !daemonConnectAvailable && !daemonConnected && !daemonConnectionUnavailable && !loading && ( + <> + {canManageCloudVmLifecycle && ( + + )} +
+ + Daemon Ready +
+ + )}
); } diff --git a/frontend/src/components/DetailPanel.tsx b/frontend/src/components/DetailPanel.tsx index 5322275b..5c91b150 100644 --- a/frontend/src/components/DetailPanel.tsx +++ b/frontend/src/components/DetailPanel.tsx @@ -78,6 +78,7 @@ function actionTooltip(action: { description?: string; disabled?: boolean; disab export function DetailPanel({ isVisible, width, height, onResize, mergeError, projectGitActions, orientation, isCollapsed, onToggleCollapse, onSwapLayout, terminalShortcuts, onCommitClick }: DetailPanelProps) { const sessionContext = useSession(); const immersiveMode = useNavigationStore(s => s.immersiveMode); + const remoteIdeTooltip = 'Open in IDE is only available in local mode. Switch this client back to the local runtime to use your desktop IDE.'; // Build IDE dropdown items, sending safe IDE keys (resolved to commands server-side) const ideItems = useMemo(() => { @@ -98,7 +99,7 @@ export function DetailPanel({ isVisible, width, height, onResize, mergeError, pr if (!sessionContext) return null; - const { session, gitBranchActions, isMerging, gitCommands, onOpenIDEWithCommand, onConfigureIDE, onSetTracking, trackingBranch } = sessionContext; + const { session, gitBranchActions, isMerging, gitCommands, onOpenIDEWithCommand, onConfigureIDE, onSetTracking, trackingBranch, isRemoteMode } = sessionContext; const gitStatus = session.gitStatus; const isProject = !!session.isMainRepo; // Treat git as unavailable only when status has loaded but indicates failure. @@ -186,25 +187,35 @@ export function DetailPanel({ isVisible, width, height, onResize, mergeError, pr {/* IDE button */} {onOpenIDEWithCommand && ( - - - - } - items={ideItems} - footer={ - - } - position="auto" - width="sm" - /> + + + ) : ( + + + + } + items={ideItems} + footer={ + + } + position="auto" + width="sm" + /> + ) )} {/* Terminal shortcut pills — inline with git actions */} @@ -333,24 +344,35 @@ export function DetailPanel({ isVisible, width, height, onResize, mergeError, pr )} {onOpenIDEWithCommand && ( - - - Open in IDE - - } - items={ideItems} - footer={ - - } - position="auto" - width="sm" - /> + isRemoteMode ? ( + + + + + + ) : ( + + + Open in IDE + + } + items={ideItems} + footer={ + + } + position="auto" + width="sm" + /> + ) )} diff --git a/frontend/src/components/HomePage.tsx b/frontend/src/components/HomePage.tsx index a10303bb..00e27346 100644 --- a/frontend/src/components/HomePage.tsx +++ b/frontend/src/components/HomePage.tsx @@ -9,7 +9,7 @@ import { Dropdown } from './ui/Dropdown'; import { Badge } from './ui/Badge'; import { AddProjectDialog } from './AddProjectDialog'; import { CloneFromGitHubDialog } from './CloneFromGitHubDialog'; -import { formatDistanceToNow } from '../utils/timestampUtils'; +import { formatDistanceToNow, isValidTimestamp } from '../utils/timestampUtils'; import type { Project } from '../types/project'; import type { Session } from '../types/session'; @@ -224,7 +224,7 @@ export function HomePage() { const recentSessions = useMemo(() => { return sessions - .filter((s): s is Session & { lastActivity: string } => !s.archived && typeof s.lastActivity === 'string') + .filter((s): s is Session & { lastActivity: string } => !s.archived && isValidTimestamp(s.lastActivity)) .sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()) .slice(0, 8); }, [sessions]); @@ -417,9 +417,11 @@ export function HomePage() { )}
- - {getStatusLabel(session.status)} - + {session.status !== 'stopped' && session.status !== 'ready' && ( + + {getStatusLabel(session.status)} + + )} {formatDistanceToNow(session.lastActivity)} diff --git a/frontend/src/components/PermissionDialog.tsx b/frontend/src/components/PermissionDialog.tsx index 781bf4a3..0583e346 100644 --- a/frontend/src/components/PermissionDialog.tsx +++ b/frontend/src/components/PermissionDialog.tsx @@ -1,20 +1,18 @@ import React, { useState, useEffect } from 'react'; import { Check, X, Shield, AlertTriangle, Code, Edit } from 'lucide-react'; +import type { PanePermissionRequest } from '../../../shared/types/daemon'; import { Modal, ModalHeader, ModalBody, ModalFooter } from './ui/Modal'; import { Button } from './ui/Button'; import { Textarea } from './ui/Textarea'; -interface PermissionRequest { - id: string; - sessionId: string; - toolName: string; - input: Record; - timestamp: number; -} - interface PermissionDialogProps { - request: PermissionRequest | null; - onRespond: (requestId: string, behavior: 'allow' | 'deny', updatedInput?: Record, message?: string) => void; + request: PanePermissionRequest | null; + onRespond: ( + requestId: string, + behavior: 'allow' | 'deny', + updatedInput?: PanePermissionRequest['input'], + message?: string, + ) => void; session?: { name: string }; } @@ -219,4 +217,4 @@ export const PermissionDialog: React.FC = ({ request, onR ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/ProjectSessionList.tsx b/frontend/src/components/ProjectSessionList.tsx index d39b4501..8d1ca3e2 100644 --- a/frontend/src/components/ProjectSessionList.tsx +++ b/frontend/src/components/ProjectSessionList.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useMemo, useCallback, useRef } from 'react'; -import { ChevronDown, ChevronRight, Plus, FolderPlus, GitBranch, MoreHorizontal, Home, Archive, ArchiveRestore, Trash2, GitPullRequest, Pin } from 'lucide-react'; +import { ChevronDown, ChevronRight, Plus, FolderPlus, GitBranch, MoreHorizontal, Home, Archive, ArchiveRestore, Trash2, GitPullRequest, Pin, Monitor } from 'lucide-react'; import { SessionDetailTooltip } from './SessionDetailTooltip'; import { useSessionStore } from '../stores/sessionStore'; import { useNavigationStore } from '../stores/navigationStore'; @@ -19,9 +19,17 @@ import { usePanelStore } from '../stores/panelStore'; interface ProjectSessionListProps { sessionSortAscending: boolean; + showRemoteDesktopLink?: boolean; + onRemoteDesktopClick?: () => void; + remoteDesktopTooltip?: string; } -export function ProjectSessionList({ sessionSortAscending }: ProjectSessionListProps) { +export function ProjectSessionList({ + sessionSortAscending, + showRemoteDesktopLink = false, + onRemoteDesktopClick, + remoteDesktopTooltip, +}: ProjectSessionListProps) { const [projects, setProjects] = useState([]); const [expandedProjects, setExpandedProjects] = useState>(new Set()); const [showCreateDialog, setShowCreateDialog] = useState(false); @@ -336,6 +344,19 @@ export function ProjectSessionList({ sessionSortAscending }: ProjectSessionListP Home + {showRemoteDesktopLink && onRemoteDesktopClick && ( + + + + )} + {pinnedSessions.length > 0 && ( <>
diff --git a/frontend/src/components/SessionView.tsx b/frontend/src/components/SessionView.tsx index 71666d24..20c30fc1 100644 --- a/frontend/src/components/SessionView.tsx +++ b/frontend/src/components/SessionView.tsx @@ -53,6 +53,7 @@ export const SessionView = memo(() => { () => (config?.customCommands ?? []).filter(cmd => cmd?.name && cmd?.command), [config?.customCommands] ); + const isRemoteMode = config?.remoteDaemon?.client.mode === 'remote'; const deleteCustomCommand = useCallback((index: number) => { const existing = config?.customCommands ?? []; updateConfig({ customCommands: existing.filter((_, i) => i !== index) }).catch(() => {}); @@ -598,6 +599,13 @@ export const SessionView = memo(() => { const handleOpenIDEWithCommand = useCallback(async (ideKey?: string) => { if (!activeSession) return; + if (isRemoteMode) { + useErrorStore.getState().showError({ + title: 'Open IDE unavailable', + error: 'Open in IDE is only available in local mode. Switch this client back to the local runtime to use your desktop IDE.', + }); + return; + } try { const response = await API.sessions.openIDE(activeSession.id, ideKey); if (!response.success) { @@ -612,7 +620,7 @@ export const SessionView = memo(() => { error: error instanceof Error ? error.message : 'Unknown error occurred', }); } - }, [activeSession]); + }, [activeSession, isRemoteMode]); // Detail panel state const [detailVisible, setDetailVisible] = useState(() => { @@ -963,7 +971,7 @@ export const SessionView = memo(() => { return (
{/* SINGLE SessionProvider wraps everything */} - setShowProjectSettings(true)} onSetTracking={handleOpenSetTracking} trackingBranch={currentUpstream} configuredIDECommand={sessionProject?.open_ide_command}> + setShowProjectSettings(true)} onSetTracking={handleOpenSetTracking} trackingBranch={currentUpstream} configuredIDECommand={sessionProject?.open_ide_command} isRemoteMode={isRemoteMode}> {/* Tab bar at top */} (null); const [verbose, setVerbose] = useState(false); + const [falApiKey, setFalApiKey] = useState(''); + const [openRouterApiKey, setOpenRouterApiKey] = useState(''); + const [deepgramApiKey, setDeepgramApiKey] = useState(''); + const [voiceTranscriptionMode, setVoiceTranscriptionMode] = useState('streaming'); const [claudeExecutablePath, setClaudeExecutablePath] = useState(''); const [autoCheckUpdates, setAutoCheckUpdates] = useState(true); const [devMode, setDevMode] = useState(false); @@ -75,13 +159,155 @@ export function Settings({ isOpen, onClose, initialSection }: SettingsProps) { const [activeTab, setActiveTab] = useState<'general' | 'notifications' | 'shortcuts'>('general'); const [analyticsEnabled, setAnalyticsEnabled] = useState(true); const [previousAnalyticsEnabled, setPreviousAnalyticsEnabled] = useState(true); - const [preferredShell, setPreferredShell] = useState('auto'); - const [availableShells, setAvailableShells] = useState>([]); + const [preferredShell, setPreferredShell] = useState('auto'); + const [availableShells, setAvailableShells] = useState([]); const [terminalShortcuts, setTerminalShortcuts] = useState([]); const [worktreeFileSync, setWorktreeFileSync] = useState([]); + const [remoteDaemonConfig, setRemoteDaemonConfig] = useState(createDefaultRemoteDaemonConfig()); + const [remoteConnectionState, setRemoteConnectionState] = useState(createDefaultRemotePaneConnectionState()); + const [remoteHostState, setRemoteHostState] = useState(createDefaultRemoteDaemonHostRuntimeState()); + const [remoteHostConfigDraft, setRemoteHostConfigDraft] = useState(createDefaultRemoteDaemonConfig().host.config); + const [remotePairLabel, setRemotePairLabel] = useState(''); + const [remotePairBaseUrl, setRemotePairBaseUrl] = useState('http://127.0.0.1:42137'); + const [remoteCreatedToken, setRemoteCreatedToken] = useState(null); + const [remoteConnectionCode, setRemoteConnectionCode] = useState(''); + const [remoteImportResult, setRemoteImportResult] = useState(null); + const [remoteImportedProfileLabel, setRemoteImportedProfileLabel] = useState(''); + const [remoteImportedProfileBaseUrl, setRemoteImportedProfileBaseUrl] = useState('http://127.0.0.1:42137'); + const [remoteImportedProfileToken, setRemoteImportedProfileToken] = useState(''); + const [remoteBusy, setRemoteBusy] = useState(false); + const [remoteSetupTerminalBusy, setRemoteSetupTerminalBusy] = useState(false); + const [remoteClientSetupTerminalBusy, setRemoteClientSetupTerminalBusy] = useState(false); + const [remoteClientSetupError, setRemoteClientSetupError] = useState(null); + const [remoteImportRecoveryProfileId, setRemoteImportRecoveryProfileId] = useState(null); + const [remoteImportRecoveryError, setRemoteImportRecoveryError] = useState(null); + const [remoteSetupDataMode, setRemoteSetupDataMode] = useState('current'); + const [remoteSetupLabel, setRemoteSetupLabel] = useState(''); + const [remoteSetupListenPort, setRemoteSetupListenPort] = useState(42137); + const [remoteSetupPaneDir, setRemoteSetupPaneDir] = useState(''); + const [remoteSetupTunnelPreference, setRemoteSetupTunnelPreference] = useState('tailscale'); + const [remoteSetupManualBaseUrl, setRemoteSetupManualBaseUrl] = useState(''); + const [remoteSetupInstallService, setRemoteSetupInstallService] = useState(true); + const [remoteSetupResult, setRemoteSetupResult] = useState(null); + const [remoteHostConnectionCode, setRemoteHostConnectionCode] = useState(null); + const [remoteSetupCopyResult, setRemoteSetupCopyResult] = useState(null); + const [remoteSetupError, setRemoteSetupError] = useState(null); + const fontsLoadedForOpenRef = useRef(false); const { updateSettings } = useNotifications(); const { theme, setTheme } = useTheme(); const { fetchConfig: refreshConfigStore } = useConfigStore(); + const activeProjectId = useNavigationStore((state) => state.activeProjectId); + const navigateToSessions = useNavigationStore((state) => state.navigateToSessions); + const activeSessionProjectId = useSessionStore((state) => { + const activeSession = state.sessions.find((session) => session.id === state.activeSessionId) + ?? state.activeMainRepoSession; + return activeSession?.projectId ?? null; + }); + const setActiveSession = useSessionStore((state) => state.setActiveSession); + + const refreshRemoteDaemonSettings = useCallback(async () => { + const [configResponse, connectionStateResponse, hostStateResponse] = await Promise.all([ + API.remoteDaemon.getConfig(), + API.remoteDaemon.getConnectionState(), + API.remoteDaemon.getHostState(), + ]); + + if (configResponse.success && configResponse.data) { + setRemoteDaemonConfig(configResponse.data); + setRemoteHostConfigDraft(configResponse.data.host.config); + setRemoteSetupListenPort((currentValue) => ( + currentValue === 42137 ? configResponse.data!.host.config.listenPort : currentValue + )); + setRemotePairBaseUrl((currentValue) => ( + currentValue === 'http://127.0.0.1:42137' + ? formatRemoteBaseUrl( + configResponse.data!.host.config.listenHost, + configResponse.data!.host.config.listenPort, + ) + : currentValue + )); + setRemoteImportedProfileBaseUrl((currentValue) => ( + currentValue === 'http://127.0.0.1:42137' + ? formatRemoteBaseUrl( + configResponse.data!.host.config.listenHost, + configResponse.data!.host.config.listenPort, + ) + : currentValue + )); + } + + if (connectionStateResponse.success && connectionStateResponse.data) { + setRemoteConnectionState(connectionStateResponse.data); + } + + if (hostStateResponse.success && hostStateResponse.data) { + setRemoteHostState(hostStateResponse.data); + } + }, []); + + const fetchConfig = useCallback(async (currentPlatform?: string) => { + try { + const response = await API.config.get(); + if (!response.success || !response.data) { + throw new Error(response.error || 'Failed to fetch config'); + } + const data = response.data; + setVerbose(data.verbose || false); + setFalApiKey(data.falApiKey || ''); + setOpenRouterApiKey(data.openRouterApiKey || ''); + setDeepgramApiKey(data.deepgramApiKey || ''); + setVoiceTranscriptionMode(isVoiceTranscriptionMode(data.voiceTranscriptionMode) + ? data.voiceTranscriptionMode + : 'streaming'); + setAutoCheckUpdates(data.autoCheckUpdates !== false); // Default to true + setDevMode(data.devMode || false); + setUsePtyHost(data.usePtyHost === true); + setInitialUsePtyHost(data.usePtyHost === true); + setClaudeExecutablePath(data.claudeExecutablePath || ''); + setEnableCommitFooter(data.enableCommitFooter !== false); // Default to true + setUiScale(data.uiScale || 1.0); + setTerminalFontFamily(data.terminalFontFamily || ''); + setTerminalFontSize(data.terminalFontSize || 14); + + // Load additional paths + const paths = data.additionalPaths || []; + setAdditionalPathsText(paths.join('\n')); + + // Load notification settings + if (data.notifications) { + setNotificationSettings(data.notifications); + // Update the useNotifications hook with loaded settings + updateSettings(data.notifications); + } + + // Load analytics settings + if (data.analytics) { + const enabled = data.analytics.enabled !== false; // Default to true + setAnalyticsEnabled(enabled); + setPreviousAnalyticsEnabled(enabled); + } + + // Fetch available shells on Windows + const platformToCheck = currentPlatform || platform; + if (platformToCheck === 'win32') { + const shellsResponse = await API.config.getAvailableShells(); + if (shellsResponse.success && shellsResponse.data) { + setAvailableShells(shellsResponse.data); + } + } + setPreferredShell(data.preferredShell || 'auto'); + + // Load terminal shortcuts + setTerminalShortcuts(data.terminalShortcuts ?? []); + + // Load worktree file sync entries + setWorktreeFileSync(data.worktreeFileSync ?? DEFAULT_WORKTREE_FILE_SYNC_ENTRIES); + + await refreshRemoteDaemonSettings(); + } catch { + setError('Failed to load configuration'); + } + }, [platform, refreshRemoteDaemonSettings, updateSettings]); useEffect(() => { if (isOpen) { @@ -91,12 +317,12 @@ export function Settings({ isOpen, onClose, initialSection }: SettingsProps) { fetchConfig(p); }); - // Load system monospace fonts for the font picker - window.electronAPI.config.getMonospaceFonts().then((result) => { - if (result?.data && Array.isArray(result.data)) { - setSystemMonoFonts(result.data as string[]); - } - }).catch(() => { /* fc-list not available — dropdown will be empty */ }); + const unsubscribeRemoteConnectionState = window.electronAPI.remoteDaemon.onConnectionStateChanged((state) => { + setRemoteConnectionState(state); + }); + const unsubscribeRemoteHostState = window.electronAPI.remoteDaemon.onHostStateChanged((state) => { + setRemoteHostState(state); + }); const loadAutoRename = async () => { try { @@ -132,64 +358,402 @@ export function Settings({ isOpen, onClose, initialSection }: SettingsProps) { if (initialSection === 'terminal-shortcuts') { setActiveTab('shortcuts'); } + + return () => { + unsubscribeRemoteConnectionState(); + unsubscribeRemoteHostState(); + }; } - }, [isOpen, initialSection]); + }, [fetchConfig, initialSection, isOpen]); - const fetchConfig = async (currentPlatform?: string) => { + useEffect(() => { + if (!isOpen) { + fontsLoadedForOpenRef.current = false; + return; + } + + if (fontsLoadedForOpenRef.current) { + return; + } + fontsLoadedForOpenRef.current = true; + + // macOS font enumeration can be expensive; keep it out of the broad + // settings refresh effect so state sync cannot repeatedly spawn it. + let cancelled = false; + window.electronAPI.config.getMonospaceFonts().then((result) => { + if (!cancelled && result?.data && Array.isArray(result.data)) { + setSystemMonoFonts(result.data as string[]); + } + }).catch(() => { /* fc-list not available — dropdown will be empty */ }); + + return () => { + cancelled = true; + }; + }, [isOpen]); + + const runRemoteDaemonAction = async ( + action: () => Promise, + options: { + onError?: (message: string) => void; + mirrorErrorToGlobal?: boolean; + } = {}, + ) => { + setRemoteBusy(true); + setError(null); try { - const response = await API.config.get(); - if (!response.success) throw new Error(response.error || 'Failed to fetch config'); - const data = response.data; - setConfig(data); - setVerbose(data.verbose || false); - setAutoCheckUpdates(data.autoCheckUpdates !== false); // Default to true - setDevMode(data.devMode || false); - setUsePtyHost(data.usePtyHost === true); - setInitialUsePtyHost(data.usePtyHost === true); - setClaudeExecutablePath(data.claudeExecutablePath || ''); - setEnableCommitFooter(data.enableCommitFooter !== false); // Default to true - setUiScale(data.uiScale || 1.0); - setTerminalFontFamily(data.terminalFontFamily || ''); - setTerminalFontSize(data.terminalFontSize || 14); + await action(); + await Promise.all([ + refreshRemoteDaemonSettings(), + refreshConfigStore(), + ]); + } catch (err) { + const message = err instanceof Error ? err.message : 'Remote daemon action failed'; + options.onError?.(message); + if (options.mirrorErrorToGlobal !== false) { + setError(message); + } + } finally { + setRemoteBusy(false); + } + }; - // Load additional paths - const paths = data.additionalPaths || []; - setAdditionalPathsText(paths.join('\n')); + const handleSaveRemoteHostConfig = async () => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.updateHostConfig(remoteHostConfigDraft); + if (!response.success) { + throw new Error(response.error || 'Failed to save remote daemon host config'); + } + }); + }; - // Load notification settings - if (data.notifications) { - setNotificationSettings(data.notifications); - // Update the useNotifications hook with loaded settings - updateSettings(data.notifications); + const handleStopRemoteHost = async () => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.updateHostConfig({ enabled: false }); + if (!response.success) { + throw new Error(response.error || 'Failed to stop remote host'); } - // Load analytics settings - if (data.analytics) { - const enabled = data.analytics.enabled !== false; // Default to true - setAnalyticsEnabled(enabled); - setPreviousAnalyticsEnabled(enabled); + setRemoteHostConfigDraft((current) => ({ + ...current, + enabled: false, + })); + }); + }; + + const handleClearRemoteHostAccess = async () => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.clearHostAccess(); + if (!response.success) { + throw new Error(response.error || 'Failed to forget remote host access'); } - // Fetch available shells on Windows - const platformToCheck = currentPlatform || platform; - if (platformToCheck === 'win32') { - const shellsResponse = await API.config.getAvailableShells(); - if (shellsResponse.success) { - setAvailableShells(shellsResponse.data); + setRemoteHostConnectionCode(null); + setRemoteSetupResult(null); + setRemoteSetupCopyResult('Forgot cached host code and revoked existing remote clients. Create a new code to reconnect.'); + }); + }; + + const handleDisconnectRemoteClients = async (clientIds?: string[]) => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.disconnectHostClients(clientIds); + if (!response.success) { + throw new Error(response.error || 'Failed to disconnect remote clients'); + } + }); + }; + + const handleRevokeRemoteClient = async (clientId: string) => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.deleteClientRecord(clientId); + if (!response.success) { + throw new Error(response.error || 'Failed to revoke remote client access'); + } + }); + }; + + const buildRemoteHostSetupRequest = (): RemoteHostSetupRequest => ({ + dataDirectoryMode: remoteSetupDataMode, + label: remoteSetupLabel, + listenPort: remoteSetupListenPort, + paneDir: remoteSetupDataMode === 'isolated' && remoteSetupPaneDir.trim().length > 0 + ? remoteSetupPaneDir + : undefined, + preferTunnel: remoteSetupTunnelPreference, + baseUrl: remoteSetupTunnelPreference === 'manual' && remoteSetupManualBaseUrl.trim().length > 0 + ? remoteSetupManualBaseUrl + : undefined, + installService: remoteSetupDataMode === 'isolated' ? remoteSetupInstallService : false, + }); + + const handleSetupRemoteHost = async () => { + await runRemoteDaemonAction(async () => { + setRemoteSetupResult(null); + setRemoteSetupCopyResult(null); + setRemoteSetupError(null); + const response = await API.remoteDaemon.setupHost(buildRemoteHostSetupRequest()); + if (!response.success || !response.data) { + throw new Error(response.error || 'Failed to set up this machine as a remote'); + } + + setRemoteSetupResult(response.data); + setRemoteHostConnectionCode(response.data.connectionCode); + setRemoteSetupLabel(''); + }, { + onError: setRemoteSetupError, + mirrorErrorToGlobal: false, + }); + }; + + const handleOpenRemoteSetupTerminal = async () => { + const projectId = activeProjectId ?? activeSessionProjectId; + if (!projectId) { + setRemoteSetupError('Select a project before opening a setup terminal.'); + return; + } + + setRemoteSetupTerminalBusy(true); + setRemoteSetupError(null); + setRemoteSetupResult(null); + setRemoteHostConnectionCode(null); + setRemoteSetupCopyResult(null); + + try { + const commandResponse = await API.remoteDaemon.getInteractiveSetupCommand(buildRemoteHostSetupRequest()); + if (!commandResponse.success || !commandResponse.data?.command) { + throw new Error(commandResponse.error || 'Failed to prepare the remote setup command'); + } + + const sessionResponse = await API.sessions.getOrCreateMainRepoSession(projectId); + if (!sessionResponse.success || !sessionResponse.data?.id) { + throw new Error(sessionResponse.error || 'Failed to open a project terminal'); + } + + const sessionId = sessionResponse.data.id as string; + const panel = await panelApi.createPanel({ + sessionId, + type: 'terminal', + title: 'Tailscale Setup', + initialState: { + customState: { + initialCommand: commandResponse.data.command, + }, + }, + }); + + await panelApi.setActivePanel(sessionId, panel.id); + await setActiveSession(sessionId); + navigateToSessions(); + onClose(); + } catch (err) { + setRemoteSetupError(err instanceof Error ? err.message : 'Failed to open setup terminal'); + } finally { + setRemoteSetupTerminalBusy(false); + } + }; + + const handleOpenTailscaleClientSetupTerminal = async () => { + const projectId = activeProjectId ?? activeSessionProjectId; + if (!projectId) { + setRemoteClientSetupError('Select a project before opening a Tailscale setup terminal.'); + return; + } + + setRemoteClientSetupTerminalBusy(true); + setRemoteClientSetupError(null); + setError(null); + + try { + const commandResponse = await API.remoteDaemon.getInteractiveClientSetupCommand(); + if (!commandResponse.success || !commandResponse.data?.command) { + throw new Error(commandResponse.error || 'Failed to prepare the Tailscale setup command'); + } + + if (remoteConnectionState.mode === 'remote') { + const localResponse = await API.remoteDaemon.updateClientState({ + activeProfileId: null, + mode: 'local', + }); + if (!localResponse.success) { + throw new Error(localResponse.error || 'Failed to switch to local runtime before opening Tailscale setup'); } } - setPreferredShell(data.preferredShell || 'auto'); - // Load terminal shortcuts - setTerminalShortcuts(data.terminalShortcuts ?? []); + const sessionResponse = await API.sessions.getOrCreateMainRepoSession(projectId); + if (!sessionResponse.success || !sessionResponse.data?.id) { + throw new Error(sessionResponse.error || 'Failed to open a project terminal'); + } - // Load worktree file sync entries - setWorktreeFileSync(data.worktreeFileSync ?? DEFAULT_WORKTREE_FILE_SYNC_ENTRIES); + const sessionId = sessionResponse.data.id as string; + const panel = await panelApi.createPanel({ + sessionId, + type: 'terminal', + title: 'Tailscale Client Setup', + initialState: { + customState: { + initialCommand: commandResponse.data.command, + }, + }, + }); + + await panelApi.setActivePanel(sessionId, panel.id); + await setActiveSession(sessionId); + navigateToSessions(); + onClose(); } catch (err) { - setError('Failed to load configuration'); + setRemoteClientSetupError(err instanceof Error ? err.message : 'Failed to open Tailscale setup terminal'); + } finally { + setRemoteClientSetupTerminalBusy(false); + } + }; + + const handleCopyRemoteSetupConnectionCode = async () => { + if (!remoteSetupResult) { + return; + } + try { + await navigator.clipboard.writeText(remoteSetupResult.connectionCode); + setRemoteSetupCopyResult('Copied connection code.'); + } catch { + setError('Failed to copy connection code'); } }; + const handleCopyRemoteHostConnectionCode = async () => { + const connectionCode = remoteHostConnectionCode ?? remoteSetupResult?.connectionCode; + if (!connectionCode) { + return; + } + try { + await navigator.clipboard.writeText(connectionCode); + setRemoteSetupCopyResult('Copied connection code.'); + } catch { + setError('Failed to copy connection code'); + } + }; + + const handleCreateAndCopyRemoteHostCode = async () => { + await runRemoteDaemonAction(async () => { + setRemoteSetupCopyResult(null); + setRemoteSetupError(null); + const response = await API.remoteDaemon.createHostConnectionCode({ + label: remoteSetupLabel, + }); + if (!response.success || !response.data) { + throw new Error(response.error || 'Failed to create remote connection code'); + } + + setRemoteHostConnectionCode(response.data.connectionCode); + try { + await navigator.clipboard.writeText(response.data.connectionCode); + setRemoteSetupCopyResult('Created and copied connection code.'); + } catch { + setRemoteSetupCopyResult('Connection code ready. Click the code to copy it.'); + } + }, { + onError: setRemoteSetupError, + mirrorErrorToGlobal: false, + }); + }; + + const handleCreateRemoteConnectionPair = async () => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.createConnectionPair({ + label: remotePairLabel, + baseUrl: remotePairBaseUrl, + }); + if (!response.success) { + throw new Error(response.error || 'Failed to create remote daemon connection pair'); + } + + setRemotePairLabel(''); + setRemoteCreatedToken(response.data?.token ?? null); + }); + }; + + const handleUseRemoteProfile = async (profileId: string) => { + await runRemoteDaemonAction(async () => { + setRemoteImportRecoveryProfileId(null); + setRemoteImportRecoveryError(null); + setRemoteClientSetupError(null); + const response = await API.remoteDaemon.updateClientState({ + activeProfileId: profileId, + mode: 'remote', + }); + if (!response.success) { + throw new Error(response.error || 'Failed to connect to remote daemon profile'); + } + }); + }; + + const handleSwitchToLocalMode = async () => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.updateClientState({ + mode: 'local', + }); + if (!response.success) { + throw new Error(response.error || 'Failed to return to local mode'); + } + }); + }; + + const handleDeleteRemoteProfile = async (profileId: string) => { + await runRemoteDaemonAction(async () => { + const response = await API.remoteDaemon.deleteConnectionProfile(profileId); + if (!response.success) { + throw new Error(response.error || 'Failed to delete remote daemon connection profile'); + } + }); + }; + + const handleSaveRemoteProfile = async () => { + await runRemoteDaemonAction(async () => { + const profile: RemotePaneConnectionProfile = { + id: crypto.randomUUID(), + label: remoteImportedProfileLabel.trim(), + baseUrl: remoteImportedProfileBaseUrl.trim(), + token: remoteImportedProfileToken.trim(), + transport: 'http+sse', + }; + + const response = await API.remoteDaemon.upsertConnectionProfile(profile); + if (!response.success) { + throw new Error(response.error || 'Failed to save remote daemon connection profile'); + } + + setRemoteImportedProfileLabel(''); + setRemoteImportedProfileToken(''); + }); + }; + + const handleImportRemoteConnectionCode = async () => { + await runRemoteDaemonAction(async () => { + setRemoteImportResult(null); + setRemoteImportRecoveryProfileId(null); + setRemoteImportRecoveryError(null); + setRemoteClientSetupError(null); + const response = await API.remoteDaemon.importConnectionCode(remoteConnectionCode, { + connect: true, + }); + if (!response.success) { + throw new Error(response.error || 'Failed to import remote daemon connection code'); + } + + const result = response.data; + setRemoteConnectionCode(''); + if (result?.connected) { + setRemoteImportResult(`Connected to ${result.profile.label}.`); + } else if (result?.connectionError) { + setRemoteImportResult(`Saved ${result.profile.label}, but connection failed: ${result.connectionError}`); + if (isTailscaleDnsConnectionFailure(result.profile, result.connectionError)) { + setRemoteImportRecoveryProfileId(result.profile.id); + setRemoteImportRecoveryError(result.connectionError); + } + } else if (result?.profile) { + setRemoteImportResult(`Saved ${result.profile.label}.`); + } + }); + }; + const handleAutoRenameToggle = async (checked: boolean) => { setAutoRenameToPR(checked); try { @@ -215,6 +779,10 @@ export function Settings({ isOpen, onClose, initialSection }: SettingsProps) { const response = await API.config.update({ verbose, + falApiKey: falApiKey.trim() || undefined, + openRouterApiKey: openRouterApiKey.trim() || undefined, + deepgramApiKey: deepgramApiKey.trim() || undefined, + voiceTranscriptionMode, autoCheckUpdates, devMode, usePtyHost, @@ -235,78 +803,742 @@ export function Settings({ isOpen, onClose, initialSection }: SettingsProps) { throw new Error(response.error || 'Failed to update configuration'); } - // Only toggle PostHog opt-in/opt-out after config save succeeds - if (previousAnalyticsEnabled !== analyticsEnabled) { - if (analyticsEnabled) { - optIn(); - capture('analytics_opted_in'); - } else { - captureAndOptOut('analytics_opted_out'); - } - } + // Only toggle PostHog opt-in/opt-out after config save succeeds + if (previousAnalyticsEnabled !== analyticsEnabled) { + if (analyticsEnabled) { + optIn(); + capture('analytics_opted_in'); + } else { + captureAndOptOut('analytics_opted_out'); + } + } + + // Update the useNotifications hook with new settings + updateSettings(notificationSettings); + + // Refresh config from server + await fetchConfig(); + + // Also refresh the global config store + await refreshConfigStore(); + + onClose(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to update configuration'); + } finally { + setIsSubmitting(false); + } + }; + + const remoteSetupRequiresManualBaseUrl = + remoteSetupTunnelPreference === 'manual' && remoteSetupManualBaseUrl.trim().length === 0; + const remoteSetupPortIsValid = + Number.isInteger(remoteSetupListenPort) && remoteSetupListenPort > 0 && remoteSetupListenPort <= 65535; + const remoteHostRuntimePresentation = getRemoteHostRuntimePresentation(remoteHostState); + const liveRemoteClientIds = new Set( + remoteHostState.connectedClients + .map((client) => client.clientId) + .filter((clientId): clientId is string => Boolean(clientId)), + ); + const remoteSetupFallbackCommands = remoteSetupResult + ? remoteSetupResult.fallbackTunnelCommands.filter((command) => command !== remoteSetupResult.tunnel?.command) + : []; + const activeRemoteHostConnectionCode = remoteHostConnectionCode ?? remoteSetupResult?.connectionCode ?? null; + const activeRemoteProfile = remoteConnectionState.activeProfileId + ? remoteDaemonConfig.client.profiles.find((profile) => profile.id === remoteConnectionState.activeProfileId) + : undefined; + const importRecoveryProfile = remoteImportRecoveryProfileId + ? remoteDaemonConfig.client.profiles.find((profile) => profile.id === remoteImportRecoveryProfileId) + : undefined; + const activeTailscaleDnsFailure = isTailscaleDnsConnectionFailure(activeRemoteProfile, remoteConnectionState.lastError); + const importTailscaleDnsFailure = isTailscaleDnsConnectionFailure(importRecoveryProfile, remoteImportRecoveryError); + const remoteLastSeenText = formatRemoteLastSeen(remoteConnectionState.lastSeenAt); + const renderTailscaleClientRecovery = ( + profile: RemotePaneConnectionProfile | undefined, + errorMessage: string | null, + ) => { + if (!profile || !errorMessage) { + return null; + } + + return ( +
+
+ +
+

Set up Tailscale on this device

+

+ This profile uses Tailscale, but Pane cannot resolve {getRemoteProfileHostname(profile) ?? 'the remote host'}. + Install Tailscale and sign in to the same account on this device, then retry the connection. +

+

{errorMessage}

+
+
+ {remoteClientSetupError && ( +
+ {remoteClientSetupError} +
+ )} +
+ + + +
+
+ ); + }; + + return ( + + } + onClose={onClose} + /> + + + {/* Tabs */} +
+ + + +
+ + {activeTab === 'general' && ( +
+ } + defaultExpanded={true} + > + } + spacing="sm" + > +
+ + +
+ +
+
+ +
+

{remoteHostRuntimePresentation.title}

+

{remoteHostRuntimePresentation.description}

+
+
+ {(remoteHostState.status === 'live' || remoteDaemonConfig.host.config.enabled) && ( + + )} + {remoteDaemonConfig.host.access && ( + + )} + {remoteHostState.status === 'live' && remoteHostState.connectedClients.length > 0 && ( + + )} + {(remoteHostState.status === 'live' || remoteDaemonConfig.host.config.enabled) && ( + + )} +
+
+ {activeRemoteHostConnectionCode && ( + + )} + {remoteSetupCopyResult && ( +

{remoteSetupCopyResult}

+ )} + {remoteSetupError && ( +

+ {remoteSetupError} +

+ )} + {remoteDaemonConfig.host.clients.length > 0 && ( +
+

Paired clients

+
+ {remoteDaemonConfig.host.clients.map((client) => { + const isLive = liveRemoteClientIds.has(client.id); + return ( +
+
+

+ {client.label} + {isLive && connected} +

+

+ {client.lastUsedAt ? formatRemoteLastSeen(client.lastUsedAt) : 'Never used.'} +

+
+
+ {isLive && ( + + )} + +
+
+ ); + })} +
+

+ Disconnect drops live clients now. Revoke removes access so the client cannot reconnect with that saved token. +

+
+ )} +
+ +
+ setRemoteSetupLabel(e.target.value)} + placeholder="Office Mac mini" + fullWidth + /> + setRemoteSetupListenPort(Number.parseInt(e.target.value, 10) || 42137)} + error={remoteSetupPortIsValid ? undefined : 'Port must be between 1 and 65535'} + fullWidth + /> +
+ + {remoteSetupDataMode === 'isolated' && ( +
+ setRemoteSetupPaneDir(e.target.value)} + placeholder="Default: ~/.pane_remote" + fullWidth + /> + setRemoteSetupInstallService(e.target.checked)} + /> +
+ )} + +
+

Access Mode

+
+ {[ + ['tailscale', 'Tailscale', 'Recommended for another device or network.'], + ['ssh', 'SSH Tunnel', 'Advanced local forwarding.'], + ['manual', 'Manual HTTPS', 'Advanced custom tunnel URL.'], + ].map(([id, label, description]) => ( + + ))} +
+
+ + {remoteSetupTunnelPreference === 'tailscale' && ( +

+ Pane can open a terminal that installs Tailscale if needed, walks through login, and configures Tailscale Serve for this daemon. +

+ )} + + {remoteSetupTunnelPreference === 'ssh' && ( +

+ SSH tunnel mode creates a localhost connection code and requires running the generated SSH command on the client machine before connecting. +

+ )} + + {remoteSetupTunnelPreference === 'manual' && ( + setRemoteSetupManualBaseUrl(e.target.value)} + placeholder="https://pane-remote.example.com" + error={remoteSetupRequiresManualBaseUrl ? 'HTTPS base URL is required for manual mode' : undefined} + fullWidth + /> + )} + +
+

+ Tailscale is recommended for another device or network. Current Pane Data keeps this host live while Pane is open; Isolated Daemon Data can install a service. +

+ +
+ + {remoteSetupError && ( +
+
+ +

{remoteSetupError}

+
+ {remoteSetupError.toLowerCase().includes('tailscale') && ( +
+ + +
+ )} +
+ )} + + {remoteSetupResult && ( +
+
+
+

Connection code ready

+

+ {remoteSetupResult.label} · {remoteSetupResult.paneDir} +

+
+ +
+