mirror of
https://github.com/humanlayer/humanlayer.git
synced 2025-08-20 19:01:22 +03:00
Allison/eng 1784 bundle daemon and cli with wui for single installation (#385)
* feat(daemon): add dynamic port allocation support
- Always create HTTP server regardless of port configuration
- Support port 0 for dynamic allocation
- Output actual port to stdout when using dynamic allocation
- Enable WUI to start daemon on any available port
* feat(wui): add daemon process management infrastructure
- Add Tauri shell and store plugins for process and state management
- Implement Rust daemon manager with automatic startup on app launch
- Add branch-based daemon isolation for development environments
- Create TypeScript services and hooks for daemon lifecycle management
- Add debug panel UI for manual daemon control in dev mode
- Support automatic daemon connection with managed port discovery
- Handle graceful daemon shutdown on app close
- Implement retry logic for daemon connection during startup
* feat(wui): add daemon process management infrastructure
* chore(wui): update gitignore for daemon artifacts
* build(wui): update Tauri bundle configuration
* build: add homebrew formula and update CI for daemon bundling
* formatting
* makefile to build daemon too (when run wui on dev)
* refactor: improve daemon management in WUI with async/await and better error handling
- Convert daemon start/stop operations to async/await pattern
- Switch HTTP host from 127.0.0.1 to localhost for better compatibility
- Add debug URL override support for development
- Improve error handling and startup behavior
- Fix path resolution for development mode when running from src-tauri
* daemon no longer randomly crashes
Needed to do weird stdout things
* Parse daemon log levels to improve dev logging output
Previously all daemon stderr output was logged as ERROR level, causing confusion
and double timestamps. Now we parse slog format to extract actual log levels
(INFO, WARN, ERROR, etc.) and route them to appropriate tracing levels.
* Integrate debug panel into status bar with theme-aware colors
- Move debug panel button from floating bottom-left to status bar bottom-right
- Replace Settings icon with Bug icon and match styling of other status controls
- Update connection status colors to use CSS variables for theme consistency
- Refactor DebugPanel to accept open/onOpenChange props for controlled state
* Remove local homebrew tap files - moved to separate repository
The homebrew tap is now properly maintained in its own repository
at humanlayer/homebrew-humanlayer for public distribution.
* Migrate WUI backend from tracing to log crate with tauri-plugin-log
- Replace tracing with log crate for consistency
- Add tauri-plugin-log with file rotation (50MB) and proper formatting
- Configure LogDir, Stdout (dev), and Webview targets
- Add proper prefixes: [Tauri] for WUI logs, [Daemon] for daemon logs
- Add log:default permission to capabilities
* Add frontend logging infrastructure for WUI
- Install @tauri-apps/plugin-log and @tauri-apps/api dependencies
- Create logging service with JSON serialization for objects
- Add log location notification for production builds
- Initialize attachConsole to display Rust logs in browser console
- All frontend logs will use [Console] prefix
* Migrate all console calls to use logger throughout WUI
- Replace 94 console.* calls with logger.* across 24 files
- Ensures all frontend logs are captured in production log file
- Maintains browser console output in development mode
- High-traffic files: NotificationService (22), AppStore (21), useSubscriptions (10)
* git ignore of compiled things
* fix it to not be annoyed
* Implement branch-based logging for WUI
- Extract branch detection logic (get_git_branch, extract_ticket_id, get_branch_id)
into shared functions in lib.rs
- Configure tauri-plugin-log to use branch-based folders in development:
~/.humanlayer/logs/wui-{branch-id}/codelayer.log
- Update daemon.rs to use shared branch detection functions
- Add get_log_directory Tauri command to expose log paths to frontend
- Update log notification service to handle branch-based directories
- All log sources (WUI backend, daemon stderr, frontend console) now write
to a single rotated log file per branch with clear source prefixes
* Update documentation and simplify Makefile for new logging structure
- Remove tee command from wui-dev target in Makefile since logging is now
handled by tauri-plugin-log
- Update DEVELOPMENT.md to show new WUI log paths in environment overview
- Update CLAUDE.md files to document branch-based log locations:
- Dev: ~/.humanlayer/logs/wui-{branch}/codelayer.log
- Prod: Platform-specific directories
- Document that logs include all sources with prefixes: [Tauri], [Daemon], [Console]
* formatting
* add touch to bins
* ci: align GitHub Actions workflows with local development setup
- Simplify main.yml workflow to use unified setup process
- Add CI detection to setup_repo.sh for conditional tool installation
- Install CI-specific tools (Claude Code CLI, UV, golangci-lint) only in CI
- Add setup-ci and ci-tools targets to Makefile for explicit CI setup
- Parallelize npm and bun dependency installations for faster setup
- Add Go and Rust tool caching to improve CI performance
- Create platform-specific dependency script for future expansion
This change reduces workflow complexity from 75+ to ~50 lines per job while
maintaining all functionality. CI and local development now share the same
setup process, improving maintainability and consistency.
* sdk needs to build in order for wui to check
This commit is contained in:
119
.github/workflows/main.yml
vendored
119
.github/workflows/main.yml
vendored
@@ -24,8 +24,14 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install Claude Code CLI
|
||||
run: npm install -g @anthropic-ai/claude-code
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
@@ -38,34 +44,6 @@ jobs:
|
||||
with:
|
||||
python-version-file: "pyproject.toml"
|
||||
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install ts deps
|
||||
run: npm -C humanlayer-ts install && npm -C hlyr install && npm -C humanlayer-ts-vercel-ai-sdk install
|
||||
|
||||
- name: Build hld SDK
|
||||
run: cd hld/sdk/typescript && bun install && bun run build
|
||||
|
||||
- name: Install wui deps
|
||||
run: bun install --cwd=humanlayer-wui
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
# TODO: Update this, let's just pull the binary?
|
||||
- name: Install golangci-lint
|
||||
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
- name: Install mockgen
|
||||
run: go install go.uber.org/mock/mockgen@latest
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@1.83.0
|
||||
with:
|
||||
@@ -78,11 +56,29 @@ jobs:
|
||||
packages: libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
version: 1.0
|
||||
|
||||
- name: Build CLI
|
||||
run: npm -C hlyr run build
|
||||
- name: Cache Go tools
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/bin
|
||||
key: go-tools-${{ runner.os }}-${{ hashFiles('**/go.mod') }}
|
||||
restore-keys: |
|
||||
go-tools-${{ runner.os }}-
|
||||
|
||||
- name: Generate Go mocks
|
||||
run: make -C hld mocks
|
||||
- name: Cache Rust tools
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Run repository setup
|
||||
run: make setup-ci
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run checks
|
||||
run: make check
|
||||
@@ -100,11 +96,15 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: humanlayer-ts/package-lock.json
|
||||
|
||||
- name: Install Claude Code CLI
|
||||
run: npm install -g @anthropic-ai/claude-code
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
@@ -115,16 +115,6 @@ jobs:
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
|
||||
- name: Install ts deps
|
||||
run: npm -C humanlayer-ts install && npm -C hlyr install && npm -C humanlayer-ts-vercel-ai-sdk install
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install mockgen
|
||||
run: go install go.uber.org/mock/mockgen@latest
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -135,22 +125,29 @@ jobs:
|
||||
packages: libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
version: 1.0
|
||||
|
||||
- name: Build CLI
|
||||
run: npm -C hlyr run build
|
||||
|
||||
- name: Generate Go mocks
|
||||
run: make -C hld mocks
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- name: Cache Go tools
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
bun-version: latest
|
||||
path: ~/go/bin
|
||||
key: go-tools-${{ runner.os }}-${{ hashFiles('**/go.mod') }}
|
||||
restore-keys: |
|
||||
go-tools-${{ runner.os }}-
|
||||
|
||||
- name: Build hld SDK
|
||||
run: cd hld/sdk/typescript && bun install && bun run build
|
||||
- name: Cache Rust tools
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install wui deps
|
||||
run: bun install --cwd=humanlayer-wui
|
||||
- name: Run repository setup
|
||||
run: make setup-ci
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
24
.github/workflows/release-macos.yml
vendored
24
.github/workflows/release-macos.yml
vendored
@@ -48,16 +48,32 @@ jobs:
|
||||
working-directory: humanlayer-wui
|
||||
run: bun install
|
||||
|
||||
- name: Build daemon for macOS ARM
|
||||
working-directory: hld
|
||||
run: GOOS=darwin GOARCH=arm64 go build -o hld-darwin-arm64 ./cmd/hld
|
||||
|
||||
- name: Build humanlayer CLI for macOS ARM
|
||||
working-directory: hlyr
|
||||
run: |
|
||||
bun install
|
||||
bun run build
|
||||
bun build ./dist/index.js --compile --target=bun-darwin-arm64 --outfile=humanlayer-darwin-arm64
|
||||
chmod +x humanlayer-darwin-arm64
|
||||
|
||||
- name: Copy binaries to Tauri resources
|
||||
run: |
|
||||
mkdir -p humanlayer-wui/src-tauri/bin
|
||||
cp hld/hld-darwin-arm64 humanlayer-wui/src-tauri/bin/hld
|
||||
cp hlyr/humanlayer-darwin-arm64 humanlayer-wui/src-tauri/bin/humanlayer
|
||||
chmod +x humanlayer-wui/src-tauri/bin/hld
|
||||
chmod +x humanlayer-wui/src-tauri/bin/humanlayer
|
||||
|
||||
- name: Build Tauri app (including DMG)
|
||||
working-directory: humanlayer-wui
|
||||
run: bun run tauri build
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: "-" # Ad-hoc signing to prevent "damaged" error
|
||||
|
||||
- name: Build daemon for macOS ARM
|
||||
working-directory: hld
|
||||
run: GOOS=darwin GOARCH=arm64 go build -o hld-darwin-arm64 ./cmd/hld
|
||||
|
||||
- name: Upload DMG artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -47,7 +47,8 @@ npx humanlayer launch "implement feature X" --daemon-socket ~/.humanlayer/daemon
|
||||
| Daemon Binary | `hld/hld-nightly` | `hld/hld-dev` |
|
||||
| Socket Path | `~/.humanlayer/daemon.sock` | `~/.humanlayer/daemon-dev.sock` |
|
||||
| Database | `~/.humanlayer/daemon.db` | `~/.humanlayer/dev/daemon-TIMESTAMP.db` |
|
||||
| Log Files | `daemon-nightly-*.log` | `daemon-dev-*.log` |
|
||||
| Daemon Logs | `daemon-nightly-*.log` | `daemon-dev-*.log` |
|
||||
| WUI Logs | Platform-specific | `~/.humanlayer/logs/wui-{branch}/codelayer.log` |
|
||||
| WUI | Installed in `~/Applications` | Running in dev mode |
|
||||
|
||||
### Available Commands
|
||||
@@ -177,6 +178,7 @@ HUMANLAYER_DATABASE_PATH=~/.humanlayer/dev/daemon-20240717-143022.db make daemon
|
||||
**Q: How do I know which environment I'm in?**
|
||||
- Check WUI title bar: shows "dev" for dev daemon
|
||||
- Check daemon logs: `tail -f ~/.humanlayer/logs/daemon-*.log`
|
||||
- Check WUI logs: `tail -f ~/.humanlayer/logs/wui-*/codelayer.log`
|
||||
|
||||
## Other Development Commands
|
||||
|
||||
|
||||
56
Makefile
56
Makefile
@@ -1,6 +1,19 @@
|
||||
.PHONY: setup
|
||||
setup: ## Set up the repository with all dependencies and builds
|
||||
hack/setup_repo.sh
|
||||
|
||||
# CI-specific targets
|
||||
.PHONY: setup-ci ci-tools
|
||||
|
||||
## CI Setup Commands
|
||||
setup-ci: ci-tools setup ## Complete CI setup including CI-specific tools
|
||||
@echo "✅ CI setup complete"
|
||||
|
||||
ci-tools: ## Install CI-specific tools
|
||||
@echo "Installing CI-specific tools..."
|
||||
@command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
|
||||
@command -v uv >/dev/null 2>&1 || (curl -LsSf https://astral.sh/uv/install.sh | sh)
|
||||
@command -v golangci-lint >/dev/null 2>&1 || go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
thoughts:
|
||||
humanlayer thoughts init --directory humanlayer
|
||||
|
||||
@@ -500,6 +513,39 @@ wui-nightly-build:
|
||||
cp -r humanlayer-wui/src-tauri/target/release/bundle/macos/CodeLayer.app ~/Applications/
|
||||
@echo "Installed WUI nightly to ~/Applications/CodeLayer.app"
|
||||
|
||||
# Build humanlayer binary for bundling
|
||||
.PHONY: humanlayer-build
|
||||
humanlayer-build:
|
||||
@echo "Building humanlayer CLI binary..."
|
||||
cd hlyr && bun install && bun run build
|
||||
@echo "humanlayer binary built at hlyr/dist/index.js"
|
||||
|
||||
# Build humanlayer standalone binary (requires bun)
|
||||
.PHONY: humanlayer-binary-darwin-arm64
|
||||
humanlayer-binary-darwin-arm64: humanlayer-build
|
||||
@echo "Creating standalone humanlayer binary for macOS ARM64..."
|
||||
cd hlyr && bun build ./dist/index.js --compile --target=bun-darwin-arm64 --outfile=humanlayer-darwin-arm64
|
||||
chmod +x hlyr/humanlayer-darwin-arm64
|
||||
@echo "Standalone binary created at hlyr/humanlayer-darwin-arm64"
|
||||
|
||||
# Build CodeLayer with bundled daemon and humanlayer
|
||||
.PHONY: codelayer-bundle
|
||||
codelayer-bundle:
|
||||
@echo "Building daemon for bundling..."
|
||||
cd hld && GOOS=darwin GOARCH=arm64 go build -o hld-darwin-arm64 ./cmd/hld
|
||||
@echo "Building humanlayer for bundling..."
|
||||
cd hlyr && bun install && bun run build
|
||||
cd hlyr && bun build ./dist/index.js --compile --target=bun-darwin-arm64 --outfile=humanlayer-darwin-arm64
|
||||
@echo "Copying binaries to CodeLayer resources..."
|
||||
mkdir -p humanlayer-wui/src-tauri/bin
|
||||
cp hld/hld-darwin-arm64 humanlayer-wui/src-tauri/bin/hld
|
||||
cp hlyr/humanlayer-darwin-arm64 humanlayer-wui/src-tauri/bin/humanlayer
|
||||
chmod +x humanlayer-wui/src-tauri/bin/hld
|
||||
chmod +x humanlayer-wui/src-tauri/bin/humanlayer
|
||||
@echo "Building CodeLayer with bundled binaries..."
|
||||
cd humanlayer-wui && bun run tauri build
|
||||
@echo "Build complete!"
|
||||
|
||||
# Open nightly WUI
|
||||
.PHONY: wui-nightly
|
||||
wui-nightly: wui-nightly-build
|
||||
@@ -570,16 +616,18 @@ daemon-dev: daemon-dev-build
|
||||
echo "$(TIMESTAMP) starting dev daemon in $$(pwd)" > ~/.humanlayer/logs/daemon-dev-$(TIMESTAMP).log
|
||||
cd hld && HUMANLAYER_DATABASE_PATH=~/.humanlayer/daemon-dev.db \
|
||||
HUMANLAYER_DAEMON_SOCKET=~/.humanlayer/daemon-dev.sock \
|
||||
HUMANLAYER_DAEMON_HTTP_PORT=0 \
|
||||
HUMANLAYER_DAEMON_VERSION_OVERRIDE=$$(git branch --show-current) \
|
||||
./run-with-logging.sh ~/.humanlayer/logs/daemon-dev-$(TIMESTAMP).log ./hld-dev
|
||||
|
||||
# Run dev WUI with custom socket
|
||||
.PHONY: wui-dev
|
||||
wui-dev:
|
||||
@mkdir -p ~/.humanlayer/logs
|
||||
$(eval TIMESTAMP := $(shell date +%Y-%m-%d-%H-%M-%S))
|
||||
echo "$(TIMESTAMP) starting dev wui in $$(pwd)" > ~/.humanlayer/logs/wui-dev-$(TIMESTAMP).log
|
||||
cd humanlayer-wui && HUMANLAYER_DAEMON_SOCKET=~/.humanlayer/daemon-dev.sock bun run tauri dev 2>&1 | tee -a ~/.humanlayer/logs/wui-dev-$(TIMESTAMP).log
|
||||
cd humanlayer-wui && HUMANLAYER_DAEMON_SOCKET=~/.humanlayer/daemon-dev.sock bun run tauri dev
|
||||
|
||||
# Alias for wui-dev that ensures daemon is built first
|
||||
.PHONY: codelayer-dev
|
||||
codelayer-dev: daemon-dev-build wui-dev
|
||||
|
||||
# Show current dev environment setup
|
||||
.PHONY: dev-status
|
||||
|
||||
17
hack/install_platform_deps.sh
Executable file
17
hack/install_platform_deps.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Detect platform
|
||||
case "$(uname -s)" in
|
||||
Linux*)
|
||||
if [ -n "$CI" ]; then
|
||||
echo "📦 Installing Linux-specific dependencies for CI..."
|
||||
# Note: apt packages are handled by GitHub Actions cache
|
||||
# This is a placeholder for any additional Linux setup
|
||||
fi
|
||||
;;
|
||||
Darwin*)
|
||||
# macOS-specific setup if needed
|
||||
;;
|
||||
esac
|
||||
@@ -5,52 +5,84 @@
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Helper function to run commands silently unless they fail
|
||||
run_silent() {
|
||||
local description="$1"
|
||||
shift
|
||||
local temp_output=$(mktemp)
|
||||
# Source the run_silent utility
|
||||
source hack/run_silent.sh
|
||||
|
||||
if "$@" > "$temp_output" 2>&1; then
|
||||
rm "$temp_output"
|
||||
else
|
||||
cat "$temp_output"
|
||||
echo
|
||||
echo "❌ $description failed. Please check the output above."
|
||||
rm "$temp_output"
|
||||
exit 1
|
||||
# Detect if running in CI
|
||||
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
|
||||
IS_CI=true
|
||||
else
|
||||
IS_CI=false
|
||||
fi
|
||||
|
||||
# Function to install CI-specific tools
|
||||
install_ci_tools() {
|
||||
echo "🔧 Installing CI-specific tools..."
|
||||
|
||||
# Install Claude Code CLI
|
||||
run_silent "Installing Claude Code CLI" "npm install -g @anthropic-ai/claude-code"
|
||||
|
||||
# Install UV for Python
|
||||
if ! command -v uv &> /dev/null; then
|
||||
run_silent "Installing UV" "curl -LsSf https://astral.sh/uv/install.sh | sh"
|
||||
fi
|
||||
|
||||
# Install golangci-lint
|
||||
if ! command -v golangci-lint &> /dev/null; then
|
||||
run_silent "Installing golangci-lint" "go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "🔧 Setting up HumanLayer repository..."
|
||||
# Main setup flow
|
||||
echo "🚀 Setting up HumanLayer repository..."
|
||||
|
||||
# Install CI tools if in CI environment
|
||||
if [ "$IS_CI" = true ]; then
|
||||
install_ci_tools
|
||||
fi
|
||||
|
||||
# Install mockgen if not already installed
|
||||
if ! command -v mockgen &> /dev/null; then
|
||||
echo "📦 Installing mockgen..."
|
||||
run_silent "mockgen installation" go install go.uber.org/mock/mockgen@latest
|
||||
run_silent "mockgen installation" "go install go.uber.org/mock/mockgen@latest"
|
||||
else
|
||||
echo "✓ mockgen already installed"
|
||||
fi
|
||||
|
||||
# Repository-specific setup commands
|
||||
echo "📦 Generating HLD mocks..."
|
||||
run_silent "HLD mock generation" make -C hld mocks
|
||||
run_silent "HLD mock generation" "make -C hld mocks"
|
||||
|
||||
echo "📦 Installing NPM dependencies..."
|
||||
run_silent "hlyr npm install" npm i -C hlyr
|
||||
run_silent "humanlayer-ts npm install" npm i -C humanlayer-ts
|
||||
run_silent "humanlayer-ts-vercel-ai-sdk npm install" npm i -C humanlayer-ts-vercel-ai-sdk
|
||||
# Install npm dependencies in parallel
|
||||
(
|
||||
run_silent "hlyr npm install" "npm i -C hlyr" &
|
||||
run_silent "humanlayer-ts npm install" "npm i -C humanlayer-ts" &
|
||||
run_silent "humanlayer-ts-vercel-ai-sdk npm install" "npm i -C humanlayer-ts-vercel-ai-sdk" &
|
||||
wait
|
||||
)
|
||||
|
||||
echo "📦 Installing HLD SDK dependencies..."
|
||||
run_silent "hld-sdk bun install" bun install --cwd=hld/sdk/typescript
|
||||
run_silent "hld-sdk bun install" "bun install --cwd=hld/sdk/typescript"
|
||||
|
||||
echo "🏗️ Building HLD TypeScript SDK..."
|
||||
run_silent "hld-sdk build" sh -c "cd hld/sdk/typescript && bun run build"
|
||||
run_silent "hld-sdk build" "sh -c 'cd hld/sdk/typescript && bun run build'"
|
||||
|
||||
echo "📦 Installing WUI dependencies..."
|
||||
run_silent "humanlayer-wui bun install" bun install --cwd=humanlayer-wui
|
||||
run_silent "humanlayer-wui bun install" "bun install --cwd=humanlayer-wui"
|
||||
|
||||
# Install Python dependencies if uv is available
|
||||
if command -v uv &> /dev/null; then
|
||||
echo "🐍 Setting up Python environment..."
|
||||
run_silent "Installing Python dependencies" "uv sync --all-extras --dev"
|
||||
fi
|
||||
|
||||
echo "🔧 Creating placeholder binaries for Tauri..."
|
||||
mkdir -p humanlayer-wui/src-tauri/bin
|
||||
touch humanlayer-wui/src-tauri/bin/hld
|
||||
touch humanlayer-wui/src-tauri/bin/humanlayer
|
||||
|
||||
echo "🏗️ Building hlyr (requires mocks and npm dependencies)..."
|
||||
run_silent "hlyr build" npm run build -C hlyr
|
||||
run_silent "hlyr build" "npm run build -C hlyr"
|
||||
|
||||
echo "✅ Repository setup complete!"
|
||||
|
||||
3
hld/.gitignore
vendored
3
hld/.gitignore
vendored
@@ -26,3 +26,6 @@ approval/mock_approval.go
|
||||
client/mock_client.go
|
||||
bus/mock_bus.go
|
||||
store/mock_store.go
|
||||
|
||||
# git ignore the built binary for specific platforms.
|
||||
hld-darwin-arm64
|
||||
|
||||
@@ -2,7 +2,11 @@ This is the humanlayer Daemon (HLD) that powers the WUI (humanlayer-wui)
|
||||
|
||||
You cannot run this process, you cannot restart it. If you make changes, you must ask the user to rebuild it.
|
||||
|
||||
The logs are in ~/.humanlayer/logs/daemon-*.log
|
||||
The daemon logs are in ~/.humanlayer/logs/daemon-*.log (timestamped files created by the Makefile when running with `make daemon-dev` or `make daemon-nightly`)
|
||||
|
||||
WUI logs (which include daemon stderr output) are in:
|
||||
- Development: `~/.humanlayer/logs/wui-{branch}/codelayer.log`
|
||||
- Production: Platform-specific log directories
|
||||
|
||||
It uses a database at ~/.humanlayer/*.db - you can access it with sqlite3 to inspect progress and debug things.
|
||||
|
||||
|
||||
@@ -115,12 +115,9 @@ func New() (*Daemon, error) {
|
||||
approvalManager := approval.NewManager(conversationStore, eventBus)
|
||||
slog.Debug("local approval manager created successfully")
|
||||
|
||||
// Create HTTP server if enabled
|
||||
var httpServer *HTTPServer
|
||||
if cfg.HTTPPort > 0 {
|
||||
slog.Info("creating HTTP server", "port", cfg.HTTPPort)
|
||||
httpServer = NewHTTPServer(cfg, sessionManager, approvalManager, conversationStore, eventBus)
|
||||
}
|
||||
// Create HTTP server (always enabled, port 0 means dynamic allocation)
|
||||
slog.Info("creating HTTP server", "port", cfg.HTTPPort)
|
||||
httpServer := NewHTTPServer(cfg, sessionManager, approvalManager, conversationStore, eventBus)
|
||||
|
||||
return &Daemon{
|
||||
config: cfg,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -90,17 +91,35 @@ func (s *HTTPServer) Start(ctx context.Context) error {
|
||||
// Register SSE endpoint directly (not part of strict interface)
|
||||
v1.GET("/stream/events", s.sseHandler.StreamEvents)
|
||||
|
||||
// Create HTTP server
|
||||
// Create listener first to handle port 0
|
||||
addr := fmt.Sprintf("%s:%d", s.config.HTTPHost, s.config.HTTPPort)
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
||||
}
|
||||
|
||||
// Get actual port after binding
|
||||
actualAddr := listener.Addr().(*net.TCPAddr)
|
||||
actualPort := actualAddr.Port
|
||||
|
||||
// If port 0 was used, output actual port to stdout
|
||||
if s.config.HTTPPort == 0 {
|
||||
fmt.Printf("HTTP_PORT=%d\n", actualPort)
|
||||
}
|
||||
|
||||
slog.Info("Starting HTTP server",
|
||||
"configured_port", s.config.HTTPPort,
|
||||
"actual_address", actualAddr.String())
|
||||
|
||||
// Create HTTP server
|
||||
s.server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.router,
|
||||
}
|
||||
|
||||
// Start server in goroutine
|
||||
go func() {
|
||||
slog.Info("Starting HTTP server", "address", addr)
|
||||
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
// Use the existing listener
|
||||
if err := s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP server error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
3
hlyr/.gitignore
vendored
3
hlyr/.gitignore
vendored
@@ -93,3 +93,6 @@ temp/
|
||||
|
||||
# test-mcp config files
|
||||
mcp-config.json
|
||||
|
||||
# git ignore the build version for specific compiled platforms
|
||||
humanlayer-darwin-arm64
|
||||
|
||||
771
hlyr/bun.lock
Normal file
771
hlyr/bun.lock
Normal file
@@ -0,0 +1,771 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "humanlayer",
|
||||
"dependencies": {
|
||||
"@humanlayer/sdk": "^0.7.7",
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"chalk": "^5.4.1",
|
||||
"commander": "^14.0.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"play-sound": "^1.1.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.21",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.1.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
||||
|
||||
"@humanlayer/sdk": ["@humanlayer/sdk@0.7.8", "", {}, "sha512-A2AJqpstF7ISxTj8w3UN9Z60WzukDmxmInR4gq5ZARepF0HHQfEsB5v5ZMVQn5TN+/l3+T2Dwf2TT5f9JChl8Q=="],
|
||||
|
||||
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@pkgr/core": ["@pkgr/core@0.2.4", "", {}, "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.0", "", { "os": "android", "cpu": "arm" }, "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.0", "", { "os": "android", "cpu": "arm64" }, "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.41.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.41.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.41.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.41.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.41.0", "", { "os": "linux", "cpu": "arm" }, "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.41.0", "", { "os": "linux", "cpu": "arm" }, "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.41.0", "", { "os": "linux", "cpu": "none" }, "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w=="],
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.41.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.41.0", "", { "os": "linux", "cpu": "none" }, "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.41.0", "", { "os": "linux", "cpu": "none" }, "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.41.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.41.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.41.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.41.0", "", { "os": "win32", "cpu": "x64" }, "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||
|
||||
"@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@3.1.4", "", { "dependencies": { "@vitest/spy": "3.1.4", "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@3.1.4", "", { "dependencies": { "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0" }, "optionalPeers": ["msw"] }, "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@3.1.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@3.1.4", "", { "dependencies": { "@vitest/utils": "3.1.4", "pathe": "^2.0.3" } }, "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@3.1.4", "", { "dependencies": { "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@3.1.4", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.1.4", "", { "dependencies": { "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" } }, "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg=="],
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"acorn": ["acorn@8.14.1", "", { "bin": "bin/acorn" }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"chai": ["chai@5.2.0", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="],
|
||||
|
||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
|
||||
|
||||
"dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": "bin/esbuild" }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": "bin/eslint.js" }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
|
||||
|
||||
"eslint-config-prettier": ["eslint-config-prettier@10.1.5", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": "bin/cli.js" }, "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw=="],
|
||||
|
||||
"eslint-plugin-prettier": ["eslint-plugin-prettier@5.4.0", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.0" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint"] }, "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
|
||||
|
||||
"expect-type": ["expect-type@1.2.1", "", {}, "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw=="],
|
||||
|
||||
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
|
||||
|
||||
"find-exec": ["find-exec@1.0.3", "", { "dependencies": { "shell-quote": "^1.8.1" } }, "sha512-gnG38zW90mS8hm5smNcrBnakPEt+cGJoiMkJwCU0IYnEb0H2NQk0NIljhNW+48oniCriFek/PH6QXbwsJo/qug=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="],
|
||||
|
||||
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="],
|
||||
|
||||
"loupe": ["loupe@3.1.3", "", {}, "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pathval": ["pathval@2.0.0", "", {}, "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
|
||||
|
||||
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
"play-sound": ["play-sound@1.1.6", "", { "dependencies": { "find-exec": "1.0.3" } }, "sha512-09eO4QiXNFXJffJaOW5P6x6F5RLihpLUkXttvUZeWml0fU6x6Zp7AjG9zaeMpgH2ZNvq4GR1ytB22ddYcqJIZA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||
|
||||
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.5.3", "", { "bin": "bin/prettier.cjs" }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
||||
|
||||
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"rollup": ["rollup@4.41.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.0", "@rollup/rollup-android-arm64": "4.41.0", "@rollup/rollup-darwin-arm64": "4.41.0", "@rollup/rollup-darwin-x64": "4.41.0", "@rollup/rollup-freebsd-arm64": "4.41.0", "@rollup/rollup-freebsd-x64": "4.41.0", "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", "@rollup/rollup-linux-arm-musleabihf": "4.41.0", "@rollup/rollup-linux-arm64-gnu": "4.41.0", "@rollup/rollup-linux-arm64-musl": "4.41.0", "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", "@rollup/rollup-linux-riscv64-gnu": "4.41.0", "@rollup/rollup-linux-riscv64-musl": "4.41.0", "@rollup/rollup-linux-s390x-gnu": "4.41.0", "@rollup/rollup-linux-x64-gnu": "4.41.0", "@rollup/rollup-linux-x64-musl": "4.41.0", "@rollup/rollup-win32-arm64-msvc": "4.41.0", "@rollup/rollup-win32-ia32-msvc": "4.41.0", "@rollup/rollup-win32-x64-msvc": "4.41.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
|
||||
|
||||
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"synckit": ["synckit@0.11.6", "", { "dependencies": { "@pkgr/core": "^0.2.4" } }, "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw=="],
|
||||
|
||||
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
|
||||
|
||||
"tinypool": ["tinypool@1.0.2", "", {}, "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||
|
||||
"tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
|
||||
|
||||
"tree-kill": ["tree-kill@1.2.2", "", { "bin": "cli.js" }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||
|
||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||
|
||||
"tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
|
||||
"vite-node": ["vite-node@3.1.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": "vite-node.mjs" }, "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA=="],
|
||||
|
||||
"vitest": ["vitest@3.1.4", "", { "dependencies": { "@vitest/expect": "3.1.4", "@vitest/mocker": "3.1.4", "@vitest/pretty-format": "^3.1.4", "@vitest/runner": "3.1.4", "@vitest/snapshot": "3.1.4", "@vitest/spy": "3.1.4", "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.13", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.1.4", "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@3.25.30", "", {}, "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"@humanwhocodes/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||
|
||||
"eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"vite/fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
|
||||
|
||||
"vite/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
}
|
||||
}
|
||||
4
humanlayer-wui/.gitignore
vendored
4
humanlayer-wui/.gitignore
vendored
@@ -25,3 +25,7 @@ dist-ssr
|
||||
|
||||
# Generated Tauri config for local development
|
||||
src-tauri/tauri.conf.local.json
|
||||
|
||||
# Bundled binaries (created during build)
|
||||
src-tauri/bin/
|
||||
!src-tauri/bin/.gitkeep
|
||||
|
||||
@@ -2,7 +2,15 @@ This is the HumanLayer Web UI (WUI) - a desktop application for managing AI agen
|
||||
|
||||
The WUI connects to the HumanLayer daemon (hld) to provide a graphical interface for monitoring Claude Code sessions and responding to approval requests. It's built with Tauri for desktop packaging and React for the interface.
|
||||
|
||||
When the WUI is running, logs are written to ~/.humanlayer/logs/wui-TIMESTAMP.log for debugging purposes. The application hot-reloads automatically when you make changes to the code - you cannot manually restart it.
|
||||
When the WUI is running, logs are written to:
|
||||
|
||||
- Development: `~/.humanlayer/logs/wui-{branch-id}/codelayer.log` (e.g., `wui-eng-1784/codelayer.log`)
|
||||
- Production: Platform-specific directories:
|
||||
- macOS: `~/Library/Logs/dev.humanlayer.wui/`
|
||||
- Windows: `%APPDATA%\dev.humanlayer.wui\logs\`
|
||||
- Linux: `~/.config/dev.humanlayer.wui/logs/`
|
||||
|
||||
Logs include output from the WUI backend, daemon stderr (prefixed with [Daemon]), and frontend console logs (prefixed with [Console]). The log files automatically rotate at 50MB. The application hot-reloads automatically when you make changes to the code - you cannot manually restart it.
|
||||
|
||||
The WUI communicates with the daemon via JSON-RPC over a Unix socket at ~/.humanlayer/daemon.sock. All session and approval data comes from the daemon - the WUI is purely a presentation layer.
|
||||
|
||||
|
||||
@@ -4,17 +4,106 @@ Web/desktop UI for the HumanLayer daemon (`hld`) built with Tauri and React.
|
||||
|
||||
## Development
|
||||
|
||||
### Running in Development Mode
|
||||
|
||||
1. Build the daemon (required for auto-launch):
|
||||
|
||||
```bash
|
||||
make daemon-dev-build
|
||||
```
|
||||
|
||||
2. Start CodeLayer in development mode:
|
||||
```bash
|
||||
make codelayer-dev
|
||||
```
|
||||
|
||||
The daemon starts automatically and invisibly when the app launches. No manual daemon management needed.
|
||||
|
||||
### Disabling Auto-Launch (Advanced Users)
|
||||
|
||||
If you prefer to manage the daemon manually:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Start dev server
|
||||
bun run tauri dev
|
||||
|
||||
# Build for production
|
||||
bun run build
|
||||
export HUMANLAYER_WUI_AUTOLAUNCH_DAEMON=false
|
||||
make codelayer-dev
|
||||
```
|
||||
|
||||
### Using an External Daemon
|
||||
|
||||
To connect to a daemon running on a specific port:
|
||||
|
||||
```bash
|
||||
export HUMANLAYER_DAEMON_HTTP_PORT=7777
|
||||
make codelayer-dev
|
||||
```
|
||||
|
||||
### Building for Production
|
||||
|
||||
To build CodeLayer with bundled daemon:
|
||||
|
||||
```bash
|
||||
make codelayer-bundle
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Build the daemon for macOS ARM64
|
||||
2. Build the humanlayer CLI for macOS ARM64
|
||||
3. Copy both to the Tauri resources
|
||||
4. Build CodeLayer with the bundled binaries
|
||||
|
||||
The resulting DMG will include both binaries and automatically manage their lifecycle.
|
||||
|
||||
### Daemon Management
|
||||
|
||||
The daemon lifecycle is completely automatic:
|
||||
|
||||
**In development mode:**
|
||||
|
||||
- Daemon starts invisibly when CodeLayer launches
|
||||
- Each git branch gets its own daemon instance
|
||||
- Database is copied from `daemon-dev.db` to `daemon-{branch}.db`
|
||||
- Socket and port are isolated per branch
|
||||
- Use debug panel (bottom-left settings icon) for manual control if needed
|
||||
|
||||
**In production mode:**
|
||||
|
||||
- Daemon starts invisibly when CodeLayer launches
|
||||
- Uses default paths (`~/.humanlayer/daemon.db`)
|
||||
- Stops automatically when the app exits
|
||||
- No user interaction or awareness required
|
||||
|
||||
**Error Handling:**
|
||||
|
||||
- If daemon fails to start, app continues normally
|
||||
- Connection can be established later via debug panel (dev) or automatically on retry
|
||||
- All errors are logged but never interrupt the user experience
|
||||
|
||||
### MCP Testing
|
||||
|
||||
To test MCP functionality:
|
||||
|
||||
**In development:**
|
||||
|
||||
- Ensure you have `humanlayer` installed globally: `npm install -g humanlayer`
|
||||
- Start CodeLayer: `make codelayer-dev`
|
||||
- Configure Claude Code to use `humanlayer mcp claude_approvals`
|
||||
- The MCP server will connect to your running daemon
|
||||
|
||||
**In production (after Homebrew installation):**
|
||||
|
||||
- Claude Code can directly execute `humanlayer mcp claude_approvals`
|
||||
- No npm or npx required - Homebrew automatically created symlinks in PATH
|
||||
- The MCP server connects to the daemon started by CodeLayer
|
||||
- Verify PATH setup is working: `which humanlayer` should show `/usr/local/bin/humanlayer`
|
||||
|
||||
**Troubleshooting MCP connection:**
|
||||
|
||||
- If MCP can't find `humanlayer`, restart Claude Code after installation
|
||||
- If launched from Dock, Claude Code may have limited PATH - launch from Terminal instead
|
||||
- Check daemon is running: `ps aux | grep hld`
|
||||
- Check MCP logs in Claude Code for connection errors
|
||||
|
||||
## Quick Start for Frontend Development
|
||||
|
||||
Always use React hooks, never the daemon client directly:
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/api": "^2.7.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-log": "^2.6.0",
|
||||
"@tauri-apps/plugin-notification": "^2.3.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -373,7 +374,7 @@
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
||||
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.5.0", "", {}, "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA=="],
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.7.0", "", {}, "sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg=="],
|
||||
|
||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.5.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.5.0", "@tauri-apps/cli-darwin-x64": "2.5.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", "@tauri-apps/cli-linux-arm64-musl": "2.5.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", "@tauri-apps/cli-linux-x64-gnu": "2.5.0", "@tauri-apps/cli-linux-x64-musl": "2.5.0", "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", "@tauri-apps/cli-win32-x64-msvc": "2.5.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg=="],
|
||||
|
||||
@@ -403,6 +404,8 @@
|
||||
|
||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g=="],
|
||||
|
||||
"@tauri-apps/plugin-log": ["@tauri-apps/plugin-log@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-gVp3l31akA1Jk2bZsTA0hMFD5/gLe49Nw1btu5lViau0QqgC2XyT79LSwvy7a44ewtQbSexchqIg7oTJKMIbXQ=="],
|
||||
|
||||
"@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-QDwXo9VzAlH97c0veuf19TZI6cRBPfJDl2O6hNEDvI66j60lOO9z+PL6MJrj8A6Y+t55r7mGhe3rQWLmOre2HA=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-yAbauwp8BCHIhhA48NN8rEf6OtfZBPCgTOCa10gmtoVCpmic5Bq+1Ba7C+NZOjogedkSiV7hAotjYnnbUVmYrw=="],
|
||||
@@ -1323,6 +1326,8 @@
|
||||
|
||||
"@tauri-apps/plugin-notification/@tauri-apps/api": ["@tauri-apps/api@2.6.0", "", {}, "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg=="],
|
||||
|
||||
"@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.5.0", "", {}, "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@humanlayer/hld-sdk": "file:../hld/sdk/typescript",
|
||||
"@radix-ui/react-collapsible": "^1.1.11",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
@@ -26,12 +27,12 @@
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/api": "^2.7.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-log": "^2.6.0",
|
||||
"@tauri-apps/plugin-notification": "^2.3.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@humanlayer/hld-sdk": "file:../hld/sdk/typescript",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
|
||||
864
humanlayer-wui/src-tauri/Cargo.lock
generated
864
humanlayer-wui/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,14 @@ tauri-plugin-opener = "2"
|
||||
tauri-plugin-notification = "2.3.0"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-clipboard-manager = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-store = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
log = "0.4"
|
||||
tauri-plugin-log = "2"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
dirs = "5.0"
|
||||
regex = "1.10"
|
||||
reqwest = { version = "0.11", features = ["blocking"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
]
|
||||
},
|
||||
"clipboard-manager:default",
|
||||
"clipboard-manager:allow-write-text"
|
||||
"clipboard-manager:allow-write-text",
|
||||
"shell:allow-spawn",
|
||||
"shell:allow-execute",
|
||||
"shell:allow-kill",
|
||||
"store:default",
|
||||
"log:default"
|
||||
]
|
||||
}
|
||||
|
||||
491
humanlayer-wui/src-tauri/src/daemon.rs
Normal file
491
humanlayer-wui/src-tauri/src/daemon.rs
Normal file
@@ -0,0 +1,491 @@
|
||||
use crate::get_branch_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DaemonInfo {
|
||||
pub port: u16,
|
||||
pub pid: u32,
|
||||
pub database_path: String,
|
||||
pub socket_path: String,
|
||||
pub branch_id: String,
|
||||
pub is_running: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaemonManager {
|
||||
process: Arc<Mutex<Option<Child>>>,
|
||||
info: Arc<Mutex<Option<DaemonInfo>>>,
|
||||
}
|
||||
|
||||
impl DaemonManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
process: Arc::new(Mutex::new(None)),
|
||||
info: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_daemon(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
is_dev: bool,
|
||||
branch_override: Option<String>,
|
||||
) -> Result<DaemonInfo, String> {
|
||||
// Check if already running
|
||||
{
|
||||
let process = self.process.lock().unwrap();
|
||||
if process.is_some() {
|
||||
if let Some(info) = self.info.lock().unwrap().as_ref() {
|
||||
return Ok(info.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if daemon is already running on a specific port
|
||||
if let Ok(port_str) = env::var("HUMANLAYER_DAEMON_HTTP_PORT") {
|
||||
if let Ok(port) = port_str.parse::<u16>() {
|
||||
// Try to connect to existing daemon
|
||||
if check_daemon_health(port).await.is_ok() {
|
||||
let info = DaemonInfo {
|
||||
port,
|
||||
pid: 0, // Unknown PID for external daemon
|
||||
database_path: "external".to_string(),
|
||||
socket_path: "external".to_string(),
|
||||
branch_id: "external".to_string(),
|
||||
is_running: true,
|
||||
};
|
||||
*self.info.lock().unwrap() = Some(info.clone());
|
||||
return Ok(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine branch identifier using shared function
|
||||
let branch_id = get_branch_id(is_dev, branch_override);
|
||||
|
||||
// Set up paths
|
||||
let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?;
|
||||
let humanlayer_dir = home_dir.join(".humanlayer");
|
||||
fs::create_dir_all(&humanlayer_dir)
|
||||
.map_err(|e| format!("Failed to create .humanlayer directory: {e}"))?;
|
||||
|
||||
// Database path
|
||||
let database_path = if is_dev {
|
||||
// Copy dev database if it doesn't exist
|
||||
let dev_db = humanlayer_dir.join(format!("daemon-{branch_id}.db"));
|
||||
if !dev_db.exists() {
|
||||
let source_db = humanlayer_dir.join("daemon-dev.db");
|
||||
if source_db.exists() {
|
||||
fs::copy(&source_db, &dev_db)
|
||||
.map_err(|e| format!("Failed to copy dev database: {e}"))?;
|
||||
}
|
||||
}
|
||||
dev_db
|
||||
} else {
|
||||
humanlayer_dir.join("daemon.db")
|
||||
};
|
||||
|
||||
// Socket path
|
||||
let socket_path = if is_dev {
|
||||
humanlayer_dir.join(format!("daemon-{branch_id}.sock"))
|
||||
} else {
|
||||
humanlayer_dir.join("daemon.sock")
|
||||
};
|
||||
|
||||
// Get daemon binary path (macOS only)
|
||||
let daemon_path = get_daemon_path(app_handle, is_dev)?;
|
||||
|
||||
// Build environment with port 0 for dynamic allocation
|
||||
let mut env_vars = env::vars().collect::<Vec<_>>();
|
||||
env_vars.push((
|
||||
"HUMANLAYER_DATABASE_PATH".to_string(),
|
||||
database_path.to_str().unwrap().to_string(),
|
||||
));
|
||||
env_vars.push((
|
||||
"HUMANLAYER_DAEMON_SOCKET".to_string(),
|
||||
socket_path.to_str().unwrap().to_string(),
|
||||
));
|
||||
env_vars.push(("HUMANLAYER_DAEMON_HTTP_PORT".to_string(), "0".to_string()));
|
||||
env_vars.push((
|
||||
"HUMANLAYER_DAEMON_HTTP_HOST".to_string(),
|
||||
"localhost".to_string(),
|
||||
));
|
||||
|
||||
if is_dev {
|
||||
env_vars.push((
|
||||
"HUMANLAYER_DAEMON_VERSION_OVERRIDE".to_string(),
|
||||
branch_id.clone(),
|
||||
));
|
||||
// Enable debug logging for daemon in dev mode
|
||||
env_vars.push((
|
||||
"HUMANLAYER_DEBUG".to_string(),
|
||||
"true".to_string(),
|
||||
));
|
||||
env_vars.push((
|
||||
"GIN_MODE".to_string(),
|
||||
"debug".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Start daemon with stdout capture and stderr logging
|
||||
let mut cmd = Command::new(&daemon_path);
|
||||
|
||||
// Log the full command being executed for debugging
|
||||
log::info!("[Tauri] Executing daemon at path: {daemon_path:?}");
|
||||
log::info!("[Tauri] Daemon environment: database_path={}, socket_path={}, port=0, branch_id={branch_id}",
|
||||
database_path.display(),
|
||||
socket_path.display());
|
||||
|
||||
// Always capture stderr for better debugging, even in production
|
||||
cmd.envs(env_vars)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start daemon: {e}"))?;
|
||||
|
||||
// Get the PID before we do anything else
|
||||
let pid = child.id();
|
||||
log::info!("[Tauri] Daemon spawned with PID: {pid}");
|
||||
|
||||
// Always spawn a task to read stderr for debugging
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let branch_id_clone = branch_id.clone();
|
||||
let is_prod = !is_dev;
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
for line in reader.lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
// In production, always log at info level or higher for visibility
|
||||
if is_prod {
|
||||
// Check if it's an error or warning
|
||||
if line.contains("ERROR") || line.contains("error") || line.contains("Error") {
|
||||
log::error!("[Daemon] {branch_id_clone}: {line}");
|
||||
} else if line.contains("WARN") || line.contains("warn") || line.contains("Warning") {
|
||||
log::warn!("[Daemon] {branch_id_clone}: {line}");
|
||||
} else {
|
||||
log::info!("[Daemon] {branch_id_clone}: {line}");
|
||||
}
|
||||
} else {
|
||||
// Dev mode: parse log levels as before
|
||||
let level = extract_log_level(&line);
|
||||
let cleaned_line = remove_timestamp(&line);
|
||||
|
||||
match level {
|
||||
LogLevel::Error => log::error!("[Daemon] {branch_id_clone}: {cleaned_line}"),
|
||||
LogLevel::Warn => log::warn!("[Daemon] {branch_id_clone}: {cleaned_line}"),
|
||||
LogLevel::Info => log::info!("[Daemon] {branch_id_clone}: {cleaned_line}"),
|
||||
LogLevel::Debug => log::debug!("[Daemon] {branch_id_clone}: {cleaned_line}"),
|
||||
LogLevel::Trace => log::trace!("[Daemon] {branch_id_clone}: {cleaned_line}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[Tauri] Error reading daemon stderr: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("[Tauri] Daemon stderr reader finished for {branch_id_clone}");
|
||||
});
|
||||
}
|
||||
|
||||
// Parse stdout to get the actual port
|
||||
let stdout = child
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or("Failed to capture daemon stdout")?;
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
let mut actual_port = None;
|
||||
let mut first_line = String::new();
|
||||
|
||||
// Read the first line synchronously to get the port
|
||||
log::info!("[Tauri] Waiting for daemon to report port on stdout...");
|
||||
match reader.read_line(&mut first_line) {
|
||||
Ok(0) => {
|
||||
log::error!("[Tauri] Daemon stdout closed immediately (0 bytes read)");
|
||||
return Err("Daemon stdout closed before reporting port".to_string());
|
||||
}
|
||||
Ok(n) => {
|
||||
log::info!("[Tauri] Read {n} bytes from daemon stdout: {first_line:?}");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[Tauri] Failed to read daemon stdout: {e}");
|
||||
return Err(format!("Failed to read daemon stdout: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
if first_line.starts_with("HTTP_PORT=") {
|
||||
let port_str = first_line.trim().replace("HTTP_PORT=", "");
|
||||
log::info!("[Tauri] Attempting to parse port from: {port_str:?}");
|
||||
match port_str.parse::<u16>() {
|
||||
Ok(p) => {
|
||||
actual_port = Some(p);
|
||||
log::info!("[Tauri] Successfully parsed port: {p}");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[Tauri] Failed to parse port from {port_str:?}: {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("[Tauri] First line from daemon stdout doesn't start with HTTP_PORT=: {first_line:?}");
|
||||
}
|
||||
|
||||
let port = actual_port.ok_or_else(|| {
|
||||
format!("Daemon failed to report port. First line was: {first_line:?}")
|
||||
})?;
|
||||
log::info!("[Tauri] Got port {port} from daemon stdout");
|
||||
|
||||
// Now spawn a task to keep reading stdout to prevent SIGPIPE
|
||||
let branch_id_for_stdout = branch_id.clone();
|
||||
tokio::spawn(async move {
|
||||
// Continue reading the rest of stdout
|
||||
for line in reader.lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
log::trace!("[Tauri] Daemon stdout {branch_id_for_stdout}: {line}");
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("[Tauri] Daemon stdout closed for {branch_id_for_stdout}: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("[Tauri] Stdout reader task finished for {branch_id_for_stdout}");
|
||||
});
|
||||
|
||||
// Check if process is still alive after reading port
|
||||
match child.try_wait() {
|
||||
Ok(None) => log::info!("[Tauri] Daemon process still running after port read"),
|
||||
Ok(Some(status)) => {
|
||||
return Err(format!("Daemon process exited immediately after starting! Status: {status:?}"));
|
||||
}
|
||||
Err(e) => log::error!("[Tauri] Error checking daemon status: {e}"),
|
||||
}
|
||||
|
||||
let daemon_info = DaemonInfo {
|
||||
port,
|
||||
pid,
|
||||
database_path: database_path.to_str().unwrap().to_string(),
|
||||
socket_path: socket_path.to_str().unwrap().to_string(),
|
||||
branch_id: branch_id.clone(),
|
||||
is_running: true,
|
||||
};
|
||||
|
||||
// Store the process and info before awaiting
|
||||
{
|
||||
let mut process = self.process.lock().unwrap();
|
||||
*process = Some(child);
|
||||
}
|
||||
*self.info.lock().unwrap() = Some(daemon_info.clone());
|
||||
|
||||
// Wait for daemon to be ready
|
||||
log::info!("[Tauri] Waiting for daemon to be ready on port {port}");
|
||||
wait_for_daemon(port).await?;
|
||||
log::info!("[Tauri] Daemon is ready and responding to health checks");
|
||||
|
||||
// Spawn a task to monitor the daemon process
|
||||
let process_arc = self.process.clone();
|
||||
let branch_id_clone = branch_id.clone();
|
||||
let port_for_monitor = port;
|
||||
tokio::spawn(async move {
|
||||
let mut last_check = std::time::Instant::now();
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
|
||||
let mut process_guard = process_arc.lock().unwrap();
|
||||
if let Some(child) = process_guard.as_mut() {
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
// Process has exited
|
||||
log::error!(
|
||||
"[Tauri] Daemon process exited unexpectedly! Branch: {branch_id_clone}, Port: {port_for_monitor}, Exit status: {status:?}, Time since last check: {:?}",
|
||||
last_check.elapsed()
|
||||
);
|
||||
// Clear the process
|
||||
*process_guard = None;
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
// Still running
|
||||
last_check = std::time::Instant::now();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[Tauri] Error checking daemon process status: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No process to monitor
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(daemon_info)
|
||||
}
|
||||
|
||||
pub fn stop_daemon(&self) -> Result<(), String> {
|
||||
let mut process = self.process.lock().unwrap();
|
||||
|
||||
if let Some(mut child) = process.take() {
|
||||
child
|
||||
.kill()
|
||||
.map_err(|e| format!("Failed to stop daemon: {e}"))?;
|
||||
|
||||
// Wait for process to exit
|
||||
let _ = child.wait();
|
||||
|
||||
// Update store to mark daemon as not running
|
||||
if let Some(info) = self.info.lock().unwrap().as_mut() {
|
||||
info.is_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_info(&self) -> Option<DaemonInfo> {
|
||||
self.info.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
let mut process = self.process.lock().unwrap();
|
||||
if let Some(child) = process.as_mut() {
|
||||
// Check if process is still alive
|
||||
match child.try_wait() {
|
||||
Ok(None) => true, // Still running
|
||||
_ => false, // Exited or error
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_daemon_path(app_handle: &AppHandle, is_dev: bool) -> Result<PathBuf, String> {
|
||||
if is_dev {
|
||||
// In dev mode, look for hld-dev in the project
|
||||
let current =
|
||||
env::current_dir().map_err(|e| format!("Failed to get current directory: {e}"))?;
|
||||
|
||||
// Handle both running from src-tauri and from humanlayer-wui
|
||||
let dev_path = if current.ends_with("src-tauri") {
|
||||
current
|
||||
.parent() // humanlayer-wui
|
||||
.and_then(|p| p.parent()) // humanlayer root
|
||||
.ok_or("Failed to get parent directory")?
|
||||
.join("hld")
|
||||
.join("hld-dev")
|
||||
} else {
|
||||
current
|
||||
.parent() // Go up from humanlayer-wui to humanlayer
|
||||
.ok_or("Failed to get parent directory")?
|
||||
.join("hld")
|
||||
.join("hld-dev")
|
||||
};
|
||||
|
||||
if dev_path.exists() {
|
||||
Ok(dev_path)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Development daemon not found at {dev_path:?}. Run 'make daemon-dev-build' first."
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// In production, use bundled binary
|
||||
let resource_dir = app_handle
|
||||
.path()
|
||||
.resource_dir()
|
||||
.map_err(|e| format!("Failed to get resource directory: {e}"))?;
|
||||
|
||||
Ok(resource_dir.join("bin").join("hld"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn check_daemon_health(port: u16) -> Result<(), String> {
|
||||
let client = reqwest::Client::new();
|
||||
match client
|
||||
.get(format!("http://localhost:{port}/api/v1/health"))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) if response.status().is_success() => Ok(()),
|
||||
Ok(_) => Err("Daemon health check failed".to_string()),
|
||||
Err(e) => Err(format!("Failed to connect to daemon: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_daemon(port: u16) -> Result<(), String> {
|
||||
let start = std::time::Instant::now();
|
||||
let timeout = std::time::Duration::from_secs(10);
|
||||
|
||||
while start.elapsed() < timeout {
|
||||
if check_daemon_health(port).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Err("Daemon failed to start within 10 seconds".to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
fn extract_log_level(line: &str) -> LogLevel {
|
||||
// slog format includes level like: "2024-01-30T10:15:30Z INFO message"
|
||||
// or sometimes: "INFO[0001] message" for other loggers
|
||||
|
||||
if line.contains(" ERROR ") || line.contains("ERROR[") {
|
||||
LogLevel::Error
|
||||
} else if line.contains(" WARN ") || line.contains("WARN[") {
|
||||
LogLevel::Warn
|
||||
} else if line.contains(" INFO ") || line.contains("INFO[") {
|
||||
LogLevel::Info
|
||||
} else if line.contains(" DEBUG ") || line.contains("DEBUG[") {
|
||||
LogLevel::Debug
|
||||
} else if line.contains(" TRACE ") || line.contains("TRACE[") {
|
||||
LogLevel::Trace
|
||||
} else {
|
||||
// Default to info for unparseable lines
|
||||
LogLevel::Info
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_timestamp(line: &str) -> &str {
|
||||
// Remove slog timestamp to avoid double timestamps
|
||||
// Format: "2024-01-30T10:15:30Z LEVEL message"
|
||||
if let Some(idx) = line.find(" INFO ") {
|
||||
&line[idx + 6..]
|
||||
} else if let Some(idx) = line.find(" ERROR ") {
|
||||
&line[idx + 7..]
|
||||
} else if let Some(idx) = line.find(" WARN ") {
|
||||
&line[idx + 6..]
|
||||
} else if let Some(idx) = line.find(" DEBUG ") {
|
||||
&line[idx + 7..]
|
||||
} else if let Some(idx) = line.find(" TRACE ") {
|
||||
&line[idx + 7..]
|
||||
} else {
|
||||
// Return original if no timestamp found
|
||||
line
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,305 @@
|
||||
mod daemon;
|
||||
|
||||
use daemon::{DaemonInfo, DaemonManager};
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tauri::{Manager, State};
|
||||
use tauri_plugin_store::StoreExt;
|
||||
|
||||
// Branch detection utilities
|
||||
pub fn get_git_branch() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(["branch", "--show-current"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| {
|
||||
if output.status.success() {
|
||||
String::from_utf8(output.stdout)
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extract_ticket_id(branch: &str) -> Option<String> {
|
||||
// Extract patterns like "eng-1234" from branch names
|
||||
let re = regex::Regex::new(r"(eng-\d+)").ok()?;
|
||||
re.captures(branch)
|
||||
.and_then(|cap| cap.get(1))
|
||||
.map(|m| m.as_str().to_string())
|
||||
}
|
||||
|
||||
pub fn get_branch_id(is_dev: bool, branch_override: Option<String>) -> String {
|
||||
if is_dev {
|
||||
let branch = branch_override
|
||||
.or_else(get_git_branch)
|
||||
.unwrap_or_else(|| "dev".to_string());
|
||||
extract_ticket_id(&branch).unwrap_or(branch)
|
||||
} else {
|
||||
"production".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get store path based on dev mode and branch
|
||||
fn get_store_path(is_dev: bool, branch_id: Option<&str>) -> PathBuf {
|
||||
let home = dirs::home_dir().expect("Failed to get home directory");
|
||||
let humanlayer_dir = home.join(".humanlayer");
|
||||
|
||||
if is_dev {
|
||||
// Use branch-specific store in dev mode
|
||||
if let Some(branch) = branch_id {
|
||||
humanlayer_dir.join(format!("codelayer-{branch}.json"))
|
||||
} else {
|
||||
humanlayer_dir.join("codelayer-dev.json")
|
||||
}
|
||||
} else {
|
||||
humanlayer_dir.join("codelayer.json")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn start_daemon(
|
||||
app_handle: tauri::AppHandle,
|
||||
daemon_manager: State<'_, DaemonManager>,
|
||||
is_dev: bool,
|
||||
branch_override: Option<String>,
|
||||
) -> Result<DaemonInfo, String> {
|
||||
let info = daemon_manager
|
||||
.start_daemon(&app_handle, is_dev, branch_override)
|
||||
.await?;
|
||||
|
||||
// Save to store using branch-specific path in dev mode
|
||||
let store_path = get_store_path(is_dev, Some(&info.branch_id));
|
||||
let store = app_handle
|
||||
.store(&store_path)
|
||||
.map_err(|e| format!("Failed to access store: {e}"))?;
|
||||
|
||||
store.set("current_daemon", serde_json::to_value(&info).unwrap());
|
||||
store
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save store: {e}"))?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn stop_daemon(
|
||||
app_handle: tauri::AppHandle,
|
||||
daemon_manager: State<'_, DaemonManager>,
|
||||
) -> Result<(), String> {
|
||||
// Get daemon info before stopping to know which store to update
|
||||
if let Some(info) = daemon_manager.get_info() {
|
||||
let is_dev = info.branch_id != "production";
|
||||
let store_path = get_store_path(is_dev, Some(&info.branch_id));
|
||||
|
||||
// Stop the daemon
|
||||
daemon_manager.stop_daemon()?;
|
||||
|
||||
// Update store to mark as not running
|
||||
let store = app_handle
|
||||
.store(&store_path)
|
||||
.map_err(|e| format!("Failed to access store: {e}"))?;
|
||||
|
||||
// Get the stored daemon info and update is_running
|
||||
if let Some(mut stored_info) = store
|
||||
.get("current_daemon")
|
||||
.and_then(|v| serde_json::from_value::<DaemonInfo>(v).ok())
|
||||
{
|
||||
stored_info.is_running = false;
|
||||
store.set(
|
||||
"current_daemon",
|
||||
serde_json::to_value(&stored_info).unwrap(),
|
||||
);
|
||||
store
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save store: {e}"))?;
|
||||
}
|
||||
} else {
|
||||
daemon_manager.stop_daemon()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_daemon_info(
|
||||
app_handle: tauri::AppHandle,
|
||||
daemon_manager: State<'_, DaemonManager>,
|
||||
is_dev: bool,
|
||||
) -> Result<Option<DaemonInfo>, String> {
|
||||
// First check if daemon manager has info
|
||||
if let Some(info) = daemon_manager.get_info() {
|
||||
return Ok(Some(info));
|
||||
}
|
||||
|
||||
// Otherwise check store for last known daemon
|
||||
let store_path = get_store_path(is_dev, None);
|
||||
let store = app_handle
|
||||
.store(&store_path)
|
||||
.map_err(|e| format!("Failed to access store: {e}"))?;
|
||||
|
||||
let stored_info = store
|
||||
.get("current_daemon")
|
||||
.and_then(|v| serde_json::from_value::<DaemonInfo>(v).ok());
|
||||
|
||||
Ok(stored_info)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn is_daemon_running(daemon_manager: State<'_, DaemonManager>) -> Result<bool, String> {
|
||||
Ok(daemon_manager.is_running())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_log_directory() -> Result<String, String> {
|
||||
let is_dev = cfg!(debug_assertions);
|
||||
|
||||
if is_dev {
|
||||
// Dev mode: return branch-based folder
|
||||
let branch_id = get_branch_id(is_dev, None);
|
||||
let home = dirs::home_dir().ok_or("Failed to get home directory")?;
|
||||
let log_dir = home.join(".humanlayer").join("logs").join(format!("wui-{branch_id}"));
|
||||
Ok(log_dir.to_string_lossy().to_string())
|
||||
} else {
|
||||
// Production: use tauri API to get platform-specific log directory
|
||||
// This will be handled by the frontend using appLogDir()
|
||||
Err("Use appLogDir() for production".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin({
|
||||
let is_dev = cfg!(debug_assertions);
|
||||
let branch_id = get_branch_id(is_dev, None);
|
||||
|
||||
// Determine log directory based on dev/prod mode
|
||||
let log_targets = if is_dev {
|
||||
// Dev mode: use branch-based folder in ~/.humanlayer/logs/
|
||||
let home = dirs::home_dir().expect("Failed to get home directory");
|
||||
let log_dir = home.join(".humanlayer").join("logs").join(format!("wui-{branch_id}"));
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
std::fs::create_dir_all(&log_dir).ok();
|
||||
|
||||
// Store the log directory path in app state for frontend access
|
||||
println!("[WUI] Logs will be written to: {}", log_dir.display());
|
||||
|
||||
vec![
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Folder {
|
||||
path: log_dir,
|
||||
file_name: Some("codelayer.log".into()),
|
||||
}),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Webview),
|
||||
]
|
||||
} else {
|
||||
// Production: use default platform-specific log directory
|
||||
vec![
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: None,
|
||||
}),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Webview),
|
||||
]
|
||||
};
|
||||
|
||||
tauri_plugin_log::Builder::new()
|
||||
.targets(log_targets)
|
||||
.level(if is_dev {
|
||||
log::LevelFilter::Debug
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
})
|
||||
.max_file_size(50_000_000) // 50MB before rotation
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepAll)
|
||||
.build()
|
||||
})
|
||||
.setup(|app| {
|
||||
let daemon_manager = DaemonManager::new();
|
||||
app.manage(daemon_manager.clone());
|
||||
|
||||
|
||||
// Check if auto-launch is disabled
|
||||
let should_autolaunch = env::var("HUMANLAYER_WUI_AUTOLAUNCH_DAEMON")
|
||||
.map(|v| v.to_lowercase() != "false")
|
||||
.unwrap_or(true);
|
||||
|
||||
if should_autolaunch {
|
||||
// Start daemon automatically
|
||||
let app_handle_clone = app.app_handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let is_dev = cfg!(debug_assertions);
|
||||
|
||||
// Small delay to ensure UI is ready
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
// Try to start daemon, but don't show errors in UI
|
||||
match daemon_manager
|
||||
.start_daemon(&app_handle_clone, is_dev, None)
|
||||
.await
|
||||
{
|
||||
Ok(info) => {
|
||||
log::info!("[Tauri] Daemon started automatically on port {}", info.port);
|
||||
}
|
||||
Err(e) => {
|
||||
// Log error but don't interrupt user experience
|
||||
log::error!("[Tauri] Failed to auto-start daemon: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log::info!("[Tauri] Daemon auto-launch disabled by environment variable");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||
let daemon_manager = window.state::<DaemonManager>();
|
||||
|
||||
// Always stop daemon when window closes
|
||||
if let Some(info) = daemon_manager.get_info() {
|
||||
let is_dev = info.branch_id != "production";
|
||||
let store_path = get_store_path(is_dev, Some(&info.branch_id));
|
||||
|
||||
if let Ok(store) = window.app_handle().store(&store_path) {
|
||||
// Update store to mark daemon as not running
|
||||
if let Some(mut stored_info) = store
|
||||
.get("current_daemon")
|
||||
.and_then(|v| serde_json::from_value::<DaemonInfo>(v).ok())
|
||||
{
|
||||
stored_info.is_running = false;
|
||||
store.set(
|
||||
"current_daemon",
|
||||
serde_json::to_value(&stored_info).unwrap(),
|
||||
);
|
||||
let _ = store.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the daemon
|
||||
let _ = daemon_manager.stop_daemon();
|
||||
}
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
start_daemon,
|
||||
stop_daemon,
|
||||
get_daemon_info,
|
||||
is_daemon_running,
|
||||
get_log_directory,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
],
|
||||
"resources": ["bin/hld", "bin/humanlayer"]
|
||||
},
|
||||
"plugins": {
|
||||
"fs": {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Session, SessionStatus } from '@/lib/daemon/types'
|
||||
import { ViewMode } from '@/lib/daemon/types'
|
||||
import { create } from 'zustand'
|
||||
import { daemonClient } from '@/lib/daemon'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface StoreState {
|
||||
/* Sessions */
|
||||
@@ -111,7 +112,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
})
|
||||
set({ sessions: response.sessions })
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh sessions:', error)
|
||||
logger.error('Failed to refresh sessions:', error)
|
||||
}
|
||||
},
|
||||
refreshActiveSessionConversation: async (sessionId: string) => {
|
||||
@@ -124,7 +125,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const conversationResponse = await daemonClient.getConversation({ session_id: sessionId })
|
||||
updateActiveSessionConversation(conversationResponse)
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh active session conversation:', error)
|
||||
logger.error('Failed to refresh active session conversation:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -164,7 +165,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
await daemonClient.interruptSession(sessionId)
|
||||
// The session status will be updated via the subscription
|
||||
} catch (error) {
|
||||
console.error('Failed to interrupt session:', error)
|
||||
logger.error('Failed to interrupt session:', error)
|
||||
}
|
||||
},
|
||||
archiveSession: async (sessionId: string, archived: boolean) => {
|
||||
@@ -175,7 +176,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
// Refresh sessions to update the list based on current view mode
|
||||
await get().refreshSessions()
|
||||
} catch (error) {
|
||||
console.error('Failed to archive session:', error)
|
||||
logger.error('Failed to archive session:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
@@ -183,14 +184,14 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
try {
|
||||
const response = await daemonClient.bulkArchiveSessions({ session_ids: sessionIds, archived })
|
||||
if (!response.success) {
|
||||
console.error('Failed to archive sessions')
|
||||
logger.error('Failed to archive sessions')
|
||||
}
|
||||
// Refresh sessions to update the list
|
||||
await get().refreshSessions()
|
||||
// Clear selection after bulk operation
|
||||
get().clearSelection()
|
||||
} catch (error) {
|
||||
console.error('Failed to bulk archive sessions:', error)
|
||||
logger.error('Failed to bulk archive sessions:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
@@ -216,7 +217,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const anchorIndex = sessions.findIndex(s => s.id === anchorId)
|
||||
const targetIndex = sessions.findIndex(s => s.id === targetId)
|
||||
|
||||
console.log('[Store] selectRange (REPLACE):', {
|
||||
logger.log('[Store] selectRange (REPLACE):', {
|
||||
anchorId,
|
||||
targetId,
|
||||
anchorIndex,
|
||||
@@ -238,7 +239,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
newSelection.add(sessions[i].id)
|
||||
}
|
||||
|
||||
console.log('[Store] selectRange result:', {
|
||||
logger.log('[Store] selectRange result:', {
|
||||
newSelectionSize: newSelection.size,
|
||||
newSelectionIds: Array.from(newSelection),
|
||||
})
|
||||
@@ -251,7 +252,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const anchorIndex = sessions.findIndex(s => s.id === anchorId)
|
||||
const targetIndex = sessions.findIndex(s => s.id === targetId)
|
||||
|
||||
console.log('[Store] addRangeToSelection (ADD):', {
|
||||
logger.log('[Store] addRangeToSelection (ADD):', {
|
||||
anchorId,
|
||||
targetId,
|
||||
anchorIndex,
|
||||
@@ -276,7 +277,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
newSelection.add(sessions[i].id)
|
||||
}
|
||||
|
||||
console.log('[Store] addRangeToSelection result:', {
|
||||
logger.log('[Store] addRangeToSelection result:', {
|
||||
newSelectionSize: newSelection.size,
|
||||
newSelectionIds: Array.from(newSelection),
|
||||
})
|
||||
@@ -289,7 +290,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const anchorIndex = sessions.findIndex(s => s.id === anchorId)
|
||||
const targetIndex = sessions.findIndex(s => s.id === targetId)
|
||||
|
||||
console.log('[Store] updateCurrentRange:', {
|
||||
logger.log('[Store] updateCurrentRange:', {
|
||||
anchorId,
|
||||
targetId,
|
||||
anchorIndex,
|
||||
@@ -344,7 +345,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
newSelection.add(sessions[i].id)
|
||||
}
|
||||
|
||||
console.log('[Store] updateCurrentRange result:', {
|
||||
logger.log('[Store] updateCurrentRange result:', {
|
||||
newSelectionSize: newSelection.size,
|
||||
newSelectionIds: Array.from(newSelection),
|
||||
currentRange: `${rangeStart}-${rangeEnd}`,
|
||||
@@ -371,7 +372,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
// Check if we're starting within an existing selection
|
||||
const isStartingInSelection = selectedSessions.has(sessionId)
|
||||
|
||||
console.log(
|
||||
logger.log(
|
||||
`[bulkSelect] sessionId: ${sessionId}, direction: ${direction}, isStartingInSelection: ${isStartingInSelection}`,
|
||||
)
|
||||
|
||||
@@ -418,7 +419,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
|
||||
const anchorId = sessions[anchorIndex].id
|
||||
|
||||
console.log(
|
||||
logger.log(
|
||||
`[bulkSelect] Starting in selection, found range: ${rangeStart}-${rangeEnd}, anchor at ${anchorIndex}`,
|
||||
)
|
||||
|
||||
@@ -426,11 +427,11 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
state.updateCurrentRange(anchorId, targetSession.id)
|
||||
} else if (selectedSessions.size > 0 && !isStartingInSelection) {
|
||||
// We have selections but starting fresh - add to existing
|
||||
console.log('[bulkSelect] Adding new range to existing selections')
|
||||
logger.log('[bulkSelect] Adding new range to existing selections')
|
||||
state.addRangeToSelection(sessionId, targetSession.id)
|
||||
} else {
|
||||
// No selections or replacing - create new range
|
||||
console.log('[bulkSelect] Creating new selection range')
|
||||
logger.log('[bulkSelect] Creating new selection range')
|
||||
state.selectRange(sessionId, targetSession.id)
|
||||
}
|
||||
|
||||
@@ -472,7 +473,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const newMap = new Map(state.recentNavigations)
|
||||
const timestamp = Date.now()
|
||||
newMap.set(sessionId, timestamp)
|
||||
console.log(
|
||||
logger.log(
|
||||
`Tracking navigation from session ${sessionId} at ${new Date(timestamp).toISOString()}`,
|
||||
)
|
||||
|
||||
@@ -483,7 +484,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const updatedMap = new Map(currentMap)
|
||||
updatedMap.delete(sessionId)
|
||||
set({ recentNavigations: updatedMap })
|
||||
console.log(`Removed navigation tracking for session ${sessionId} after timeout`)
|
||||
logger.log(`Removed navigation tracking for session ${sessionId} after timeout`)
|
||||
}
|
||||
}, 5000)
|
||||
return { recentNavigations: newMap }
|
||||
@@ -493,13 +494,13 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
const now = Date.now()
|
||||
|
||||
if (!timestamp) {
|
||||
console.log(`No navigation tracking found for session ${sessionId}`)
|
||||
logger.log(`No navigation tracking found for session ${sessionId}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const elapsed = now - timestamp
|
||||
const wasRecent = elapsed < withinMs
|
||||
console.log(
|
||||
logger.log(
|
||||
`Checking navigation for session ${sessionId}: elapsed ${elapsed}ms, within ${withinMs}ms window: ${wasRecent}`,
|
||||
)
|
||||
return wasRecent
|
||||
@@ -554,7 +555,7 @@ export const useStore = create<StoreState>((set, get) => ({
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch session detail'
|
||||
console.error('Failed to fetch session detail:', error)
|
||||
logger.error('Failed to fetch session detail:', error)
|
||||
|
||||
set({
|
||||
activeSessionDetail: {
|
||||
|
||||
224
humanlayer-wui/src/components/DebugPanel.tsx
Normal file
224
humanlayer-wui/src/components/DebugPanel.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { daemonService, type DaemonInfo } from '@/services/daemon-service'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Loader2, RefreshCw, Link, Server } from 'lucide-react'
|
||||
import { useDaemonConnection } from '@/hooks/useDaemonConnection'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface DebugPanelProps {
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function DebugPanel({ open, onOpenChange }: DebugPanelProps) {
|
||||
const { connected, reconnect } = useDaemonConnection()
|
||||
const [isRestarting, setIsRestarting] = useState(false)
|
||||
const [restartError, setRestartError] = useState<string | null>(null)
|
||||
const [customUrl, setCustomUrl] = useState('')
|
||||
const [connectError, setConnectError] = useState<string | null>(null)
|
||||
const [daemonType, setDaemonType] = useState<'managed' | 'external'>('managed')
|
||||
const [externalDaemonUrl, setExternalDaemonUrl] = useState<string | null>(null)
|
||||
const [daemonInfo, setDaemonInfo] = useState<DaemonInfo | null>(null)
|
||||
|
||||
// Only show in dev mode
|
||||
if (!import.meta.env.DEV) {
|
||||
return null
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadDaemonInfo()
|
||||
}, [connected])
|
||||
|
||||
async function loadDaemonInfo() {
|
||||
try {
|
||||
const info = await daemonService.getDaemonInfo()
|
||||
setDaemonInfo(info)
|
||||
const type = daemonService.getDaemonType()
|
||||
setDaemonType(type)
|
||||
if (type === 'external') {
|
||||
setExternalDaemonUrl((window as any).__HUMANLAYER_DAEMON_URL || null)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to load daemon info:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRestartDaemon() {
|
||||
setIsRestarting(true)
|
||||
setRestartError(null)
|
||||
|
||||
try {
|
||||
// Stop current daemon
|
||||
await daemonService.stopDaemon()
|
||||
|
||||
// Wait a moment
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// Start new daemon (with rebuild in dev)
|
||||
await daemonService.startDaemon(true)
|
||||
|
||||
// Wait for it to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// Reconnect
|
||||
await reconnect()
|
||||
await loadDaemonInfo()
|
||||
|
||||
setIsRestarting(false)
|
||||
} catch (error: any) {
|
||||
setRestartError(error.message || 'Failed to restart daemon')
|
||||
setIsRestarting(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConnectToCustom() {
|
||||
setConnectError(null)
|
||||
|
||||
try {
|
||||
await daemonService.connectToExisting(customUrl)
|
||||
await reconnect()
|
||||
await loadDaemonInfo()
|
||||
setCustomUrl('')
|
||||
} catch (error: any) {
|
||||
setConnectError(error.message || 'Failed to connect')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSwitchToManaged() {
|
||||
try {
|
||||
await daemonService.switchToManagedDaemon()
|
||||
await reconnect()
|
||||
await loadDaemonInfo()
|
||||
} catch (error: any) {
|
||||
setConnectError(error.message || 'Failed to switch to managed daemon')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Debug Panel</DialogTitle>
|
||||
<DialogDescription>Advanced daemon management for developers</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm">Daemon Status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Connection</span>
|
||||
<span
|
||||
className={`text-sm font-medium ${connected ? 'text-[var(--terminal-success)]' : 'text-[var(--terminal-error)]'}`}
|
||||
>
|
||||
{connected ? 'Connected' : 'Disconnected'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Daemon Type</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Server className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-sm font-medium capitalize">{daemonType}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{daemonType === 'external' && externalDaemonUrl && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">External URL</span>
|
||||
<span className="text-sm font-mono">{externalDaemonUrl}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{daemonInfo && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Port</span>
|
||||
<span className="text-sm font-mono">{daemonInfo.port}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Branch</span>
|
||||
<span className="text-sm font-mono">{daemonInfo.branch_id}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{daemonType === 'managed' ? (
|
||||
<Button
|
||||
onClick={handleRestartDaemon}
|
||||
disabled={isRestarting}
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
>
|
||||
{isRestarting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Restarting Daemon...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Restart Daemon
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleSwitchToManaged} className="w-full" variant="outline">
|
||||
Switch to Managed Daemon
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{restartError && <p className="text-sm text-destructive">{restartError}</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm">Connect to Existing Daemon</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Connect to a daemon running on a custom URL
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="url" className="text-sm">
|
||||
Daemon URL
|
||||
</Label>
|
||||
<Input
|
||||
id="url"
|
||||
type="text"
|
||||
placeholder="http://127.0.0.1:7777"
|
||||
value={customUrl}
|
||||
onChange={e => setCustomUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleConnectToCustom}
|
||||
disabled={!customUrl}
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
>
|
||||
<Link className="mr-2 h-4 w-4" />
|
||||
Connect
|
||||
</Button>
|
||||
|
||||
{connectError && <p className="text-sm text-destructive">{connectError}</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -15,14 +15,18 @@ import { Toaster } from 'sonner'
|
||||
import { notificationService } from '@/services/NotificationService'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { MessageCircle } from 'lucide-react'
|
||||
import { MessageCircle, Bug } from 'lucide-react'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { DebugPanel } from '@/components/DebugPanel'
|
||||
import { notifyLogLocation } from '@/lib/log-notification'
|
||||
import '@/App.css'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
export function Layout() {
|
||||
const [approvals, setApprovals] = useState<any[]>([])
|
||||
const [activeSessionId] = useState<string | null>(null)
|
||||
const { setTheme } = useTheme()
|
||||
const [isDebugPanelOpen, setIsDebugPanelOpen] = useState(false)
|
||||
|
||||
// Use the daemon connection hook for all connection management
|
||||
const { connected, connecting, version, connect } = useDaemonConnection()
|
||||
@@ -46,18 +50,18 @@ export function Layout() {
|
||||
// Set up single SSE subscription for all events
|
||||
useSessionSubscriptions(connected, {
|
||||
onSessionStatusChanged: async data => {
|
||||
console.log('useSessionSubscriptions.onSessionStatusChanged', data)
|
||||
logger.log('useSessionSubscriptions.onSessionStatusChanged', data)
|
||||
const { session_id, new_status } = data
|
||||
updateSessionStatus(session_id, new_status as SessionStatus)
|
||||
await refreshActiveSessionConversation(session_id)
|
||||
},
|
||||
onNewApproval: async data => {
|
||||
console.log('useSessionSubscriptions.onNewApproval', data)
|
||||
logger.log('useSessionSubscriptions.onNewApproval', data)
|
||||
updateSessionStatus(data.session_id, SessionStatus.WaitingInput)
|
||||
await refreshActiveSessionConversation(data.session_id)
|
||||
},
|
||||
onApprovalResolved: async data => {
|
||||
console.log('useSessionSubscriptions.onApprovalResolved', data)
|
||||
logger.log('useSessionSubscriptions.onApprovalResolved', data)
|
||||
updateSessionStatus(data.session_id, SessionStatus.Running)
|
||||
await refreshActiveSessionConversation(data.session_id)
|
||||
},
|
||||
@@ -82,7 +86,7 @@ export function Layout() {
|
||||
'https://github.com/humanlayer/humanlayer/issues/new?title=Feedback%20on%20CodeLayer&body=%23%23%23%20Problem%20to%20solve%20%2F%20Expected%20Behavior%0A%0A%0A%23%23%23%20Proposed%20solution',
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to open feedback URL:', error)
|
||||
logger.error('Failed to open feedback URL:', error)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -123,11 +127,18 @@ export function Layout() {
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [handleKeyDown])
|
||||
|
||||
// Notify about log location on startup (production only)
|
||||
useEffect(() => {
|
||||
if (!import.meta.env.DEV) {
|
||||
notifyLogLocation()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const loadSessions = async () => {
|
||||
try {
|
||||
await useStore.getState().refreshSessions()
|
||||
} catch (error) {
|
||||
console.error('Failed to load sessions:', error)
|
||||
logger.error('Failed to load sessions:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +146,7 @@ export function Layout() {
|
||||
try {
|
||||
// Handle new approval format directly
|
||||
if (!approval || !approval.id) {
|
||||
console.error('Invalid approval data:', approval)
|
||||
logger.error('Invalid approval data:', approval)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,6 +251,21 @@ export function Layout() {
|
||||
<p>Submit feedback (⌘⇧F)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{import.meta.env.DEV && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => setIsDebugPanelOpen(true)}
|
||||
className="px-1.5 py-0.5 text-xs font-mono border border-border bg-background text-foreground hover:bg-accent/10 transition-colors"
|
||||
>
|
||||
<Bug className="w-3 h-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Debug Panel (Dev Only)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<ThemeSelector />
|
||||
<div className="flex items-center gap-2 font-mono text-xs">
|
||||
<span className="uppercase tracking-wider">
|
||||
@@ -264,6 +290,9 @@ export function Layout() {
|
||||
|
||||
{/* Notifications */}
|
||||
<Toaster position="bottom-right" richColors />
|
||||
|
||||
{/* Debug Panel */}
|
||||
<DebugPanel open={isDebugPanelOpen} onOpenChange={setIsDebugPanelOpen} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
@@ -21,7 +22,7 @@ export class MarkdownErrorBoundary extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('Markdown rendering error:', error, errorInfo)
|
||||
logger.error('Markdown rendering error:', error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useSessionNavigation } from './hooks/useSessionNavigation'
|
||||
import { useTaskGrouping } from './hooks/useTaskGrouping'
|
||||
import { useSessionClipboard } from './hooks/useSessionClipboard'
|
||||
import { useStealHotkeyScope } from '@/hooks/useStealHotkeyScope'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface SessionDetailProps {
|
||||
session: Session
|
||||
@@ -307,7 +308,7 @@ function SessionDetail({ session, onClose }: SessionDetailProps) {
|
||||
'escape',
|
||||
ev => {
|
||||
if ((ev.target as HTMLElement)?.dataset.slot === 'dialog-close') {
|
||||
console.warn('Ignoring onClose triggered by dialog-close in SessionDetail')
|
||||
logger.warn('Ignoring onClose triggered by dialog-close in SessionDetail')
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -355,7 +356,7 @@ function SessionDetail({ session, onClose }: SessionDetailProps) {
|
||||
|
||||
// State will be updated via event subscription
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle auto-accept mode:', error)
|
||||
logger.error('Failed to toggle auto-accept mode:', error)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// --- Minimal diff utilities (no third-party libraries) ---
|
||||
function computeLineDiff(oldStr: string, newStr: string) {
|
||||
@@ -601,7 +602,7 @@ export const CustomDiffViewer = ({
|
||||
)
|
||||
} catch (error) {
|
||||
// Log detailed context for debugging
|
||||
console.warn('Snapshot-based diff rendering failed, falling back to simple diff:', {
|
||||
logger.warn('Snapshot-based diff rendering failed, falling back to simple diff:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
editsCount: edits.length,
|
||||
fileContentLength: fileContents?.length,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component, ReactNode } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode
|
||||
@@ -21,7 +22,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('SessionDetail Error:', error, errorInfo)
|
||||
logger.error('SessionDetail Error:', error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -62,7 +62,7 @@ export function eventToDisplayObject(
|
||||
let iconComponent = null
|
||||
let toolResultContent = null
|
||||
|
||||
// console.log('event', event)
|
||||
// logger.log('event', event)
|
||||
// Check if this is a thinking message
|
||||
const isThinking =
|
||||
event.eventType === ConversationEventType.Thinking ||
|
||||
@@ -618,7 +618,7 @@ export function eventToDisplayObject(
|
||||
}
|
||||
|
||||
if (subject === null) {
|
||||
// console.warn('Unknown subject for event', event)
|
||||
// logger.warn('Unknown subject for event', event)
|
||||
subject = <span>Unknown Subject</span>
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { daemonClient } from '@/lib/daemon/client'
|
||||
import { renderSessionStatus } from '@/utils/sessionStatus'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface SessionTableProps {
|
||||
sessions: Session[]
|
||||
@@ -243,7 +244,7 @@ export default function SessionTable({
|
||||
const currentSession = sessions.find(s => s.id === focusedSession?.id)
|
||||
if (!currentSession) return
|
||||
|
||||
console.log('Archive hotkey pressed:', {
|
||||
logger.log('Archive hotkey pressed:', {
|
||||
sessionId: currentSession.id,
|
||||
archived: currentSession.archived,
|
||||
willArchive: !currentSession.archived,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
|
||||
import { daemonClient } from '@/lib/daemon'
|
||||
import { Approval } from '@/lib/daemon/types'
|
||||
import { formatError } from '@/utils/errors'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface UseApprovalsReturn {
|
||||
approvals: Approval[]
|
||||
@@ -109,7 +110,7 @@ export function useApprovalsWithSubscription(sessionId?: string): UseApprovalsRe
|
||||
})
|
||||
unsubscribe = handle.unsubscribe
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe to events:', err)
|
||||
logger.error('Failed to subscribe to events:', err)
|
||||
// Fall back to polling on subscription failure
|
||||
const interval = setInterval(() => {
|
||||
if (isSubscribed) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { daemonClient } from '@/lib/daemon'
|
||||
import { formatError } from '@/utils/errors'
|
||||
import { daemonService } from '@/services/daemon-service'
|
||||
|
||||
interface UseDaemonConnectionReturn {
|
||||
connected: boolean
|
||||
@@ -8,6 +9,7 @@ interface UseDaemonConnectionReturn {
|
||||
error: string | null
|
||||
version: string | null
|
||||
connect: () => Promise<void>
|
||||
reconnect: () => Promise<void>
|
||||
checkHealth: () => Promise<void>
|
||||
}
|
||||
|
||||
@@ -16,6 +18,7 @@ export function useDaemonConnection(): UseDaemonConnectionReturn {
|
||||
const [connecting, setConnecting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [version, setVersion] = useState<string | null>(null)
|
||||
const retryCount = useRef(0)
|
||||
|
||||
const checkHealth = useCallback(async () => {
|
||||
try {
|
||||
@@ -31,11 +34,47 @@ export function useDaemonConnection(): UseDaemonConnectionReturn {
|
||||
}, [])
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
if (connecting) return
|
||||
|
||||
setConnecting(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
await daemonClient.connect()
|
||||
const health = await daemonClient.health()
|
||||
|
||||
setConnected(true)
|
||||
setVersion(health.version)
|
||||
retryCount.current = 0
|
||||
} catch (err: any) {
|
||||
setConnected(false)
|
||||
|
||||
// Check if this is first failure and we have a managed daemon
|
||||
if (retryCount.current === 0) {
|
||||
const isManaged = await daemonService.isDaemonRunning()
|
||||
if (!isManaged) {
|
||||
// Let DaemonManager handle it
|
||||
setError(formatError(err))
|
||||
} else {
|
||||
// Managed daemon might be starting, retry
|
||||
retryCount.current++
|
||||
setTimeout(() => connect(), 2000)
|
||||
}
|
||||
} else {
|
||||
setError(formatError(err))
|
||||
}
|
||||
} finally {
|
||||
setConnecting(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const reconnect = useCallback(async () => {
|
||||
try {
|
||||
setConnecting(true)
|
||||
setError(null)
|
||||
setConnected(false)
|
||||
|
||||
await daemonClient.connect()
|
||||
await daemonClient.reconnect()
|
||||
await checkHealth()
|
||||
} catch (err) {
|
||||
setError(formatError(err))
|
||||
@@ -64,6 +103,7 @@ export function useDaemonConnection(): UseDaemonConnectionReturn {
|
||||
error,
|
||||
version,
|
||||
connect,
|
||||
reconnect,
|
||||
checkHealth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useHotkeysContext } from 'react-hotkeys-hook'
|
||||
import { SessionTableHotkeysScope } from '@/components/internal/SessionTable'
|
||||
import { exists } from '@tauri-apps/plugin-fs'
|
||||
import { homeDir } from '@tauri-apps/api/path'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
interface SessionConfig {
|
||||
query: string
|
||||
@@ -264,7 +265,7 @@ export function useSessionLauncherHotkeys() {
|
||||
e.preventDefault()
|
||||
setGPrefixMode(false)
|
||||
// TODO: Navigate to approvals view
|
||||
console.log('Navigate to approvals (Phase 2)')
|
||||
logger.log('Navigate to approvals (Phase 2)')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useHotkeysContext } from 'react-hotkeys-hook'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
export function useStealHotkeyScope(targetScope: string) {
|
||||
const { activeScopes, enableScope, disableScope } = useHotkeysContext()
|
||||
@@ -7,16 +8,16 @@ export function useStealHotkeyScope(targetScope: string) {
|
||||
|
||||
useEffect(() => {
|
||||
initialScopesRef.current = activeScopes
|
||||
console.log('disabling scopes (storing initial)', activeScopes)
|
||||
logger.log('disabling scopes (storing initial)', activeScopes)
|
||||
activeScopes.forEach(scope => disableScope(scope))
|
||||
console.log('activating target scope', targetScope)
|
||||
logger.log('activating target scope', targetScope)
|
||||
enableScope(targetScope)
|
||||
|
||||
return () => {
|
||||
console.log('disabling target scope', targetScope)
|
||||
logger.log('disabling target scope', targetScope)
|
||||
disableScope(targetScope)
|
||||
|
||||
console.log('enabling scopes', initialScopesRef.current)
|
||||
logger.log('enabling scopes', initialScopesRef.current)
|
||||
initialScopesRef.current.forEach(scope => enableScope(scope))
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { daemonClient } from '@/lib/daemon'
|
||||
import { logger } from '@/lib/logging'
|
||||
import type {
|
||||
Event,
|
||||
SessionStatusChangedEventData,
|
||||
@@ -26,18 +27,18 @@ export function useSessionSubscriptions(
|
||||
}, [handlers])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('useSubscriptions effect running', {
|
||||
logger.log('useSubscriptions effect running', {
|
||||
connected,
|
||||
isSubscribed: isSubscribedRef.current,
|
||||
})
|
||||
|
||||
if (!connected) {
|
||||
console.log('Not connected, skipping subscription')
|
||||
logger.log('Not connected, skipping subscription')
|
||||
return
|
||||
}
|
||||
|
||||
if (isSubscribedRef.current) {
|
||||
console.log('Already subscribed, skipping')
|
||||
logger.log('Already subscribed, skipping')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,12 +51,12 @@ export function useSessionSubscriptions(
|
||||
|
||||
// Double-check we're still active after the delay
|
||||
if (!isActive) {
|
||||
console.log('Component unmounted during subscription setup')
|
||||
logger.log('Component unmounted during subscription setup')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Creating new subscription...')
|
||||
logger.log('Creating new subscription...')
|
||||
// Mark as subscribed immediately to prevent duplicate subscriptions
|
||||
isSubscribedRef.current = true
|
||||
|
||||
@@ -91,9 +92,9 @@ export function useSessionSubscriptions(
|
||||
})
|
||||
|
||||
unsubscribe = subscription.unsubscribe
|
||||
console.log('Subscription created successfully')
|
||||
logger.log('Subscription created successfully')
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe to events:', err)
|
||||
logger.error('Failed to subscribe to events:', err)
|
||||
// Reset on error to allow retry
|
||||
isSubscribedRef.current = false
|
||||
// Handler should deal with fallback behavior
|
||||
@@ -103,17 +104,17 @@ export function useSessionSubscriptions(
|
||||
subscribe()
|
||||
|
||||
return () => {
|
||||
console.log('Cleanup: Unsubscribing from session events')
|
||||
logger.log('Cleanup: Unsubscribing from session events')
|
||||
isActive = false
|
||||
// Reset the ref immediately to allow re-subscription
|
||||
isSubscribedRef.current = false
|
||||
|
||||
// Call unsubscribe if it exists
|
||||
if (unsubscribe) {
|
||||
console.log('Calling unsubscribe function')
|
||||
logger.log('Calling unsubscribe function')
|
||||
unsubscribe()
|
||||
} else {
|
||||
console.log('No unsubscribe function available')
|
||||
logger.log('No unsubscribe function available')
|
||||
}
|
||||
}
|
||||
}, [connected]) // Only re-subscribe when connection status changes
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ConversationEvent,
|
||||
} from '@humanlayer/hld-sdk'
|
||||
import { getDaemonUrl, getDefaultHeaders } from './http-config'
|
||||
import { logger } from '@/lib/logging'
|
||||
import type {
|
||||
DaemonClient as IDaemonClient,
|
||||
LaunchSessionParams,
|
||||
@@ -61,7 +62,8 @@ export class HTTPDaemonClient implements IDaemonClient {
|
||||
}
|
||||
|
||||
private async doConnect(): Promise<void> {
|
||||
const baseUrl = getDaemonUrl()
|
||||
// getDaemonUrl now checks for managed daemon port dynamically
|
||||
const baseUrl = await getDaemonUrl()
|
||||
|
||||
this.client = new HLDClient({
|
||||
baseUrl: `${baseUrl}/api/v1`,
|
||||
@@ -82,6 +84,16 @@ export class HTTPDaemonClient implements IDaemonClient {
|
||||
}
|
||||
}
|
||||
|
||||
async reconnect(): Promise<void> {
|
||||
// Disconnect first if connected
|
||||
if (this.connected || this.connectionPromise) {
|
||||
await this.disconnect()
|
||||
}
|
||||
|
||||
// Now connect to the potentially new URL
|
||||
return this.connect()
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
// Unsubscribe all event streams
|
||||
for (const unsubscribe of this.subscriptions.values()) {
|
||||
@@ -91,6 +103,8 @@ export class HTTPDaemonClient implements IDaemonClient {
|
||||
|
||||
this.connected = false
|
||||
this.client = undefined
|
||||
this.connectionPromise = undefined
|
||||
this.retryCount = 0
|
||||
}
|
||||
|
||||
async health(): Promise<HealthCheckResponse> {
|
||||
@@ -346,10 +360,10 @@ export class HTTPDaemonClient implements IDaemonClient {
|
||||
})
|
||||
},
|
||||
onError: error => {
|
||||
console.error('Event subscription error:', error)
|
||||
logger.error('Event subscription error:', error)
|
||||
// Attempt reconnection
|
||||
if (!this.connected) {
|
||||
this.connect().catch(console.error)
|
||||
this.connect().catch(logger.error)
|
||||
}
|
||||
},
|
||||
onDisconnect: () => {
|
||||
@@ -362,7 +376,7 @@ export class HTTPDaemonClient implements IDaemonClient {
|
||||
this.subscriptions.set(subscriptionId, unsubscribe)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to start event subscription:', error)
|
||||
logger.error('Failed to start event subscription:', error)
|
||||
})
|
||||
|
||||
// Return handle for unsubscribing
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
// Get daemon URL from environment or default
|
||||
export function getDaemonUrl(): string {
|
||||
// Check for explicit URL first
|
||||
import { daemonService } from '@/services/daemon-service'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// Get daemon URL from environment or managed daemon
|
||||
export async function getDaemonUrl(): Promise<string> {
|
||||
// Check for custom URL from debug panel first
|
||||
if ((window as any).__HUMANLAYER_DAEMON_URL) {
|
||||
return (window as any).__HUMANLAYER_DAEMON_URL
|
||||
}
|
||||
|
||||
// Check for explicit URL from environment
|
||||
if (import.meta.env.VITE_HUMANLAYER_DAEMON_URL) {
|
||||
return import.meta.env.VITE_HUMANLAYER_DAEMON_URL
|
||||
}
|
||||
|
||||
// Check if we have a managed daemon
|
||||
try {
|
||||
const daemonInfo = await daemonService.getDaemonInfo()
|
||||
if (daemonInfo && daemonInfo.port) {
|
||||
return `http://localhost:${daemonInfo.port}`
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Failed to get managed daemon info:', error)
|
||||
}
|
||||
|
||||
// Check for port override
|
||||
const port = import.meta.env.VITE_HUMANLAYER_DAEMON_HTTP_PORT || '7777'
|
||||
const host = import.meta.env.VITE_HUMANLAYER_DAEMON_HTTP_HOST || 'localhost'
|
||||
@@ -15,7 +33,7 @@ export function getDaemonUrl(): string {
|
||||
// Headers to include with all requests
|
||||
export function getDefaultHeaders(): Record<string, string> {
|
||||
return {
|
||||
'X-Client': 'wui',
|
||||
'X-Client': 'codelayer',
|
||||
'X-Client-Version': import.meta.env.VITE_APP_VERSION || 'unknown',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface SubscriptionHandle {
|
||||
// Client interface using legacy types for backward compatibility
|
||||
export interface DaemonClient {
|
||||
connect(): Promise<void>
|
||||
reconnect(): Promise<void>
|
||||
disconnect(): Promise<void>
|
||||
health(): Promise<HealthCheckResponse>
|
||||
|
||||
|
||||
50
humanlayer-wui/src/lib/log-notification.ts
Normal file
50
humanlayer-wui/src/lib/log-notification.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { appLogDir } from '@tauri-apps/api/path'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { logger } from './logging'
|
||||
|
||||
export async function notifyLogLocation() {
|
||||
try {
|
||||
let logDir: string
|
||||
|
||||
// Try to get custom log directory first (dev mode)
|
||||
try {
|
||||
logDir = await invoke<string>('get_log_directory')
|
||||
} catch {
|
||||
// Fall back to default app log directory (production)
|
||||
logDir = await appLogDir()
|
||||
}
|
||||
|
||||
// Log to console (visible in dev mode)
|
||||
console.log(
|
||||
`%c📁 Application logs are stored in: ${logDir}`,
|
||||
'color: #4CAF50; font-weight: bold; font-size: 14px;',
|
||||
)
|
||||
|
||||
// Also log through our logging service
|
||||
logger.log(`Application logs are stored in: ${logDir}`)
|
||||
|
||||
return logDir
|
||||
} catch (error) {
|
||||
logger.error('Failed to get log directory:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function openLogDirectory() {
|
||||
try {
|
||||
let logDir: string
|
||||
|
||||
// Try to get custom log directory first (dev mode)
|
||||
try {
|
||||
logDir = await invoke<string>('get_log_directory')
|
||||
} catch {
|
||||
// Fall back to default app log directory (production)
|
||||
logDir = await appLogDir()
|
||||
}
|
||||
|
||||
await openUrl(logDir)
|
||||
} catch (error) {
|
||||
logger.error('Failed to open log directory:', error)
|
||||
}
|
||||
}
|
||||
107
humanlayer-wui/src/lib/logging.ts
Normal file
107
humanlayer-wui/src/lib/logging.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
// Detect if we're in a test environment (no window object)
|
||||
const isTestEnvironment = typeof window === 'undefined'
|
||||
|
||||
// Lazy load Tauri logging to avoid import errors in tests
|
||||
let tauriLog: any = null
|
||||
const getTauriLog = async () => {
|
||||
if (!tauriLog && !isTestEnvironment) {
|
||||
tauriLog = await import('@tauri-apps/plugin-log')
|
||||
}
|
||||
return tauriLog
|
||||
}
|
||||
|
||||
// Create a logging service that preserves console functionality
|
||||
// while also sending to Tauri's log plugin
|
||||
export const logger = {
|
||||
log: (message: string, ...args: any[]) => {
|
||||
const fullMessage = [message, ...args]
|
||||
.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')
|
||||
|
||||
// In test environment, preserve original arguments
|
||||
if (isTestEnvironment) {
|
||||
console.log(message, ...args)
|
||||
return
|
||||
}
|
||||
|
||||
getTauriLog().then(log => log?.info?.(`[Console] ${fullMessage}`))
|
||||
// Also log to browser console in dev mode
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(message, ...args)
|
||||
}
|
||||
},
|
||||
|
||||
error: (message: string, ...args: any[]) => {
|
||||
const fullMessage = [message, ...args]
|
||||
.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')
|
||||
|
||||
if (isTestEnvironment) {
|
||||
// In tests, preserve original arguments for test assertions
|
||||
console.error(message, ...args)
|
||||
return
|
||||
}
|
||||
|
||||
getTauriLog().then(log => log?.error?.(`[Console] ${fullMessage}`))
|
||||
if (import.meta.env.DEV) {
|
||||
console.error(message, ...args)
|
||||
}
|
||||
},
|
||||
|
||||
warn: (message: string, ...args: any[]) => {
|
||||
const fullMessage = [message, ...args]
|
||||
.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')
|
||||
|
||||
if (isTestEnvironment) {
|
||||
console.warn(message, ...args)
|
||||
return
|
||||
}
|
||||
|
||||
getTauriLog().then(log => log?.warn?.(`[Console] ${fullMessage}`))
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn(message, ...args)
|
||||
}
|
||||
},
|
||||
|
||||
debug: (message: string, ...args: any[]) => {
|
||||
const fullMessage = [message, ...args]
|
||||
.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')
|
||||
|
||||
if (isTestEnvironment) {
|
||||
console.debug(message, ...args)
|
||||
return
|
||||
}
|
||||
|
||||
getTauriLog().then(log => log?.debug?.(`[Console] ${fullMessage}`))
|
||||
if (import.meta.env.DEV) {
|
||||
console.debug(message, ...args)
|
||||
}
|
||||
},
|
||||
|
||||
trace: (message: string, ...args: any[]) => {
|
||||
const fullMessage = [message, ...args]
|
||||
.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')
|
||||
|
||||
if (isTestEnvironment) {
|
||||
console.trace(message, ...args)
|
||||
return
|
||||
}
|
||||
|
||||
getTauriLog().then(log => log?.trace?.(`[Console] ${fullMessage}`))
|
||||
if (import.meta.env.DEV) {
|
||||
console.trace(message, ...args)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Export original console methods for migration
|
||||
export const browserConsole = {
|
||||
log: console.log.bind(console),
|
||||
error: console.error.bind(console),
|
||||
warn: console.warn.bind(console),
|
||||
debug: console.debug.bind(console),
|
||||
trace: console.trace.bind(console),
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import { RouterProvider } from 'react-router-dom'
|
||||
import { router } from './router'
|
||||
import { ThemeProvider } from './contexts/ThemeContext'
|
||||
import { HotkeysProvider } from 'react-hotkeys-hook'
|
||||
import { attachConsole } from '@tauri-apps/plugin-log'
|
||||
|
||||
// Initialize console logging bridge to display Tauri logs in browser console
|
||||
// Note: This does NOT capture frontend console.* calls - it only shows Rust logs in the browser
|
||||
attachConsole().catch(console.error)
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { create, StoreApi, useStore } from 'zustand'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// Define the store interface
|
||||
interface CounterStore {
|
||||
@@ -58,7 +59,7 @@ class DemoAnimator {
|
||||
|
||||
// Subscribe to store changes for logging/debugging
|
||||
this.unsubscribe = store.subscribe(state => {
|
||||
console.log('[Demo Store] Count updated to:', state.count)
|
||||
logger.log('[Demo Store] Count updated to:', state.count)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ function RealStoreProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
// Optional: Subscribe to real store for logging
|
||||
const unsubscribe = realStore.subscribe(state => {
|
||||
console.log('[Real Store] Count updated to:', state.count)
|
||||
logger.log('[Real Store] Count updated to:', state.count)
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Session } from '@/lib/daemon/types'
|
||||
import { ThemeSelector } from '@/components/ThemeSelector'
|
||||
import { SessionLauncher } from '@/components/SessionLauncher'
|
||||
import { DemoStoreProvider, useDemoStore } from '@/stores/demo/providers/DemoStoreProvider'
|
||||
import { logger } from '@/lib/logging'
|
||||
import {
|
||||
launcherWorkflowSequence,
|
||||
statusChangesSequence,
|
||||
@@ -32,7 +33,7 @@ function SessionTableWrapper() {
|
||||
})
|
||||
|
||||
const handleActivateSession = (session: Session) => {
|
||||
console.log('Activating session:', session.id)
|
||||
logger.log('Activating session:', session.id)
|
||||
// In demo mode, this would navigate to session detail
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@tauri-apps/plugin-notification'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { formatError } from '@/utils/errors'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// Types for generic notification system
|
||||
export type NotificationType =
|
||||
@@ -44,7 +45,7 @@ class NotificationService {
|
||||
private unlistenBlur: (() => void) | null = null
|
||||
|
||||
constructor() {
|
||||
console.log('NotificationService: Constructor called')
|
||||
logger.log('NotificationService: Constructor called')
|
||||
this.attachFocusListeners()
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
private async attachFocusListeners() {
|
||||
console.log('NotificationService: Attaching focus listeners')
|
||||
logger.log('NotificationService: Attaching focus listeners')
|
||||
|
||||
// Check initial focus state synchronously
|
||||
this.validateFocusState()
|
||||
@@ -70,36 +71,36 @@ class NotificationService {
|
||||
|
||||
// Listen for focus events
|
||||
this.unlistenFocus = await appWindow.onFocusChanged(event => {
|
||||
console.log('Tauri window focus changed:', event)
|
||||
logger.log('Tauri window focus changed:', event)
|
||||
this.appFocused = event.payload
|
||||
})
|
||||
|
||||
// Also use standard window events as fallback
|
||||
this.focusHandler = () => {
|
||||
console.log('window focused (standard event)')
|
||||
logger.log('window focused (standard event)')
|
||||
this.appFocused = true
|
||||
}
|
||||
|
||||
this.blurHandler = () => {
|
||||
console.log('window blurred (standard event)')
|
||||
logger.log('window blurred (standard event)')
|
||||
this.appFocused = false
|
||||
}
|
||||
|
||||
window.addEventListener('focus', this.focusHandler)
|
||||
window.addEventListener('blur', this.blurHandler)
|
||||
|
||||
console.log('NotificationService: Focus listeners attached')
|
||||
logger.log('NotificationService: Focus listeners attached')
|
||||
} catch (error) {
|
||||
console.error('Failed to attach Tauri focus listeners, using standard events only:', error)
|
||||
logger.error('Failed to attach Tauri focus listeners, using standard events only:', error)
|
||||
|
||||
// Fallback to standard events only
|
||||
this.focusHandler = () => {
|
||||
console.log('window focused')
|
||||
logger.log('window focused')
|
||||
this.appFocused = true
|
||||
}
|
||||
|
||||
this.blurHandler = () => {
|
||||
console.log('window blurred')
|
||||
logger.log('window blurred')
|
||||
this.appFocused = false
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
private async detachFocusListeners() {
|
||||
console.log('NotificationService: Detaching focus listeners', {
|
||||
logger.log('NotificationService: Detaching focus listeners', {
|
||||
hasFocusHandler: !!this.focusHandler,
|
||||
hasBlurHandler: !!this.blurHandler,
|
||||
hasUnlistenFocus: !!this.unlistenFocus,
|
||||
@@ -142,7 +143,7 @@ class NotificationService {
|
||||
* Clean up resources (useful for hot module replacement)
|
||||
*/
|
||||
cleanup() {
|
||||
console.log('NotificationService: Cleanup called')
|
||||
logger.log('NotificationService: Cleanup called')
|
||||
this.detachFocusListeners()
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ class NotificationService {
|
||||
? this.isViewingSession(options.metadata.sessionId)
|
||||
: false
|
||||
|
||||
console.log('NotificationService.notify:', {
|
||||
logger.log('NotificationService.notify:', {
|
||||
appFocused: this.appFocused,
|
||||
notificationType: options.type,
|
||||
sessionId: options.metadata.sessionId,
|
||||
@@ -208,7 +209,7 @@ class NotificationService {
|
||||
}
|
||||
// If app is focused but user is viewing the session, skip in-app notification
|
||||
else if (isViewingSession) {
|
||||
console.log(`Skipping in-app notification: User is viewing session ${options.metadata.sessionId}`)
|
||||
logger.log(`Skipping in-app notification: User is viewing session ${options.metadata.sessionId}`)
|
||||
return null
|
||||
}
|
||||
// Otherwise show in-app notification
|
||||
@@ -258,11 +259,11 @@ class NotificationService {
|
||||
* Show OS-level notification using Tauri plugin
|
||||
*/
|
||||
private async showOSNotification(options: NotificationOptions) {
|
||||
console.log('NotificationService.showOSNotification called:', options.title)
|
||||
logger.log('NotificationService.showOSNotification called:', options.title)
|
||||
try {
|
||||
// Check if we have permission
|
||||
let permissionGranted = await isPermissionGranted()
|
||||
console.log('OS notification permission granted:', permissionGranted)
|
||||
logger.log('OS notification permission granted:', permissionGranted)
|
||||
|
||||
// Request permission if not granted
|
||||
if (!permissionGranted) {
|
||||
@@ -271,7 +272,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
if (!permissionGranted) {
|
||||
console.warn('Notification permission not granted, falling back to in-app notification')
|
||||
logger.warn('Notification permission not granted, falling back to in-app notification')
|
||||
this.showInAppNotification(options)
|
||||
return
|
||||
}
|
||||
@@ -284,7 +285,7 @@ class NotificationService {
|
||||
// but at least the notification will bring attention to the app
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to show OS notification:', error)
|
||||
logger.error('Failed to show OS notification:', error)
|
||||
// Fallback to in-app notification
|
||||
this.showInAppNotification(options)
|
||||
}
|
||||
@@ -295,7 +296,7 @@ class NotificationService {
|
||||
*/
|
||||
async notifyError(error: unknown, context?: string): Promise<string | null> {
|
||||
// Always log to console for debugging
|
||||
console.error(context || 'Error:', error)
|
||||
logger.error(context || 'Error:', error)
|
||||
|
||||
// Format the error message
|
||||
const formattedMessage = formatError(error)
|
||||
@@ -372,19 +373,19 @@ let notificationService: NotificationService
|
||||
|
||||
// Clean up previous instance on hot reload
|
||||
if (import.meta.hot) {
|
||||
console.log('NotificationService: HMR detected, checking for previous instance')
|
||||
logger.log('NotificationService: HMR detected, checking for previous instance')
|
||||
if ((import.meta as any).hot.data.notificationService) {
|
||||
console.log('NotificationService: Cleaning up previous instance')
|
||||
logger.log('NotificationService: Cleaning up previous instance')
|
||||
;(import.meta as any).hot.data.notificationService.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
console.log('NotificationService: Creating new instance')
|
||||
logger.log('NotificationService: Creating new instance')
|
||||
notificationService = new NotificationService()
|
||||
|
||||
// Store instance for cleanup on next hot reload
|
||||
if (import.meta.hot) {
|
||||
console.log('NotificationService: Storing instance for future cleanup')
|
||||
logger.log('NotificationService: Storing instance for future cleanup')
|
||||
;(import.meta as any).hot.data.notificationService = notificationService
|
||||
}
|
||||
|
||||
|
||||
105
humanlayer-wui/src/services/daemon-service.ts
Normal file
105
humanlayer-wui/src/services/daemon-service.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
export interface DaemonInfo {
|
||||
port: number
|
||||
pid: number
|
||||
database_path: string
|
||||
socket_path: string
|
||||
branch_id: string
|
||||
}
|
||||
|
||||
export interface DaemonConfig {
|
||||
branch_id: string
|
||||
port: number
|
||||
database_path: string
|
||||
socket_path: string
|
||||
last_used: string
|
||||
}
|
||||
|
||||
class DaemonService {
|
||||
private static instance: DaemonService
|
||||
|
||||
static getInstance(): DaemonService {
|
||||
if (!DaemonService.instance) {
|
||||
DaemonService.instance = new DaemonService()
|
||||
}
|
||||
return DaemonService.instance
|
||||
}
|
||||
|
||||
async startDaemon(isDev: boolean = false, branchOverride?: string): Promise<DaemonInfo> {
|
||||
try {
|
||||
return await invoke<DaemonInfo>('start_daemon', { isDev, branchOverride })
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to start daemon: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async stopDaemon(): Promise<void> {
|
||||
try {
|
||||
await invoke('stop_daemon')
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to stop daemon: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getDaemonInfo(isDev: boolean = import.meta.env.DEV): Promise<DaemonInfo | null> {
|
||||
try {
|
||||
return await invoke<DaemonInfo | null>('get_daemon_info', { isDev })
|
||||
} catch (error) {
|
||||
logger.error('Failed to get daemon info:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async isDaemonRunning(): Promise<boolean> {
|
||||
try {
|
||||
return await invoke<boolean>('is_daemon_running')
|
||||
} catch (error) {
|
||||
logger.error('Failed to check daemon status:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async getStoredConfigs(): Promise<DaemonConfig[]> {
|
||||
try {
|
||||
return await invoke<DaemonConfig[]>('get_stored_configs')
|
||||
} catch (error) {
|
||||
logger.error('Failed to get stored configs:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async getConfigPath(): Promise<string> {
|
||||
try {
|
||||
return await invoke<string>('get_config_path')
|
||||
} catch {
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
async connectToExisting(url: string): Promise<void> {
|
||||
// Validate URL format
|
||||
try {
|
||||
new URL(url)
|
||||
} catch {
|
||||
throw new Error(`Invalid URL format: ${url}`)
|
||||
}
|
||||
|
||||
// Store the custom URL temporarily (not in Tauri store)
|
||||
;(window as any).__HUMANLAYER_DAEMON_URL = url
|
||||
;(window as any).__HUMANLAYER_DAEMON_TYPE = 'external'
|
||||
}
|
||||
|
||||
async switchToManagedDaemon(): Promise<void> {
|
||||
// Clear the custom URL to switch back to managed daemon
|
||||
delete (window as any).__HUMANLAYER_DAEMON_URL
|
||||
delete (window as any).__HUMANLAYER_DAEMON_TYPE
|
||||
}
|
||||
|
||||
getDaemonType(): 'managed' | 'external' {
|
||||
return (window as any).__HUMANLAYER_DAEMON_TYPE || 'managed'
|
||||
}
|
||||
}
|
||||
|
||||
export const daemonService = DaemonService.getInstance()
|
||||
@@ -2,6 +2,7 @@ import type { Session, Approval } from '@/lib/daemon/types'
|
||||
import { SessionStatus } from '@/lib/daemon/types'
|
||||
import { create, StoreApi } from 'zustand'
|
||||
import { daemonClient } from '@/lib/daemon'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
export interface AppState {
|
||||
/* Sessions */
|
||||
@@ -110,7 +111,7 @@ export function createRealAppStore(): StoreApi<AppState> {
|
||||
const response = await daemonClient.getSessionLeaves()
|
||||
set({ sessions: response.sessions })
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh sessions:', error)
|
||||
logger.error('Failed to refresh sessions:', error)
|
||||
}
|
||||
},
|
||||
setFocusedSession: (session: Session | null) => set({ focusedSession: session }),
|
||||
@@ -149,7 +150,7 @@ export function createRealAppStore(): StoreApi<AppState> {
|
||||
await daemonClient.interruptSession(sessionId)
|
||||
// The session status will be updated via the subscription
|
||||
} catch (error) {
|
||||
console.error('Failed to interrupt session:', error)
|
||||
logger.error('Failed to interrupt session:', error)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -193,7 +194,7 @@ export function createRealAppStore(): StoreApi<AppState> {
|
||||
activeSessionId: sessionId,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch session detail:', error)
|
||||
logger.error('Failed to fetch session detail:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SessionSlice, createSessionSlice } from './slices/sessionSlice'
|
||||
import { LauncherSlice, createLauncherSlice } from './slices/launcherSlice'
|
||||
import { ThemeSlice, createThemeSlice } from './slices/themeSlice'
|
||||
import { AppSlice, createAppSlice } from './slices/appSlice'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// Combined store type that includes all slices
|
||||
export type ComposedDemoStore = SessionSlice & LauncherSlice & ThemeSlice & AppSlice
|
||||
@@ -97,7 +98,7 @@ export class ComposedDemoAnimator {
|
||||
const step = this.sequence[this.currentIndex]
|
||||
|
||||
this.timeoutId = setTimeout(() => {
|
||||
console.log(
|
||||
logger.log(
|
||||
`[Demo Animator] Step ${this.currentIndex + 1}/${this.sequence.length}: ${step.description || 'State update'}`,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create, StoreApi } from 'zustand'
|
||||
import { Session, SessionStatus } from '@/lib/daemon/types'
|
||||
import { Theme } from '@/contexts/ThemeContext'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
// Comprehensive app state for demo animations
|
||||
interface DemoAppState {
|
||||
@@ -249,7 +250,7 @@ export class DemoAppAnimator {
|
||||
|
||||
// Subscribe to store changes for logging
|
||||
this.unsubscribe = store.subscribe(state => {
|
||||
console.log('[Demo App Store] State updated:', {
|
||||
logger.log('[Demo App Store] State updated:', {
|
||||
sessions: state.sessions.length,
|
||||
launcher: state.launcherOpen,
|
||||
theme: state.theme,
|
||||
@@ -287,7 +288,7 @@ export class DemoAppAnimator {
|
||||
const step = this.sequence[this.currentIndex]
|
||||
|
||||
this.timeoutId = setTimeout(() => {
|
||||
console.log(
|
||||
logger.log(
|
||||
`[Demo App Animator] Step ${this.currentIndex + 1}/${this.sequence.length}: ${step.description || 'State update'}`,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react'
|
||||
import { StoreApi, useStore } from 'zustand'
|
||||
import { logger } from '@/lib/logging'
|
||||
import {
|
||||
ComposedDemoStore,
|
||||
createComposedDemoStore,
|
||||
@@ -61,8 +62,8 @@ export function useDemoAnimator() {
|
||||
// In a real implementation, we'd expose the animator instance
|
||||
// For now, return basic controls
|
||||
return {
|
||||
pause: () => console.log('Pause not implemented'),
|
||||
resume: () => console.log('Resume not implemented'),
|
||||
reset: () => console.log('Reset not implemented'),
|
||||
pause: () => logger.log('Pause not implemented'),
|
||||
resume: () => logger.log('Resume not implemented'),
|
||||
reset: () => logger.log('Reset not implemented'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { toast } from 'sonner'
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
||||
import { logger } from '@/lib/logging'
|
||||
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -7,7 +8,7 @@ export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
toast.success('Copied to clipboard')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to copy to clipboard:', error instanceof Error ? error.message : error)
|
||||
logger.error('Failed to copy to clipboard:', error instanceof Error ? error.message : error)
|
||||
toast.error('Failed to copy to clipboard')
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user