mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-12-23 17:44:24 +00:00
Compare commits
22 Commits
ccdbbdfdd2
...
v0.13.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 463bb561e1 | |||
| ebdb9143c0 | |||
| 4269d04381 | |||
| c734329a62 | |||
| 15fcf7c377 | |||
|
|
795f530956 | ||
|
|
4507358310 | ||
| f3c02b4939 | |||
| 0a85409deb | |||
| 22fd295250 | |||
| c0cecdd377 | |||
| 4d57b37a5d | |||
| c13b71d07f | |||
| 406a711682 | |||
| 0b3d654945 | |||
| e2a49402d6 | |||
| 48836c9c12 | |||
| 4200b8eed9 | |||
|
|
9a7ae87df8 | ||
| e54c495528 | |||
|
|
83006968ca | ||
|
|
c8d9c6907c |
34
.github/workflows/release.yaml
vendored
34
.github/workflows/release.yaml
vendored
@@ -45,15 +45,23 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build Binaries
|
name: Build Binaries
|
||||||
needs: build-webui
|
needs: build-webui
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.runner }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux, windows, darwin]
|
include:
|
||||||
goarch: [amd64, arm64]
|
- goos: linux
|
||||||
exclude:
|
goarch: amd64
|
||||||
# Windows ARM64 support is limited
|
runner: ubuntu-latest
|
||||||
- goos: windows
|
- goos: linux
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
cc: aarch64-linux-gnu-gcc
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
runner: macos-latest
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
runner: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -70,11 +78,19 @@ jobs:
|
|||||||
name: webui-dist
|
name: webui-dist
|
||||||
path: webui/dist/
|
path: webui/dist/
|
||||||
|
|
||||||
|
- name: Install cross-compilation tools (Linux ARM64 only)
|
||||||
|
if: matrix.cc != ''
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||||
|
|
||||||
- name: Build binary
|
- name: Build binary
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 1
|
||||||
|
CC: ${{ matrix.cc }}
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Set binary extension for Windows
|
# Set binary extension for Windows
|
||||||
BINARY_NAME="llamactl"
|
BINARY_NAME="llamactl"
|
||||||
@@ -91,8 +107,10 @@ jobs:
|
|||||||
ARCHIVE_OS="macos"
|
ARCHIVE_OS="macos"
|
||||||
fi
|
fi
|
||||||
ARCHIVE_NAME="llamactl-${{ github.ref_name }}-${ARCHIVE_OS}-${{ matrix.goarch }}"
|
ARCHIVE_NAME="llamactl-${{ github.ref_name }}-${ARCHIVE_OS}-${{ matrix.goarch }}"
|
||||||
|
|
||||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||||
zip "${ARCHIVE_NAME}.zip" "${BINARY_NAME}"
|
# Use 7z on Windows (pre-installed)
|
||||||
|
7z a "${ARCHIVE_NAME}.zip" "${BINARY_NAME}"
|
||||||
echo "ASSET_PATH=${ARCHIVE_NAME}.zip" >> $GITHUB_ENV
|
echo "ASSET_PATH=${ARCHIVE_NAME}.zip" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
tar -czf "${ARCHIVE_NAME}.tar.gz" "${BINARY_NAME}"
|
tar -czf "${ARCHIVE_NAME}.tar.gz" "${BINARY_NAME}"
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ instances:
|
|||||||
default_on_demand_start: true # Default on-demand start setting
|
default_on_demand_start: true # Default on-demand start setting
|
||||||
on_demand_start_timeout: 120 # Default on-demand start timeout in seconds
|
on_demand_start_timeout: 120 # Default on-demand start timeout in seconds
|
||||||
timeout_check_interval: 5 # Idle instance timeout check in minutes
|
timeout_check_interval: 5 # Idle instance timeout check in minutes
|
||||||
|
log_rotation_enabled: true # Enable log rotation (default: true)
|
||||||
|
log_rotation_max_size: 100 # Max log file size in MB before rotation (default: 100)
|
||||||
|
log_rotation_compress: false # Compress rotated log files (default: false)
|
||||||
|
|
||||||
database:
|
database:
|
||||||
path: ~/.local/share/llamactl/llamactl.db # Database file path (platform dependent)
|
path: ~/.local/share/llamactl/llamactl.db # Database file path (platform dependent)
|
||||||
|
|||||||
@@ -230,6 +230,9 @@ instances:
|
|||||||
default_on_demand_start: true # Default on-demand start setting
|
default_on_demand_start: true # Default on-demand start setting
|
||||||
on_demand_start_timeout: 120 # Default on-demand start timeout in seconds
|
on_demand_start_timeout: 120 # Default on-demand start timeout in seconds
|
||||||
timeout_check_interval: 5 # Default instance timeout check interval in minutes
|
timeout_check_interval: 5 # Default instance timeout check interval in minutes
|
||||||
|
log_rotation_enabled: true # Enable log rotation (default: true)
|
||||||
|
log_rotation_max_size: 100 # Max log file size in MB before rotation (default: 100)
|
||||||
|
log_rotation_compress: false # Compress rotated log files (default: false)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Environment Variables:**
|
**Environment Variables:**
|
||||||
@@ -246,6 +249,9 @@ instances:
|
|||||||
- `LLAMACTL_DEFAULT_ON_DEMAND_START` - Default on-demand start setting (true/false)
|
- `LLAMACTL_DEFAULT_ON_DEMAND_START` - Default on-demand start setting (true/false)
|
||||||
- `LLAMACTL_ON_DEMAND_START_TIMEOUT` - Default on-demand start timeout in seconds
|
- `LLAMACTL_ON_DEMAND_START_TIMEOUT` - Default on-demand start timeout in seconds
|
||||||
- `LLAMACTL_TIMEOUT_CHECK_INTERVAL` - Default instance timeout check interval in minutes
|
- `LLAMACTL_TIMEOUT_CHECK_INTERVAL` - Default instance timeout check interval in minutes
|
||||||
|
- `LLAMACTL_LOG_ROTATION_ENABLED` - Enable log rotation (true/false)
|
||||||
|
- `LLAMACTL_LOG_ROTATION_MAX_SIZE` - Max log file size in MB
|
||||||
|
- `LLAMACTL_LOG_ROTATION_COMPRESS` - Compress rotated logs (true/false)
|
||||||
|
|
||||||
### Database Configuration
|
### Database Configuration
|
||||||
|
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -3,13 +3,14 @@ module llamactl
|
|||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/DeRuina/timberjack v1.3.9
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
github.com/swaggo/http-swagger v1.3.4
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
github.com/swaggo/swag v1.16.5
|
github.com/swaggo/swag v1.16.5
|
||||||
golang.org/x/crypto v0.45.0
|
golang.org/x/crypto v0.46.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,11 +21,12 @@ require (
|
|||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
github.com/go-openapi/swag v0.23.1 // indirect
|
github.com/go-openapi/swag v0.23.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/swaggo/files v1.0.1 // indirect
|
github.com/swaggo/files v1.0.1 // indirect
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -1,7 +1,11 @@
|
|||||||
|
github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo=
|
||||||
|
github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
@@ -20,6 +24,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -45,8 +51,8 @@ github.com/swaggo/swag v1.16.5/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
@@ -66,8 +72,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
|||||||
@@ -6,233 +6,18 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackendSettings contains structured backend configuration
|
|
||||||
type BackendSettings struct {
|
|
||||||
Command string `yaml:"command" json:"command"`
|
|
||||||
Args []string `yaml:"args" json:"args"`
|
|
||||||
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
|
||||||
Docker *DockerSettings `yaml:"docker,omitempty" json:"docker,omitempty"`
|
|
||||||
ResponseHeaders map[string]string `yaml:"response_headers,omitempty" json:"response_headers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerSettings contains Docker-specific configuration
|
|
||||||
type DockerSettings struct {
|
|
||||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
||||||
Image string `yaml:"image" json:"image"`
|
|
||||||
Args []string `yaml:"args" json:"args"`
|
|
||||||
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackendConfig contains backend executable configurations
|
|
||||||
type BackendConfig struct {
|
|
||||||
LlamaCpp BackendSettings `yaml:"llama-cpp" json:"llama-cpp"`
|
|
||||||
VLLM BackendSettings `yaml:"vllm" json:"vllm"`
|
|
||||||
MLX BackendSettings `yaml:"mlx" json:"mlx"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppConfig represents the configuration for llamactl
|
|
||||||
type AppConfig struct {
|
|
||||||
Server ServerConfig `yaml:"server" json:"server"`
|
|
||||||
Backends BackendConfig `yaml:"backends" json:"backends"`
|
|
||||||
Instances InstancesConfig `yaml:"instances" json:"instances"`
|
|
||||||
Database DatabaseConfig `yaml:"database" json:"database"`
|
|
||||||
Auth AuthConfig `yaml:"auth" json:"auth"`
|
|
||||||
LocalNode string `yaml:"local_node,omitempty" json:"local_node,omitempty"`
|
|
||||||
Nodes map[string]NodeConfig `yaml:"nodes,omitempty" json:"nodes,omitempty"`
|
|
||||||
|
|
||||||
// Directory where all llamactl data will be stored (database, instances, logs, etc.)
|
|
||||||
DataDir string `yaml:"data_dir" json:"data_dir"`
|
|
||||||
|
|
||||||
Version string `yaml:"-" json:"version"`
|
|
||||||
CommitHash string `yaml:"-" json:"commit_hash"`
|
|
||||||
BuildTime string `yaml:"-" json:"build_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerConfig contains HTTP server configuration
|
|
||||||
type ServerConfig struct {
|
|
||||||
// Server host to bind to
|
|
||||||
Host string `yaml:"host" json:"host"`
|
|
||||||
|
|
||||||
// Server port to bind to
|
|
||||||
Port int `yaml:"port" json:"port"`
|
|
||||||
|
|
||||||
// Allowed origins for CORS (e.g., "http://localhost:3000")
|
|
||||||
AllowedOrigins []string `yaml:"allowed_origins" json:"allowed_origins"`
|
|
||||||
|
|
||||||
// Allowed headers for CORS (e.g., "Accept", "Authorization", "Content-Type", "X-CSRF-Token")
|
|
||||||
AllowedHeaders []string `yaml:"allowed_headers" json:"allowed_headers"`
|
|
||||||
|
|
||||||
// Enable Swagger UI for API documentation
|
|
||||||
EnableSwagger bool `yaml:"enable_swagger" json:"enable_swagger"`
|
|
||||||
|
|
||||||
// Response headers to send with responses
|
|
||||||
ResponseHeaders map[string]string `yaml:"response_headers,omitempty" json:"response_headers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatabaseConfig contains database configuration settings
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
// Database file path (relative to the top-level data_dir or absolute)
|
|
||||||
Path string `yaml:"path" json:"path"`
|
|
||||||
|
|
||||||
// Connection settings
|
|
||||||
MaxOpenConnections int `yaml:"max_open_connections" json:"max_open_connections"`
|
|
||||||
MaxIdleConnections int `yaml:"max_idle_connections" json:"max_idle_connections"`
|
|
||||||
ConnMaxLifetime time.Duration `yaml:"connection_max_lifetime" json:"connection_max_lifetime" swaggertype:"string" example:"1h"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstancesConfig contains instance management configuration
|
|
||||||
type InstancesConfig struct {
|
|
||||||
// Port range for instances (e.g., 8000,9000)
|
|
||||||
PortRange [2]int `yaml:"port_range" json:"port_range"`
|
|
||||||
|
|
||||||
// Instance config directory override (relative to data_dir if not absolute)
|
|
||||||
InstancesDir string `yaml:"configs_dir" json:"configs_dir"`
|
|
||||||
|
|
||||||
// Logs directory override (relative to data_dir if not absolute)
|
|
||||||
LogsDir string `yaml:"logs_dir" json:"logs_dir"`
|
|
||||||
|
|
||||||
// Automatically create the data directory if it doesn't exist
|
|
||||||
AutoCreateDirs bool `yaml:"auto_create_dirs" json:"auto_create_dirs"`
|
|
||||||
|
|
||||||
// Maximum number of instances that can be created
|
|
||||||
MaxInstances int `yaml:"max_instances" json:"max_instances"`
|
|
||||||
|
|
||||||
// Maximum number of instances that can be running at the same time
|
|
||||||
MaxRunningInstances int `yaml:"max_running_instances,omitempty" json:"max_running_instances,omitempty"`
|
|
||||||
|
|
||||||
// Enable LRU eviction for instance logs
|
|
||||||
EnableLRUEviction bool `yaml:"enable_lru_eviction" json:"enable_lru_eviction"`
|
|
||||||
|
|
||||||
// Default auto-restart setting for new instances
|
|
||||||
DefaultAutoRestart bool `yaml:"default_auto_restart" json:"default_auto_restart"`
|
|
||||||
|
|
||||||
// Default max restarts for new instances
|
|
||||||
DefaultMaxRestarts int `yaml:"default_max_restarts" json:"default_max_restarts"`
|
|
||||||
|
|
||||||
// Default restart delay for new instances (in seconds)
|
|
||||||
DefaultRestartDelay int `yaml:"default_restart_delay" json:"default_restart_delay"`
|
|
||||||
|
|
||||||
// Default on-demand start setting for new instances
|
|
||||||
DefaultOnDemandStart bool `yaml:"default_on_demand_start" json:"default_on_demand_start"`
|
|
||||||
|
|
||||||
// How long to wait for an instance to start on demand (in seconds)
|
|
||||||
OnDemandStartTimeout int `yaml:"on_demand_start_timeout,omitempty" json:"on_demand_start_timeout,omitempty"`
|
|
||||||
|
|
||||||
// Interval for checking instance timeouts (in minutes)
|
|
||||||
TimeoutCheckInterval int `yaml:"timeout_check_interval" json:"timeout_check_interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthConfig contains authentication settings
|
|
||||||
type AuthConfig struct {
|
|
||||||
|
|
||||||
// Require authentication for OpenAI compatible inference endpoints
|
|
||||||
RequireInferenceAuth bool `yaml:"require_inference_auth" json:"require_inference_auth"`
|
|
||||||
|
|
||||||
// List of keys for OpenAI compatible inference endpoints
|
|
||||||
InferenceKeys []string `yaml:"inference_keys" json:"inference_keys"`
|
|
||||||
|
|
||||||
// Require authentication for management endpoints
|
|
||||||
RequireManagementAuth bool `yaml:"require_management_auth" json:"require_management_auth"`
|
|
||||||
|
|
||||||
// List of keys for management endpoints
|
|
||||||
ManagementKeys []string `yaml:"management_keys" json:"management_keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeConfig struct {
|
|
||||||
Address string `yaml:"address" json:"address"`
|
|
||||||
APIKey string `yaml:"api_key,omitempty" json:"api_key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfig loads configuration with the following precedence:
|
// LoadConfig loads configuration with the following precedence:
|
||||||
// 1. Hardcoded defaults
|
// 1. Hardcoded defaults
|
||||||
// 2. Config file
|
// 2. Config file
|
||||||
// 3. Environment variables
|
// 3. Environment variables
|
||||||
func LoadConfig(configPath string) (AppConfig, error) {
|
func LoadConfig(configPath string) (AppConfig, error) {
|
||||||
// 1. Start with defaults
|
// 1. Start with defaults
|
||||||
defaultDataDir := getDefaultDataDirectory()
|
defaultDataDir := getDefaultDataDir()
|
||||||
|
cfg := getDefaultConfig(defaultDataDir)
|
||||||
cfg := AppConfig{
|
|
||||||
Server: ServerConfig{
|
|
||||||
Host: "0.0.0.0",
|
|
||||||
Port: 8080,
|
|
||||||
AllowedOrigins: []string{"*"}, // Default to allow all origins
|
|
||||||
AllowedHeaders: []string{"*"}, // Default to allow all headers
|
|
||||||
EnableSwagger: false,
|
|
||||||
},
|
|
||||||
LocalNode: "main",
|
|
||||||
Nodes: map[string]NodeConfig{},
|
|
||||||
DataDir: defaultDataDir,
|
|
||||||
Backends: BackendConfig{
|
|
||||||
LlamaCpp: BackendSettings{
|
|
||||||
Command: "llama-server",
|
|
||||||
Args: []string{},
|
|
||||||
Environment: map[string]string{},
|
|
||||||
Docker: &DockerSettings{
|
|
||||||
Enabled: false,
|
|
||||||
Image: "ghcr.io/ggml-org/llama.cpp:server",
|
|
||||||
Args: []string{
|
|
||||||
"run", "--rm", "--network", "host", "--gpus", "all",
|
|
||||||
"-v", filepath.Join(defaultDataDir, "llama.cpp") + ":/root/.cache/llama.cpp"},
|
|
||||||
Environment: map[string]string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VLLM: BackendSettings{
|
|
||||||
Command: "vllm",
|
|
||||||
Args: []string{"serve"},
|
|
||||||
Docker: &DockerSettings{
|
|
||||||
Enabled: false,
|
|
||||||
Image: "vllm/vllm-openai:latest",
|
|
||||||
Args: []string{
|
|
||||||
"run", "--rm", "--network", "host", "--gpus", "all", "--shm-size", "1g",
|
|
||||||
"-v", filepath.Join(defaultDataDir, "huggingface") + ":/root/.cache/huggingface",
|
|
||||||
},
|
|
||||||
Environment: map[string]string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MLX: BackendSettings{
|
|
||||||
Command: "mlx_lm.server",
|
|
||||||
Args: []string{},
|
|
||||||
// No Docker section for MLX - not supported
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Instances: InstancesConfig{
|
|
||||||
PortRange: [2]int{8000, 9000},
|
|
||||||
// NOTE: empty strings are set as placeholder values since InstancesDir and LogsDir
|
|
||||||
// should be relative path to DataDir if not explicitly set.
|
|
||||||
InstancesDir: "",
|
|
||||||
LogsDir: "",
|
|
||||||
AutoCreateDirs: true,
|
|
||||||
MaxInstances: -1, // -1 means unlimited
|
|
||||||
MaxRunningInstances: -1, // -1 means unlimited
|
|
||||||
EnableLRUEviction: true,
|
|
||||||
DefaultAutoRestart: true,
|
|
||||||
DefaultMaxRestarts: 3,
|
|
||||||
DefaultRestartDelay: 5,
|
|
||||||
DefaultOnDemandStart: true,
|
|
||||||
OnDemandStartTimeout: 120, // 2 minutes
|
|
||||||
TimeoutCheckInterval: 5, // Check timeouts every 5 minutes
|
|
||||||
},
|
|
||||||
Database: DatabaseConfig{
|
|
||||||
Path: "", // Will be set to data_dir/llamactl.db if empty
|
|
||||||
MaxOpenConnections: 25,
|
|
||||||
MaxIdleConnections: 5,
|
|
||||||
ConnMaxLifetime: 5 * time.Minute,
|
|
||||||
},
|
|
||||||
Auth: AuthConfig{
|
|
||||||
RequireInferenceAuth: true,
|
|
||||||
InferenceKeys: []string{},
|
|
||||||
RequireManagementAuth: true,
|
|
||||||
ManagementKeys: []string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Load from config file
|
// 2. Load from config file
|
||||||
if err := loadConfigFile(&cfg, configPath); err != nil {
|
if err := loadConfigFile(&cfg, configPath); err != nil {
|
||||||
@@ -300,372 +85,6 @@ func loadConfigFile(cfg *AppConfig, configPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadEnvVars overrides config with environment variables
|
|
||||||
func loadEnvVars(cfg *AppConfig) {
|
|
||||||
// Server config
|
|
||||||
if host := os.Getenv("LLAMACTL_HOST"); host != "" {
|
|
||||||
cfg.Server.Host = host
|
|
||||||
}
|
|
||||||
if port := os.Getenv("LLAMACTL_PORT"); port != "" {
|
|
||||||
if p, err := strconv.Atoi(port); err == nil {
|
|
||||||
cfg.Server.Port = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if allowedOrigins := os.Getenv("LLAMACTL_ALLOWED_ORIGINS"); allowedOrigins != "" {
|
|
||||||
cfg.Server.AllowedOrigins = strings.Split(allowedOrigins, ",")
|
|
||||||
}
|
|
||||||
if enableSwagger := os.Getenv("LLAMACTL_ENABLE_SWAGGER"); enableSwagger != "" {
|
|
||||||
if b, err := strconv.ParseBool(enableSwagger); err == nil {
|
|
||||||
cfg.Server.EnableSwagger = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data config
|
|
||||||
if dataDir := os.Getenv("LLAMACTL_DATA_DIRECTORY"); dataDir != "" {
|
|
||||||
cfg.DataDir = dataDir
|
|
||||||
}
|
|
||||||
if instancesDir := os.Getenv("LLAMACTL_INSTANCES_DIR"); instancesDir != "" {
|
|
||||||
cfg.Instances.InstancesDir = instancesDir
|
|
||||||
}
|
|
||||||
if logsDir := os.Getenv("LLAMACTL_LOGS_DIR"); logsDir != "" {
|
|
||||||
cfg.Instances.LogsDir = logsDir
|
|
||||||
}
|
|
||||||
if autoCreate := os.Getenv("LLAMACTL_AUTO_CREATE_DATA_DIR"); autoCreate != "" {
|
|
||||||
if b, err := strconv.ParseBool(autoCreate); err == nil {
|
|
||||||
cfg.Instances.AutoCreateDirs = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance config
|
|
||||||
if portRange := os.Getenv("LLAMACTL_INSTANCE_PORT_RANGE"); portRange != "" {
|
|
||||||
if ports := ParsePortRange(portRange); ports != [2]int{0, 0} {
|
|
||||||
cfg.Instances.PortRange = ports
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxInstances := os.Getenv("LLAMACTL_MAX_INSTANCES"); maxInstances != "" {
|
|
||||||
if m, err := strconv.Atoi(maxInstances); err == nil {
|
|
||||||
cfg.Instances.MaxInstances = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxRunning := os.Getenv("LLAMACTL_MAX_RUNNING_INSTANCES"); maxRunning != "" {
|
|
||||||
if m, err := strconv.Atoi(maxRunning); err == nil {
|
|
||||||
cfg.Instances.MaxRunningInstances = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if enableLRUEviction := os.Getenv("LLAMACTL_ENABLE_LRU_EVICTION"); enableLRUEviction != "" {
|
|
||||||
if b, err := strconv.ParseBool(enableLRUEviction); err == nil {
|
|
||||||
cfg.Instances.EnableLRUEviction = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Backend config
|
|
||||||
// LlamaCpp backend
|
|
||||||
if llamaCmd := os.Getenv("LLAMACTL_LLAMACPP_COMMAND"); llamaCmd != "" {
|
|
||||||
cfg.Backends.LlamaCpp.Command = llamaCmd
|
|
||||||
}
|
|
||||||
if llamaArgs := os.Getenv("LLAMACTL_LLAMACPP_ARGS"); llamaArgs != "" {
|
|
||||||
cfg.Backends.LlamaCpp.Args = strings.Split(llamaArgs, " ")
|
|
||||||
}
|
|
||||||
if llamaEnv := os.Getenv("LLAMACTL_LLAMACPP_ENV"); llamaEnv != "" {
|
|
||||||
if cfg.Backends.LlamaCpp.Environment == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Environment = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseEnvVars(llamaEnv, cfg.Backends.LlamaCpp.Environment)
|
|
||||||
}
|
|
||||||
if llamaDockerEnabled := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ENABLED"); llamaDockerEnabled != "" {
|
|
||||||
if b, err := strconv.ParseBool(llamaDockerEnabled); err == nil {
|
|
||||||
if cfg.Backends.LlamaCpp.Docker == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.LlamaCpp.Docker.Enabled = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if llamaDockerImage := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_IMAGE"); llamaDockerImage != "" {
|
|
||||||
if cfg.Backends.LlamaCpp.Docker == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.LlamaCpp.Docker.Image = llamaDockerImage
|
|
||||||
}
|
|
||||||
if llamaDockerArgs := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ARGS"); llamaDockerArgs != "" {
|
|
||||||
if cfg.Backends.LlamaCpp.Docker == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.LlamaCpp.Docker.Args = strings.Split(llamaDockerArgs, " ")
|
|
||||||
}
|
|
||||||
if llamaDockerEnv := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ENV"); llamaDockerEnv != "" {
|
|
||||||
if cfg.Backends.LlamaCpp.Docker == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
if cfg.Backends.LlamaCpp.Docker.Environment == nil {
|
|
||||||
cfg.Backends.LlamaCpp.Docker.Environment = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseEnvVars(llamaDockerEnv, cfg.Backends.LlamaCpp.Docker.Environment)
|
|
||||||
}
|
|
||||||
if llamaEnv := os.Getenv("LLAMACTL_LLAMACPP_RESPONSE_HEADERS"); llamaEnv != "" {
|
|
||||||
if cfg.Backends.LlamaCpp.ResponseHeaders == nil {
|
|
||||||
cfg.Backends.LlamaCpp.ResponseHeaders = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseHeaders(llamaEnv, cfg.Backends.LlamaCpp.ResponseHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
// vLLM backend
|
|
||||||
if vllmCmd := os.Getenv("LLAMACTL_VLLM_COMMAND"); vllmCmd != "" {
|
|
||||||
cfg.Backends.VLLM.Command = vllmCmd
|
|
||||||
}
|
|
||||||
if vllmArgs := os.Getenv("LLAMACTL_VLLM_ARGS"); vllmArgs != "" {
|
|
||||||
cfg.Backends.VLLM.Args = strings.Split(vllmArgs, " ")
|
|
||||||
}
|
|
||||||
if vllmEnv := os.Getenv("LLAMACTL_VLLM_ENV"); vllmEnv != "" {
|
|
||||||
if cfg.Backends.VLLM.Environment == nil {
|
|
||||||
cfg.Backends.VLLM.Environment = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseEnvVars(vllmEnv, cfg.Backends.VLLM.Environment)
|
|
||||||
}
|
|
||||||
if vllmDockerEnabled := os.Getenv("LLAMACTL_VLLM_DOCKER_ENABLED"); vllmDockerEnabled != "" {
|
|
||||||
if b, err := strconv.ParseBool(vllmDockerEnabled); err == nil {
|
|
||||||
if cfg.Backends.VLLM.Docker == nil {
|
|
||||||
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.VLLM.Docker.Enabled = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vllmDockerImage := os.Getenv("LLAMACTL_VLLM_DOCKER_IMAGE"); vllmDockerImage != "" {
|
|
||||||
if cfg.Backends.VLLM.Docker == nil {
|
|
||||||
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.VLLM.Docker.Image = vllmDockerImage
|
|
||||||
}
|
|
||||||
if vllmDockerArgs := os.Getenv("LLAMACTL_VLLM_DOCKER_ARGS"); vllmDockerArgs != "" {
|
|
||||||
if cfg.Backends.VLLM.Docker == nil {
|
|
||||||
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
cfg.Backends.VLLM.Docker.Args = strings.Split(vllmDockerArgs, " ")
|
|
||||||
}
|
|
||||||
if vllmDockerEnv := os.Getenv("LLAMACTL_VLLM_DOCKER_ENV"); vllmDockerEnv != "" {
|
|
||||||
if cfg.Backends.VLLM.Docker == nil {
|
|
||||||
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
|
||||||
}
|
|
||||||
if cfg.Backends.VLLM.Docker.Environment == nil {
|
|
||||||
cfg.Backends.VLLM.Docker.Environment = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseEnvVars(vllmDockerEnv, cfg.Backends.VLLM.Docker.Environment)
|
|
||||||
}
|
|
||||||
if llamaEnv := os.Getenv("LLAMACTL_VLLM_RESPONSE_HEADERS"); llamaEnv != "" {
|
|
||||||
if cfg.Backends.VLLM.ResponseHeaders == nil {
|
|
||||||
cfg.Backends.VLLM.ResponseHeaders = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseHeaders(llamaEnv, cfg.Backends.VLLM.ResponseHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MLX backend
|
|
||||||
if mlxCmd := os.Getenv("LLAMACTL_MLX_COMMAND"); mlxCmd != "" {
|
|
||||||
cfg.Backends.MLX.Command = mlxCmd
|
|
||||||
}
|
|
||||||
if mlxArgs := os.Getenv("LLAMACTL_MLX_ARGS"); mlxArgs != "" {
|
|
||||||
cfg.Backends.MLX.Args = strings.Split(mlxArgs, " ")
|
|
||||||
}
|
|
||||||
if mlxEnv := os.Getenv("LLAMACTL_MLX_ENV"); mlxEnv != "" {
|
|
||||||
if cfg.Backends.MLX.Environment == nil {
|
|
||||||
cfg.Backends.MLX.Environment = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseEnvVars(mlxEnv, cfg.Backends.MLX.Environment)
|
|
||||||
}
|
|
||||||
if llamaEnv := os.Getenv("LLAMACTL_MLX_RESPONSE_HEADERS"); llamaEnv != "" {
|
|
||||||
if cfg.Backends.MLX.ResponseHeaders == nil {
|
|
||||||
cfg.Backends.MLX.ResponseHeaders = make(map[string]string)
|
|
||||||
}
|
|
||||||
parseHeaders(llamaEnv, cfg.Backends.MLX.ResponseHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance defaults
|
|
||||||
if autoRestart := os.Getenv("LLAMACTL_DEFAULT_AUTO_RESTART"); autoRestart != "" {
|
|
||||||
if b, err := strconv.ParseBool(autoRestart); err == nil {
|
|
||||||
cfg.Instances.DefaultAutoRestart = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxRestarts := os.Getenv("LLAMACTL_DEFAULT_MAX_RESTARTS"); maxRestarts != "" {
|
|
||||||
if m, err := strconv.Atoi(maxRestarts); err == nil {
|
|
||||||
cfg.Instances.DefaultMaxRestarts = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if restartDelay := os.Getenv("LLAMACTL_DEFAULT_RESTART_DELAY"); restartDelay != "" {
|
|
||||||
if seconds, err := strconv.Atoi(restartDelay); err == nil {
|
|
||||||
cfg.Instances.DefaultRestartDelay = seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if onDemandStart := os.Getenv("LLAMACTL_DEFAULT_ON_DEMAND_START"); onDemandStart != "" {
|
|
||||||
if b, err := strconv.ParseBool(onDemandStart); err == nil {
|
|
||||||
cfg.Instances.DefaultOnDemandStart = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if onDemandTimeout := os.Getenv("LLAMACTL_ON_DEMAND_START_TIMEOUT"); onDemandTimeout != "" {
|
|
||||||
if seconds, err := strconv.Atoi(onDemandTimeout); err == nil {
|
|
||||||
cfg.Instances.OnDemandStartTimeout = seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if timeoutCheckInterval := os.Getenv("LLAMACTL_TIMEOUT_CHECK_INTERVAL"); timeoutCheckInterval != "" {
|
|
||||||
if minutes, err := strconv.Atoi(timeoutCheckInterval); err == nil {
|
|
||||||
cfg.Instances.TimeoutCheckInterval = minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Auth config
|
|
||||||
if requireInferenceAuth := os.Getenv("LLAMACTL_REQUIRE_INFERENCE_AUTH"); requireInferenceAuth != "" {
|
|
||||||
if b, err := strconv.ParseBool(requireInferenceAuth); err == nil {
|
|
||||||
cfg.Auth.RequireInferenceAuth = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if inferenceKeys := os.Getenv("LLAMACTL_INFERENCE_KEYS"); inferenceKeys != "" {
|
|
||||||
cfg.Auth.InferenceKeys = strings.Split(inferenceKeys, ",")
|
|
||||||
}
|
|
||||||
if requireManagementAuth := os.Getenv("LLAMACTL_REQUIRE_MANAGEMENT_AUTH"); requireManagementAuth != "" {
|
|
||||||
if b, err := strconv.ParseBool(requireManagementAuth); err == nil {
|
|
||||||
cfg.Auth.RequireManagementAuth = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if managementKeys := os.Getenv("LLAMACTL_MANAGEMENT_KEYS"); managementKeys != "" {
|
|
||||||
cfg.Auth.ManagementKeys = strings.Split(managementKeys, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local node config
|
|
||||||
if localNode := os.Getenv("LLAMACTL_LOCAL_NODE"); localNode != "" {
|
|
||||||
cfg.LocalNode = localNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database config
|
|
||||||
if dbPath := os.Getenv("LLAMACTL_DATABASE_PATH"); dbPath != "" {
|
|
||||||
cfg.Database.Path = dbPath
|
|
||||||
}
|
|
||||||
if maxOpenConns := os.Getenv("LLAMACTL_DATABASE_MAX_OPEN_CONNECTIONS"); maxOpenConns != "" {
|
|
||||||
if m, err := strconv.Atoi(maxOpenConns); err == nil {
|
|
||||||
cfg.Database.MaxOpenConnections = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxIdleConns := os.Getenv("LLAMACTL_DATABASE_MAX_IDLE_CONNECTIONS"); maxIdleConns != "" {
|
|
||||||
if m, err := strconv.Atoi(maxIdleConns); err == nil {
|
|
||||||
cfg.Database.MaxIdleConnections = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if connMaxLifetime := os.Getenv("LLAMACTL_DATABASE_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
|
|
||||||
if d, err := time.ParseDuration(connMaxLifetime); err == nil {
|
|
||||||
cfg.Database.ConnMaxLifetime = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePortRange parses port range from string formats like "8000-9000" or "8000,9000"
|
|
||||||
func ParsePortRange(s string) [2]int {
|
|
||||||
var parts []string
|
|
||||||
|
|
||||||
// Try both separators
|
|
||||||
if strings.Contains(s, "-") {
|
|
||||||
parts = strings.Split(s, "-")
|
|
||||||
} else if strings.Contains(s, ",") {
|
|
||||||
parts = strings.Split(s, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the two parts
|
|
||||||
if len(parts) == 2 {
|
|
||||||
start, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
|
|
||||||
end, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
|
|
||||||
if err1 == nil && err2 == nil {
|
|
||||||
return [2]int{start, end}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [2]int{0, 0} // Invalid format
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseEnvVars parses environment variables in format "KEY1=value1,KEY2=value2"
|
|
||||||
// and populates the provided environment map
|
|
||||||
func parseEnvVars(envString string, envMap map[string]string) {
|
|
||||||
if envString == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, envPair := range strings.Split(envString, ",") {
|
|
||||||
if parts := strings.SplitN(strings.TrimSpace(envPair), "=", 2); len(parts) == 2 {
|
|
||||||
envMap[parts[0]] = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseHeaders parses HTTP headers in format "KEY1=value1;KEY2=value2"
|
|
||||||
// and populates the provided environment map
|
|
||||||
func parseHeaders(envString string, envMap map[string]string) {
|
|
||||||
if envString == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, envPair := range strings.Split(envString, ";") {
|
|
||||||
if parts := strings.SplitN(strings.TrimSpace(envPair), "=", 2); len(parts) == 2 {
|
|
||||||
envMap[parts[0]] = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultDataDirectory returns platform-specific default data directory
|
|
||||||
func getDefaultDataDirectory() string {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
// Try PROGRAMDATA first (system-wide), fallback to LOCALAPPDATA (user)
|
|
||||||
if programData := os.Getenv("PROGRAMDATA"); programData != "" {
|
|
||||||
return filepath.Join(programData, "llamactl")
|
|
||||||
}
|
|
||||||
if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
|
|
||||||
return filepath.Join(localAppData, "llamactl")
|
|
||||||
}
|
|
||||||
return "C:\\ProgramData\\llamactl" // Final fallback
|
|
||||||
|
|
||||||
case "darwin":
|
|
||||||
// For macOS, use user's Application Support directory
|
|
||||||
if homeDir, _ := os.UserHomeDir(); homeDir != "" {
|
|
||||||
return filepath.Join(homeDir, "Library", "Application Support", "llamactl")
|
|
||||||
}
|
|
||||||
return "/usr/local/var/llamactl" // Fallback
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Linux and other Unix-like systems
|
|
||||||
if homeDir, _ := os.UserHomeDir(); homeDir != "" {
|
|
||||||
return filepath.Join(homeDir, ".local", "share", "llamactl")
|
|
||||||
}
|
|
||||||
return "/var/lib/llamactl" // Final fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultConfigLocations returns platform-specific config file locations
|
|
||||||
func getDefaultConfigLocations() []string {
|
|
||||||
var locations []string
|
|
||||||
// Use ./llamactl.yaml and ./config.yaml as the default config file
|
|
||||||
locations = append(locations, "llamactl.yaml")
|
|
||||||
locations = append(locations, "config.yaml")
|
|
||||||
|
|
||||||
homeDir, _ := os.UserHomeDir()
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
// Windows: Use APPDATA if available, else user home, fallback to ProgramData
|
|
||||||
if appData := os.Getenv("APPDATA"); appData != "" {
|
|
||||||
locations = append(locations, filepath.Join(appData, "llamactl", "config.yaml"))
|
|
||||||
} else if homeDir != "" {
|
|
||||||
locations = append(locations, filepath.Join(homeDir, "llamactl", "config.yaml"))
|
|
||||||
}
|
|
||||||
locations = append(locations, filepath.Join(os.Getenv("PROGRAMDATA"), "llamactl", "config.yaml"))
|
|
||||||
|
|
||||||
case "darwin":
|
|
||||||
// macOS: Use Application Support in user home, fallback to /Library/Application Support
|
|
||||||
if homeDir != "" {
|
|
||||||
locations = append(locations, filepath.Join(homeDir, "Library", "Application Support", "llamactl", "config.yaml"))
|
|
||||||
}
|
|
||||||
locations = append(locations, "/Library/Application Support/llamactl/config.yaml")
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Linux/Unix: Use ~/.config/llamactl/config.yaml, fallback to /etc/llamactl/config.yaml
|
|
||||||
if homeDir != "" {
|
|
||||||
locations = append(locations, filepath.Join(homeDir, ".config", "llamactl", "config.yaml"))
|
|
||||||
}
|
|
||||||
locations = append(locations, "/etc/llamactl/config.yaml")
|
|
||||||
}
|
|
||||||
|
|
||||||
return locations
|
|
||||||
}
|
|
||||||
|
|
||||||
// SanitizedCopy returns a copy of the AppConfig with sensitive information removed
|
// SanitizedCopy returns a copy of the AppConfig with sensitive information removed
|
||||||
func (cfg *AppConfig) SanitizedCopy() (AppConfig, error) {
|
func (cfg *AppConfig) SanitizedCopy() (AppConfig, error) {
|
||||||
// Deep copy via JSON marshal/unmarshal to avoid concurrent map access
|
// Deep copy via JSON marshal/unmarshal to avoid concurrent map access
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ server:
|
|||||||
port: 9090
|
port: 9090
|
||||||
instances:
|
instances:
|
||||||
port_range: [7000, 8000]
|
port_range: [7000, 8000]
|
||||||
logs_dir: "/custom/logs"
|
|
||||||
max_instances: 5
|
max_instances: 5
|
||||||
|
logs_dir: "/custom/logs"
|
||||||
llama_executable: "/usr/bin/llama-server"
|
llama_executable: "/usr/bin/llama-server"
|
||||||
default_auto_restart: false
|
default_auto_restart: false
|
||||||
default_max_restarts: 10
|
default_max_restarts: 10
|
||||||
@@ -219,7 +219,6 @@ instances:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestParsePortRange(t *testing.T) {
|
func TestParsePortRange(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -248,7 +247,6 @@ func TestParsePortRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestGetBackendSettings_NewStructuredConfig(t *testing.T) {
|
func TestGetBackendSettings_NewStructuredConfig(t *testing.T) {
|
||||||
bc := &config.BackendConfig{
|
bc := &config.BackendConfig{
|
||||||
LlamaCpp: config.BackendSettings{
|
LlamaCpp: config.BackendSettings{
|
||||||
@@ -305,7 +303,6 @@ func TestGetBackendSettings_NewStructuredConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestLoadConfig_BackendEnvironmentVariables(t *testing.T) {
|
func TestLoadConfig_BackendEnvironmentVariables(t *testing.T) {
|
||||||
// Test that backend environment variables work correctly
|
// Test that backend environment variables work correctly
|
||||||
envVars := map[string]string{
|
envVars := map[string]string{
|
||||||
@@ -375,7 +372,6 @@ func TestLoadConfig_BackendEnvironmentVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestLoadConfig_LocalNode(t *testing.T) {
|
func TestLoadConfig_LocalNode(t *testing.T) {
|
||||||
t.Run("default local node", func(t *testing.T) {
|
t.Run("default local node", func(t *testing.T) {
|
||||||
cfg, err := config.LoadConfig("nonexistent-file.yaml")
|
cfg, err := config.LoadConfig("nonexistent-file.yaml")
|
||||||
|
|||||||
154
pkg/config/defaults.go
Normal file
154
pkg/config/defaults.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDefaultConfig(dataDir string) AppConfig {
|
||||||
|
return AppConfig{
|
||||||
|
Server: ServerConfig{
|
||||||
|
Host: "0.0.0.0",
|
||||||
|
Port: 8080,
|
||||||
|
AllowedOrigins: []string{"*"}, // Default to allow all origins
|
||||||
|
AllowedHeaders: []string{"*"}, // Default to allow all headers
|
||||||
|
EnableSwagger: false,
|
||||||
|
},
|
||||||
|
LocalNode: "main",
|
||||||
|
Nodes: map[string]NodeConfig{},
|
||||||
|
DataDir: dataDir,
|
||||||
|
Backends: BackendConfig{
|
||||||
|
LlamaCpp: BackendSettings{
|
||||||
|
Command: "llama-server",
|
||||||
|
Args: []string{},
|
||||||
|
Environment: map[string]string{},
|
||||||
|
Docker: &DockerSettings{
|
||||||
|
Enabled: false,
|
||||||
|
Image: "ghcr.io/ggml-org/llama.cpp:server",
|
||||||
|
Args: []string{
|
||||||
|
"run", "--rm", "--network", "host", "--gpus", "all",
|
||||||
|
"-v", filepath.Join(dataDir, "llama.cpp") + ":/root/.cache/llama.cpp"},
|
||||||
|
Environment: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VLLM: BackendSettings{
|
||||||
|
Command: "vllm",
|
||||||
|
Args: []string{"serve"},
|
||||||
|
Docker: &DockerSettings{
|
||||||
|
Enabled: false,
|
||||||
|
Image: "vllm/vllm-openai:latest",
|
||||||
|
Args: []string{
|
||||||
|
"run", "--rm", "--network", "host", "--gpus", "all", "--shm-size", "1g",
|
||||||
|
"-v", filepath.Join(dataDir, "huggingface") + ":/root/.cache/huggingface",
|
||||||
|
},
|
||||||
|
Environment: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MLX: BackendSettings{
|
||||||
|
Command: "mlx_lm.server",
|
||||||
|
Args: []string{},
|
||||||
|
// No Docker section for MLX - not supported
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Instances: InstancesConfig{
|
||||||
|
PortRange: [2]int{8000, 9000},
|
||||||
|
// NOTE: empty string is set as placeholder value since InstancesDir
|
||||||
|
// should be relative path to DataDir if not explicitly set.
|
||||||
|
InstancesDir: "",
|
||||||
|
AutoCreateDirs: true,
|
||||||
|
MaxInstances: -1, // -1 means unlimited
|
||||||
|
MaxRunningInstances: -1, // -1 means unlimited
|
||||||
|
EnableLRUEviction: true,
|
||||||
|
DefaultAutoRestart: true,
|
||||||
|
DefaultMaxRestarts: 3,
|
||||||
|
DefaultRestartDelay: 5,
|
||||||
|
DefaultOnDemandStart: true,
|
||||||
|
OnDemandStartTimeout: 120, // 2 minutes
|
||||||
|
TimeoutCheckInterval: 5, // Check timeouts every 5 minutes
|
||||||
|
LogsDir: "", // Will be set to data_dir/logs if empty
|
||||||
|
LogRotationEnabled: true,
|
||||||
|
LogRotationMaxSize: 100,
|
||||||
|
LogRotationCompress: false,
|
||||||
|
},
|
||||||
|
Database: DatabaseConfig{
|
||||||
|
Path: "", // Will be set to data_dir/llamactl.db if empty
|
||||||
|
MaxOpenConnections: 25,
|
||||||
|
MaxIdleConnections: 5,
|
||||||
|
ConnMaxLifetime: 5 * time.Minute,
|
||||||
|
},
|
||||||
|
Auth: AuthConfig{
|
||||||
|
RequireInferenceAuth: true,
|
||||||
|
InferenceKeys: []string{},
|
||||||
|
RequireManagementAuth: true,
|
||||||
|
ManagementKeys: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDefaultDataDir returns platform-specific default data directory
|
||||||
|
func getDefaultDataDir() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
// Try PROGRAMDATA first (system-wide), fallback to LOCALAPPDATA (user)
|
||||||
|
if programData := os.Getenv("PROGRAMDATA"); programData != "" {
|
||||||
|
return filepath.Join(programData, "llamactl")
|
||||||
|
}
|
||||||
|
if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
|
||||||
|
return filepath.Join(localAppData, "llamactl")
|
||||||
|
}
|
||||||
|
return "C:\\ProgramData\\llamactl" // Final fallback
|
||||||
|
|
||||||
|
case "darwin":
|
||||||
|
// For macOS, use user's Application Support directory
|
||||||
|
if homeDir, _ := os.UserHomeDir(); homeDir != "" {
|
||||||
|
return filepath.Join(homeDir, "Library", "Application Support", "llamactl")
|
||||||
|
}
|
||||||
|
return "/usr/local/var/llamactl" // Fallback
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Linux and other Unix-like systems
|
||||||
|
if homeDir, _ := os.UserHomeDir(); homeDir != "" {
|
||||||
|
return filepath.Join(homeDir, ".local", "share", "llamactl")
|
||||||
|
}
|
||||||
|
return "/var/lib/llamactl" // Final fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDefaultConfigLocations returns platform-specific config file locations
|
||||||
|
func getDefaultConfigLocations() []string {
|
||||||
|
var locations []string
|
||||||
|
// Use ./llamactl.yaml and ./config.yaml as the default config file
|
||||||
|
locations = append(locations, "llamactl.yaml")
|
||||||
|
locations = append(locations, "config.yaml")
|
||||||
|
|
||||||
|
homeDir, _ := os.UserHomeDir()
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
// Windows: Use APPDATA if available, else user home, fallback to ProgramData
|
||||||
|
if appData := os.Getenv("APPDATA"); appData != "" {
|
||||||
|
locations = append(locations, filepath.Join(appData, "llamactl", "config.yaml"))
|
||||||
|
} else if homeDir != "" {
|
||||||
|
locations = append(locations, filepath.Join(homeDir, "llamactl", "config.yaml"))
|
||||||
|
}
|
||||||
|
locations = append(locations, filepath.Join(os.Getenv("PROGRAMDATA"), "llamactl", "config.yaml"))
|
||||||
|
|
||||||
|
case "darwin":
|
||||||
|
// macOS: Use Application Support in user home, fallback to /Library/Application Support
|
||||||
|
if homeDir != "" {
|
||||||
|
locations = append(locations, filepath.Join(homeDir, "Library", "Application Support", "llamactl", "config.yaml"))
|
||||||
|
}
|
||||||
|
locations = append(locations, "/Library/Application Support/llamactl/config.yaml")
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Linux/Unix: Use ~/.config/llamactl/config.yaml, fallback to /etc/llamactl/config.yaml
|
||||||
|
if homeDir != "" {
|
||||||
|
locations = append(locations, filepath.Join(homeDir, ".config", "llamactl", "config.yaml"))
|
||||||
|
}
|
||||||
|
locations = append(locations, "/etc/llamactl/config.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations
|
||||||
|
}
|
||||||
325
pkg/config/env.go
Normal file
325
pkg/config/env.go
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadEnvVars overrides config with environment variables
|
||||||
|
func loadEnvVars(cfg *AppConfig) {
|
||||||
|
// Server config
|
||||||
|
if host := os.Getenv("LLAMACTL_HOST"); host != "" {
|
||||||
|
cfg.Server.Host = host
|
||||||
|
}
|
||||||
|
if port := os.Getenv("LLAMACTL_PORT"); port != "" {
|
||||||
|
if p, err := strconv.Atoi(port); err == nil {
|
||||||
|
cfg.Server.Port = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowedOrigins := os.Getenv("LLAMACTL_ALLOWED_ORIGINS"); allowedOrigins != "" {
|
||||||
|
cfg.Server.AllowedOrigins = strings.Split(allowedOrigins, ",")
|
||||||
|
}
|
||||||
|
if enableSwagger := os.Getenv("LLAMACTL_ENABLE_SWAGGER"); enableSwagger != "" {
|
||||||
|
if b, err := strconv.ParseBool(enableSwagger); err == nil {
|
||||||
|
cfg.Server.EnableSwagger = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data config
|
||||||
|
if dataDir := os.Getenv("LLAMACTL_DATA_DIRECTORY"); dataDir != "" {
|
||||||
|
cfg.DataDir = dataDir
|
||||||
|
}
|
||||||
|
if instancesDir := os.Getenv("LLAMACTL_INSTANCES_DIR"); instancesDir != "" {
|
||||||
|
cfg.Instances.InstancesDir = instancesDir
|
||||||
|
}
|
||||||
|
if logsDir := os.Getenv("LLAMACTL_LOGS_DIR"); logsDir != "" {
|
||||||
|
cfg.Instances.LogsDir = logsDir
|
||||||
|
}
|
||||||
|
if autoCreate := os.Getenv("LLAMACTL_AUTO_CREATE_DATA_DIR"); autoCreate != "" {
|
||||||
|
if b, err := strconv.ParseBool(autoCreate); err == nil {
|
||||||
|
cfg.Instances.AutoCreateDirs = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance config
|
||||||
|
if portRange := os.Getenv("LLAMACTL_INSTANCE_PORT_RANGE"); portRange != "" {
|
||||||
|
if ports := ParsePortRange(portRange); ports != [2]int{0, 0} {
|
||||||
|
cfg.Instances.PortRange = ports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxInstances := os.Getenv("LLAMACTL_MAX_INSTANCES"); maxInstances != "" {
|
||||||
|
if m, err := strconv.Atoi(maxInstances); err == nil {
|
||||||
|
cfg.Instances.MaxInstances = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxRunning := os.Getenv("LLAMACTL_MAX_RUNNING_INSTANCES"); maxRunning != "" {
|
||||||
|
if m, err := strconv.Atoi(maxRunning); err == nil {
|
||||||
|
cfg.Instances.MaxRunningInstances = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if enableLRUEviction := os.Getenv("LLAMACTL_ENABLE_LRU_EVICTION"); enableLRUEviction != "" {
|
||||||
|
if b, err := strconv.ParseBool(enableLRUEviction); err == nil {
|
||||||
|
cfg.Instances.EnableLRUEviction = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Backend config
|
||||||
|
// LlamaCpp backend
|
||||||
|
if llamaCmd := os.Getenv("LLAMACTL_LLAMACPP_COMMAND"); llamaCmd != "" {
|
||||||
|
cfg.Backends.LlamaCpp.Command = llamaCmd
|
||||||
|
}
|
||||||
|
if llamaArgs := os.Getenv("LLAMACTL_LLAMACPP_ARGS"); llamaArgs != "" {
|
||||||
|
cfg.Backends.LlamaCpp.Args = strings.Split(llamaArgs, " ")
|
||||||
|
}
|
||||||
|
if llamaEnv := os.Getenv("LLAMACTL_LLAMACPP_ENV"); llamaEnv != "" {
|
||||||
|
if cfg.Backends.LlamaCpp.Environment == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Environment = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseEnvVars(llamaEnv, cfg.Backends.LlamaCpp.Environment)
|
||||||
|
}
|
||||||
|
if llamaDockerEnabled := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ENABLED"); llamaDockerEnabled != "" {
|
||||||
|
if b, err := strconv.ParseBool(llamaDockerEnabled); err == nil {
|
||||||
|
if cfg.Backends.LlamaCpp.Docker == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.LlamaCpp.Docker.Enabled = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if llamaDockerImage := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_IMAGE"); llamaDockerImage != "" {
|
||||||
|
if cfg.Backends.LlamaCpp.Docker == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.LlamaCpp.Docker.Image = llamaDockerImage
|
||||||
|
}
|
||||||
|
if llamaDockerArgs := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ARGS"); llamaDockerArgs != "" {
|
||||||
|
if cfg.Backends.LlamaCpp.Docker == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.LlamaCpp.Docker.Args = strings.Split(llamaDockerArgs, " ")
|
||||||
|
}
|
||||||
|
if llamaDockerEnv := os.Getenv("LLAMACTL_LLAMACPP_DOCKER_ENV"); llamaDockerEnv != "" {
|
||||||
|
if cfg.Backends.LlamaCpp.Docker == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
if cfg.Backends.LlamaCpp.Docker.Environment == nil {
|
||||||
|
cfg.Backends.LlamaCpp.Docker.Environment = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseEnvVars(llamaDockerEnv, cfg.Backends.LlamaCpp.Docker.Environment)
|
||||||
|
}
|
||||||
|
if llamaEnv := os.Getenv("LLAMACTL_LLAMACPP_RESPONSE_HEADERS"); llamaEnv != "" {
|
||||||
|
if cfg.Backends.LlamaCpp.ResponseHeaders == nil {
|
||||||
|
cfg.Backends.LlamaCpp.ResponseHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseHeaders(llamaEnv, cfg.Backends.LlamaCpp.ResponseHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// vLLM backend
|
||||||
|
if vllmCmd := os.Getenv("LLAMACTL_VLLM_COMMAND"); vllmCmd != "" {
|
||||||
|
cfg.Backends.VLLM.Command = vllmCmd
|
||||||
|
}
|
||||||
|
if vllmArgs := os.Getenv("LLAMACTL_VLLM_ARGS"); vllmArgs != "" {
|
||||||
|
cfg.Backends.VLLM.Args = strings.Split(vllmArgs, " ")
|
||||||
|
}
|
||||||
|
if vllmEnv := os.Getenv("LLAMACTL_VLLM_ENV"); vllmEnv != "" {
|
||||||
|
if cfg.Backends.VLLM.Environment == nil {
|
||||||
|
cfg.Backends.VLLM.Environment = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseEnvVars(vllmEnv, cfg.Backends.VLLM.Environment)
|
||||||
|
}
|
||||||
|
if vllmDockerEnabled := os.Getenv("LLAMACTL_VLLM_DOCKER_ENABLED"); vllmDockerEnabled != "" {
|
||||||
|
if b, err := strconv.ParseBool(vllmDockerEnabled); err == nil {
|
||||||
|
if cfg.Backends.VLLM.Docker == nil {
|
||||||
|
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.VLLM.Docker.Enabled = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vllmDockerImage := os.Getenv("LLAMACTL_VLLM_DOCKER_IMAGE"); vllmDockerImage != "" {
|
||||||
|
if cfg.Backends.VLLM.Docker == nil {
|
||||||
|
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.VLLM.Docker.Image = vllmDockerImage
|
||||||
|
}
|
||||||
|
if vllmDockerArgs := os.Getenv("LLAMACTL_VLLM_DOCKER_ARGS"); vllmDockerArgs != "" {
|
||||||
|
if cfg.Backends.VLLM.Docker == nil {
|
||||||
|
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
cfg.Backends.VLLM.Docker.Args = strings.Split(vllmDockerArgs, " ")
|
||||||
|
}
|
||||||
|
if vllmDockerEnv := os.Getenv("LLAMACTL_VLLM_DOCKER_ENV"); vllmDockerEnv != "" {
|
||||||
|
if cfg.Backends.VLLM.Docker == nil {
|
||||||
|
cfg.Backends.VLLM.Docker = &DockerSettings{}
|
||||||
|
}
|
||||||
|
if cfg.Backends.VLLM.Docker.Environment == nil {
|
||||||
|
cfg.Backends.VLLM.Docker.Environment = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseEnvVars(vllmDockerEnv, cfg.Backends.VLLM.Docker.Environment)
|
||||||
|
}
|
||||||
|
if llamaEnv := os.Getenv("LLAMACTL_VLLM_RESPONSE_HEADERS"); llamaEnv != "" {
|
||||||
|
if cfg.Backends.VLLM.ResponseHeaders == nil {
|
||||||
|
cfg.Backends.VLLM.ResponseHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseHeaders(llamaEnv, cfg.Backends.VLLM.ResponseHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MLX backend
|
||||||
|
if mlxCmd := os.Getenv("LLAMACTL_MLX_COMMAND"); mlxCmd != "" {
|
||||||
|
cfg.Backends.MLX.Command = mlxCmd
|
||||||
|
}
|
||||||
|
if mlxArgs := os.Getenv("LLAMACTL_MLX_ARGS"); mlxArgs != "" {
|
||||||
|
cfg.Backends.MLX.Args = strings.Split(mlxArgs, " ")
|
||||||
|
}
|
||||||
|
if mlxEnv := os.Getenv("LLAMACTL_MLX_ENV"); mlxEnv != "" {
|
||||||
|
if cfg.Backends.MLX.Environment == nil {
|
||||||
|
cfg.Backends.MLX.Environment = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseEnvVars(mlxEnv, cfg.Backends.MLX.Environment)
|
||||||
|
}
|
||||||
|
if llamaEnv := os.Getenv("LLAMACTL_MLX_RESPONSE_HEADERS"); llamaEnv != "" {
|
||||||
|
if cfg.Backends.MLX.ResponseHeaders == nil {
|
||||||
|
cfg.Backends.MLX.ResponseHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
parseHeaders(llamaEnv, cfg.Backends.MLX.ResponseHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance defaults
|
||||||
|
if autoRestart := os.Getenv("LLAMACTL_DEFAULT_AUTO_RESTART"); autoRestart != "" {
|
||||||
|
if b, err := strconv.ParseBool(autoRestart); err == nil {
|
||||||
|
cfg.Instances.DefaultAutoRestart = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxRestarts := os.Getenv("LLAMACTL_DEFAULT_MAX_RESTARTS"); maxRestarts != "" {
|
||||||
|
if m, err := strconv.Atoi(maxRestarts); err == nil {
|
||||||
|
cfg.Instances.DefaultMaxRestarts = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if restartDelay := os.Getenv("LLAMACTL_DEFAULT_RESTART_DELAY"); restartDelay != "" {
|
||||||
|
if seconds, err := strconv.Atoi(restartDelay); err == nil {
|
||||||
|
cfg.Instances.DefaultRestartDelay = seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if onDemandStart := os.Getenv("LLAMACTL_DEFAULT_ON_DEMAND_START"); onDemandStart != "" {
|
||||||
|
if b, err := strconv.ParseBool(onDemandStart); err == nil {
|
||||||
|
cfg.Instances.DefaultOnDemandStart = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if onDemandTimeout := os.Getenv("LLAMACTL_ON_DEMAND_START_TIMEOUT"); onDemandTimeout != "" {
|
||||||
|
if seconds, err := strconv.Atoi(onDemandTimeout); err == nil {
|
||||||
|
cfg.Instances.OnDemandStartTimeout = seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timeoutCheckInterval := os.Getenv("LLAMACTL_TIMEOUT_CHECK_INTERVAL"); timeoutCheckInterval != "" {
|
||||||
|
if minutes, err := strconv.Atoi(timeoutCheckInterval); err == nil {
|
||||||
|
cfg.Instances.TimeoutCheckInterval = minutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Auth config
|
||||||
|
if requireInferenceAuth := os.Getenv("LLAMACTL_REQUIRE_INFERENCE_AUTH"); requireInferenceAuth != "" {
|
||||||
|
if b, err := strconv.ParseBool(requireInferenceAuth); err == nil {
|
||||||
|
cfg.Auth.RequireInferenceAuth = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inferenceKeys := os.Getenv("LLAMACTL_INFERENCE_KEYS"); inferenceKeys != "" {
|
||||||
|
cfg.Auth.InferenceKeys = strings.Split(inferenceKeys, ",")
|
||||||
|
}
|
||||||
|
if requireManagementAuth := os.Getenv("LLAMACTL_REQUIRE_MANAGEMENT_AUTH"); requireManagementAuth != "" {
|
||||||
|
if b, err := strconv.ParseBool(requireManagementAuth); err == nil {
|
||||||
|
cfg.Auth.RequireManagementAuth = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if managementKeys := os.Getenv("LLAMACTL_MANAGEMENT_KEYS"); managementKeys != "" {
|
||||||
|
cfg.Auth.ManagementKeys = strings.Split(managementKeys, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local node config
|
||||||
|
if localNode := os.Getenv("LLAMACTL_LOCAL_NODE"); localNode != "" {
|
||||||
|
cfg.LocalNode = localNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database config
|
||||||
|
if dbPath := os.Getenv("LLAMACTL_DATABASE_PATH"); dbPath != "" {
|
||||||
|
cfg.Database.Path = dbPath
|
||||||
|
}
|
||||||
|
if maxOpenConns := os.Getenv("LLAMACTL_DATABASE_MAX_OPEN_CONNECTIONS"); maxOpenConns != "" {
|
||||||
|
if m, err := strconv.Atoi(maxOpenConns); err == nil {
|
||||||
|
cfg.Database.MaxOpenConnections = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxIdleConns := os.Getenv("LLAMACTL_DATABASE_MAX_IDLE_CONNECTIONS"); maxIdleConns != "" {
|
||||||
|
if m, err := strconv.Atoi(maxIdleConns); err == nil {
|
||||||
|
cfg.Database.MaxIdleConnections = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if connMaxLifetime := os.Getenv("LLAMACTL_DATABASE_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
|
||||||
|
if d, err := time.ParseDuration(connMaxLifetime); err == nil {
|
||||||
|
cfg.Database.ConnMaxLifetime = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log rotation config
|
||||||
|
if logRotationEnabled := os.Getenv("LLAMACTL_LOG_ROTATION_ENABLED"); logRotationEnabled != "" {
|
||||||
|
if b, err := strconv.ParseBool(logRotationEnabled); err == nil {
|
||||||
|
cfg.Instances.LogRotationEnabled = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logRotationMaxSize := os.Getenv("LLAMACTL_LOG_ROTATION_MAX_SIZE"); logRotationMaxSize != "" {
|
||||||
|
if m, err := strconv.Atoi(logRotationMaxSize); err == nil {
|
||||||
|
cfg.Instances.LogRotationMaxSize = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logRotationCompress := os.Getenv("LLAMACTL_LOG_ROTATION_COMPRESS"); logRotationCompress != "" {
|
||||||
|
if b, err := strconv.ParseBool(logRotationCompress); err == nil {
|
||||||
|
cfg.Instances.LogRotationCompress = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePortRange parses port range from string formats like "8000-9000" or "8000,9000"
|
||||||
|
func ParsePortRange(s string) [2]int {
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
// Try both separators
|
||||||
|
if strings.Contains(s, "-") {
|
||||||
|
parts = strings.Split(s, "-")
|
||||||
|
} else if strings.Contains(s, ",") {
|
||||||
|
parts = strings.Split(s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the two parts
|
||||||
|
if len(parts) == 2 {
|
||||||
|
start, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||||
|
end, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||||
|
if err1 == nil && err2 == nil {
|
||||||
|
return [2]int{start, end}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [2]int{0, 0} // Invalid format
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEnvVars parses environment variables in format "KEY1=value1,KEY2=value2"
|
||||||
|
// and populates the provided environment map
|
||||||
|
func parseEnvVars(envString string, envMap map[string]string) {
|
||||||
|
if envString == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, envPair := range strings.Split(envString, ",") {
|
||||||
|
if parts := strings.SplitN(strings.TrimSpace(envPair), "=", 2); len(parts) == 2 {
|
||||||
|
envMap[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHeaders parses HTTP headers in format "KEY1=value1;KEY2=value2"
|
||||||
|
// and populates the provided environment map
|
||||||
|
func parseHeaders(envString string, envMap map[string]string) {
|
||||||
|
if envString == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, envPair := range strings.Split(envString, ";") {
|
||||||
|
if parts := strings.SplitN(strings.TrimSpace(envPair), "=", 2); len(parts) == 2 {
|
||||||
|
envMap[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
pkg/config/types.go
Normal file
149
pkg/config/types.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// BackendSettings contains structured backend configuration
|
||||||
|
type BackendSettings struct {
|
||||||
|
Command string `yaml:"command" json:"command"`
|
||||||
|
Args []string `yaml:"args" json:"args"`
|
||||||
|
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||||
|
Docker *DockerSettings `yaml:"docker,omitempty" json:"docker,omitempty"`
|
||||||
|
ResponseHeaders map[string]string `yaml:"response_headers,omitempty" json:"response_headers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerSettings contains Docker-specific configuration
|
||||||
|
type DockerSettings struct {
|
||||||
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||||
|
Image string `yaml:"image" json:"image"`
|
||||||
|
Args []string `yaml:"args" json:"args"`
|
||||||
|
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackendConfig contains backend executable configurations
|
||||||
|
type BackendConfig struct {
|
||||||
|
LlamaCpp BackendSettings `yaml:"llama-cpp" json:"llama-cpp"`
|
||||||
|
VLLM BackendSettings `yaml:"vllm" json:"vllm"`
|
||||||
|
MLX BackendSettings `yaml:"mlx" json:"mlx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppConfig represents the configuration for llamactl
|
||||||
|
type AppConfig struct {
|
||||||
|
Server ServerConfig `yaml:"server" json:"server"`
|
||||||
|
Backends BackendConfig `yaml:"backends" json:"backends"`
|
||||||
|
Instances InstancesConfig `yaml:"instances" json:"instances"`
|
||||||
|
Database DatabaseConfig `yaml:"database" json:"database"`
|
||||||
|
Auth AuthConfig `yaml:"auth" json:"auth"`
|
||||||
|
LocalNode string `yaml:"local_node,omitempty" json:"local_node,omitempty"`
|
||||||
|
Nodes map[string]NodeConfig `yaml:"nodes,omitempty" json:"nodes,omitempty"`
|
||||||
|
|
||||||
|
// Directory where all llamactl data will be stored (database, instances, logs, etc.)
|
||||||
|
DataDir string `yaml:"data_dir" json:"data_dir"`
|
||||||
|
|
||||||
|
Version string `yaml:"-" json:"version"`
|
||||||
|
CommitHash string `yaml:"-" json:"commit_hash"`
|
||||||
|
BuildTime string `yaml:"-" json:"build_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerConfig contains HTTP server configuration
|
||||||
|
type ServerConfig struct {
|
||||||
|
// Server host to bind to
|
||||||
|
Host string `yaml:"host" json:"host"`
|
||||||
|
|
||||||
|
// Server port to bind to
|
||||||
|
Port int `yaml:"port" json:"port"`
|
||||||
|
|
||||||
|
// Allowed origins for CORS (e.g., "http://localhost:3000")
|
||||||
|
AllowedOrigins []string `yaml:"allowed_origins" json:"allowed_origins"`
|
||||||
|
|
||||||
|
// Allowed headers for CORS (e.g., "Accept", "Authorization", "Content-Type", "X-CSRF-Token")
|
||||||
|
AllowedHeaders []string `yaml:"allowed_headers" json:"allowed_headers"`
|
||||||
|
|
||||||
|
// Enable Swagger UI for API documentation
|
||||||
|
EnableSwagger bool `yaml:"enable_swagger" json:"enable_swagger"`
|
||||||
|
|
||||||
|
// Response headers to send with responses
|
||||||
|
ResponseHeaders map[string]string `yaml:"response_headers,omitempty" json:"response_headers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseConfig contains database configuration settings
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
// Database file path (relative to the top-level data_dir or absolute)
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
|
||||||
|
// Connection settings
|
||||||
|
MaxOpenConnections int `yaml:"max_open_connections" json:"max_open_connections"`
|
||||||
|
MaxIdleConnections int `yaml:"max_idle_connections" json:"max_idle_connections"`
|
||||||
|
ConnMaxLifetime time.Duration `yaml:"connection_max_lifetime" json:"connection_max_lifetime" swaggertype:"string" example:"1h"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstancesConfig contains instance management configuration
|
||||||
|
type InstancesConfig struct {
|
||||||
|
// Port range for instances (e.g., 8000,9000)
|
||||||
|
PortRange [2]int `yaml:"port_range" json:"port_range"`
|
||||||
|
|
||||||
|
// Instance config directory override (relative to data_dir if not absolute)
|
||||||
|
InstancesDir string `yaml:"configs_dir" json:"configs_dir"`
|
||||||
|
|
||||||
|
// Automatically create the data directory if it doesn't exist
|
||||||
|
AutoCreateDirs bool `yaml:"auto_create_dirs" json:"auto_create_dirs"`
|
||||||
|
|
||||||
|
// Maximum number of instances that can be created
|
||||||
|
MaxInstances int `yaml:"max_instances" json:"max_instances"`
|
||||||
|
|
||||||
|
// Maximum number of instances that can be running at the same time
|
||||||
|
MaxRunningInstances int `yaml:"max_running_instances,omitempty" json:"max_running_instances,omitempty"`
|
||||||
|
|
||||||
|
// Enable LRU eviction for instance logs
|
||||||
|
EnableLRUEviction bool `yaml:"enable_lru_eviction" json:"enable_lru_eviction"`
|
||||||
|
|
||||||
|
// Default auto-restart setting for new instances
|
||||||
|
DefaultAutoRestart bool `yaml:"default_auto_restart" json:"default_auto_restart"`
|
||||||
|
|
||||||
|
// Default max restarts for new instances
|
||||||
|
DefaultMaxRestarts int `yaml:"default_max_restarts" json:"default_max_restarts"`
|
||||||
|
|
||||||
|
// Default restart delay for new instances (in seconds)
|
||||||
|
DefaultRestartDelay int `yaml:"default_restart_delay" json:"default_restart_delay"`
|
||||||
|
|
||||||
|
// Default on-demand start setting for new instances
|
||||||
|
DefaultOnDemandStart bool `yaml:"default_on_demand_start" json:"default_on_demand_start"`
|
||||||
|
|
||||||
|
// How long to wait for an instance to start on demand (in seconds)
|
||||||
|
OnDemandStartTimeout int `yaml:"on_demand_start_timeout,omitempty" json:"on_demand_start_timeout,omitempty"`
|
||||||
|
|
||||||
|
// Interval for checking instance timeouts (in minutes)
|
||||||
|
TimeoutCheckInterval int `yaml:"timeout_check_interval" json:"timeout_check_interval"`
|
||||||
|
|
||||||
|
// Logs directory override (relative to data_dir if not absolute)
|
||||||
|
LogsDir string `yaml:"logs_dir" json:"logs_dir"`
|
||||||
|
|
||||||
|
// Log rotation enabled
|
||||||
|
LogRotationEnabled bool `yaml:"log_rotation_enabled" default:"true"`
|
||||||
|
|
||||||
|
// Maximum log file size in MB before rotation
|
||||||
|
LogRotationMaxSize int `yaml:"log_rotation_max_size" default:"100"`
|
||||||
|
|
||||||
|
// Whether to compress rotated log files
|
||||||
|
LogRotationCompress bool `yaml:"log_rotation_compress" default:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthConfig contains authentication settings
|
||||||
|
type AuthConfig struct {
|
||||||
|
|
||||||
|
// Require authentication for OpenAI compatible inference endpoints
|
||||||
|
RequireInferenceAuth bool `yaml:"require_inference_auth" json:"require_inference_auth"`
|
||||||
|
|
||||||
|
// List of keys for OpenAI compatible inference endpoints
|
||||||
|
InferenceKeys []string `yaml:"inference_keys" json:"inference_keys"`
|
||||||
|
|
||||||
|
// Require authentication for management endpoints
|
||||||
|
RequireManagementAuth bool `yaml:"require_management_auth" json:"require_management_auth"`
|
||||||
|
|
||||||
|
// List of keys for management endpoints
|
||||||
|
ManagementKeys []string `yaml:"management_keys" json:"management_keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeConfig struct {
|
||||||
|
Address string `yaml:"address" json:"address"`
|
||||||
|
APIKey string `yaml:"api_key,omitempty" json:"api_key,omitempty"`
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ package instance
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"llamactl/pkg/config"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"llamactl/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Instance represents a running instance of llama server
|
// Instance represents a running instance of llama server
|
||||||
@@ -68,7 +69,16 @@ func New(name string, globalConfig *config.AppConfig, opts *Options, onStatusCha
|
|||||||
|
|
||||||
// Only create logger, proxy, and process for local instances
|
// Only create logger, proxy, and process for local instances
|
||||||
if !instance.IsRemote() {
|
if !instance.IsRemote() {
|
||||||
instance.logger = newLogger(name, globalInstanceSettings.LogsDir)
|
logRotationConfig := &LogRotationConfig{
|
||||||
|
Enabled: globalInstanceSettings.LogRotationEnabled,
|
||||||
|
MaxSize: globalInstanceSettings.LogRotationMaxSize,
|
||||||
|
Compress: globalInstanceSettings.LogRotationCompress,
|
||||||
|
}
|
||||||
|
instance.logger = newLogger(
|
||||||
|
name,
|
||||||
|
globalInstanceSettings.LogsDir,
|
||||||
|
logRotationConfig,
|
||||||
|
)
|
||||||
instance.process = newProcess(instance)
|
instance.process = newProcess(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ func TestNewInstance(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Instances: config.InstancesConfig{
|
Instances: config.InstancesConfig{
|
||||||
LogsDir: "/tmp/test",
|
|
||||||
DefaultAutoRestart: true,
|
DefaultAutoRestart: true,
|
||||||
|
LogsDir: "/tmp/test",
|
||||||
DefaultMaxRestarts: 3,
|
DefaultMaxRestarts: 3,
|
||||||
DefaultRestartDelay: 5,
|
DefaultRestartDelay: 5,
|
||||||
},
|
},
|
||||||
@@ -120,8 +120,8 @@ func TestSetOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Instances: config.InstancesConfig{
|
Instances: config.InstancesConfig{
|
||||||
LogsDir: "/tmp/test",
|
|
||||||
DefaultAutoRestart: true,
|
DefaultAutoRestart: true,
|
||||||
|
LogsDir: "/tmp/test",
|
||||||
DefaultMaxRestarts: 3,
|
DefaultMaxRestarts: 3,
|
||||||
DefaultRestartDelay: 5,
|
DefaultRestartDelay: 5,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,66 +7,117 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
timber "github.com/DeRuina/timberjack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LogRotationConfig contains log rotation settings for instances
|
||||||
|
type LogRotationConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
MaxSize int
|
||||||
|
Compress bool
|
||||||
|
}
|
||||||
|
|
||||||
type logger struct {
|
type logger struct {
|
||||||
name string
|
name string
|
||||||
logDir string
|
logDir string
|
||||||
logFile atomic.Pointer[os.File]
|
logFile *timber.Logger
|
||||||
logFilePath string
|
logFilePath string
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
cfg *LogRotationConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLogger(name string, logDir string) *logger {
|
func newLogger(name, logDir string, cfg *LogRotationConfig) *logger {
|
||||||
return &logger{
|
return &logger{
|
||||||
name: name,
|
name: name,
|
||||||
logDir: logDir,
|
logDir: logDir,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates and opens the log files for stdout and stderr
|
func (l *logger) create() error {
|
||||||
func (i *logger) create() error {
|
l.mu.Lock()
|
||||||
i.mu.Lock()
|
defer l.mu.Unlock()
|
||||||
defer i.mu.Unlock()
|
|
||||||
|
|
||||||
if i.logDir == "" {
|
if l.logDir == "" {
|
||||||
return fmt.Errorf("logDir is empty for instance %s", i.name)
|
return fmt.Errorf("logDir empty for instance %s", l.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up instance logs
|
if err := os.MkdirAll(l.logDir, 0755); err != nil {
|
||||||
logPath := i.logDir + "/" + i.name + ".log"
|
|
||||||
|
|
||||||
i.logFilePath = logPath
|
|
||||||
if err := os.MkdirAll(i.logDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create log directory: %w", err)
|
return fmt.Errorf("failed to create log directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
logPath := fmt.Sprintf("%s/%s.log", l.logDir, l.name)
|
||||||
if err != nil {
|
l.logFilePath = logPath
|
||||||
return fmt.Errorf("failed to create stdout log file: %w", err)
|
|
||||||
|
// Build the timber logger
|
||||||
|
t := &timber.Logger{
|
||||||
|
Filename: logPath,
|
||||||
|
MaxSize: l.cfg.MaxSize,
|
||||||
|
MaxBackups: 0, // No limit on backups
|
||||||
|
// Compression: "gzip" if Compress is true, else "none"
|
||||||
|
Compression: func() string {
|
||||||
|
if l.cfg.Compress {
|
||||||
|
return "gzip"
|
||||||
|
}
|
||||||
|
return "none"
|
||||||
|
}(),
|
||||||
|
FileMode: 0644,
|
||||||
|
LocalTime: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
i.logFile.Store(logFile)
|
// If rotation is disabled, set MaxSize to 0 so no rotation occurs
|
||||||
|
if !l.cfg.Enabled {
|
||||||
|
t.MaxSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Write a startup marker to both files
|
l.logFile = t
|
||||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
fmt.Fprintf(logFile, "\n=== Instance %s started at %s ===\n", i.name, timestamp)
|
// Write a startup marker
|
||||||
|
ts := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
fmt.Fprintf(t, "\n=== Instance %s started at %s ===\n", l.name, ts)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLogs retrieves the last n lines of logs from the instance
|
func (l *logger) readOutput(rc io.ReadCloser) {
|
||||||
func (i *logger) getLogs(num_lines int) (string, error) {
|
defer rc.Close()
|
||||||
i.mu.RLock()
|
scanner := bufio.NewScanner(rc)
|
||||||
defer i.mu.RUnlock()
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if lg := l.logFile; lg != nil {
|
||||||
|
fmt.Fprintln(lg, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if i.logFilePath == "" {
|
func (l *logger) close() {
|
||||||
return "", fmt.Errorf("log file not created for instance %s", i.name)
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
lg := l.logFile
|
||||||
|
if lg == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(i.logFilePath)
|
ts := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
fmt.Fprintf(lg, "=== Instance %s stopped at %s ===\n\n", l.name, ts)
|
||||||
|
|
||||||
|
_ = lg.Close()
|
||||||
|
l.logFile = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLogs retrieves the last n lines of logs from the instance
|
||||||
|
func (l *logger) getLogs(num_lines int) (string, error) {
|
||||||
|
l.mu.RLock()
|
||||||
|
defer l.mu.RUnlock()
|
||||||
|
|
||||||
|
if l.logFilePath == "" {
|
||||||
|
return "", fmt.Errorf("log file not created for instance %s", l.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(l.logFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to open log file: %w", err)
|
return "", fmt.Errorf("failed to open log file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -97,31 +148,3 @@ func (i *logger) getLogs(num_lines int) (string, error) {
|
|||||||
|
|
||||||
return strings.Join(lines[start:], "\n"), nil
|
return strings.Join(lines[start:], "\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// close closes the log files
|
|
||||||
func (i *logger) close() {
|
|
||||||
i.mu.Lock()
|
|
||||||
defer i.mu.Unlock()
|
|
||||||
|
|
||||||
logFile := i.logFile.Swap(nil)
|
|
||||||
if logFile != nil {
|
|
||||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
fmt.Fprintf(logFile, "=== Instance %s stopped at %s ===\n\n", i.name, timestamp)
|
|
||||||
logFile.Sync() // Ensure all buffered data is written to disk
|
|
||||||
logFile.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readOutput reads from the given reader and writes lines to the log file
|
|
||||||
func (i *logger) readOutput(reader io.ReadCloser) {
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
// Use atomic load to avoid lock contention on every line
|
|
||||||
if logFile := i.logFile.Load(); logFile != nil {
|
|
||||||
fmt.Fprintln(logFile, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -203,11 +203,11 @@ func createTestAppConfig(instancesDir string) *config.AppConfig {
|
|||||||
Instances: config.InstancesConfig{
|
Instances: config.InstancesConfig{
|
||||||
PortRange: [2]int{8000, 9000},
|
PortRange: [2]int{8000, 9000},
|
||||||
InstancesDir: instancesDir,
|
InstancesDir: instancesDir,
|
||||||
LogsDir: instancesDir,
|
|
||||||
MaxInstances: 10,
|
MaxInstances: 10,
|
||||||
MaxRunningInstances: 10,
|
MaxRunningInstances: 10,
|
||||||
DefaultAutoRestart: true,
|
DefaultAutoRestart: true,
|
||||||
DefaultMaxRestarts: 3,
|
DefaultMaxRestarts: 3,
|
||||||
|
LogsDir: instancesDir,
|
||||||
DefaultRestartDelay: 5,
|
DefaultRestartDelay: 5,
|
||||||
TimeoutCheckInterval: 5,
|
TimeoutCheckInterval: 5,
|
||||||
},
|
},
|
||||||
|
|||||||
452
webui/package-lock.json
generated
452
webui/package-lock.json
generated
@@ -18,20 +18,20 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.556.0",
|
"lucide-react": "^0.561.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^25.0.2",
|
||||||
"@types/react": "^19.2.4",
|
"@types/react": "^19.2.4",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
@@ -40,18 +40,18 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"jsdom": "^27.2.0",
|
"jsdom": "^27.3.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.48.0",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.3.0",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@acemir/cssom": {
|
"node_modules/@acemir/cssom": {
|
||||||
"version": "0.9.23",
|
"version": "0.9.28",
|
||||||
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz",
|
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.28.tgz",
|
||||||
"integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==",
|
"integrity": "sha512-LuS6IVEivI75vKN8S04qRD+YySP0RmU/cV8UNukhQZvprxF+76Z43TNo/a08eCodaGhT1Us8etqS1ZRY9/Or0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/css-color": {
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
"version": "4.0.5",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz",
|
||||||
"integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
|
"integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -86,23 +86,23 @@
|
|||||||
"@csstools/css-color-parser": "^3.1.0",
|
"@csstools/css-color-parser": "^3.1.0",
|
||||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
"@csstools/css-tokenizer": "^3.0.4",
|
"@csstools/css-tokenizer": "^3.0.4",
|
||||||
"lru-cache": "^11.2.1"
|
"lru-cache": "^11.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
|
||||||
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "6.7.4",
|
"version": "6.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz",
|
||||||
"integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
|
"integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -110,15 +110,15 @@
|
|||||||
"bidi-js": "^1.0.3",
|
"bidi-js": "^1.0.3",
|
||||||
"css-tree": "^3.1.0",
|
"css-tree": "^3.1.0",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"lru-cache": "^11.2.2"
|
"lru-cache": "^11.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
|
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
|
||||||
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
}
|
}
|
||||||
@@ -520,9 +520,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||||
"version": "1.0.16",
|
"version": "1.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
|
||||||
"integrity": "sha512-2SpS4/UaWQaGpBINyG5ZuCHnUDeVByOhvbkARwfmnfxDvTaj80yOI1cD8Tw93ICV5Fx4fnyDKWQZI1CDtcWyUg==",
|
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -537,6 +537,9 @@
|
|||||||
"license": "MIT-0",
|
"license": "MIT-0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/css-tokenizer": {
|
"node_modules/@csstools/css-tokenizer": {
|
||||||
@@ -561,9 +564,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
|
||||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -577,9 +580,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
|
||||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -593,9 +596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -609,9 +612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -625,9 +628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
"integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -641,9 +644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -657,9 +660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -673,9 +676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -689,9 +692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
|
||||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -705,9 +708,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -721,9 +724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
|
||||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -737,9 +740,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
|
||||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -753,9 +756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
|
||||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -769,9 +772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
|
||||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -785,9 +788,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
|
||||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -801,9 +804,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
|
||||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -817,9 +820,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -833,9 +836,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -849,9 +852,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -865,9 +868,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -881,9 +884,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -897,9 +900,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -913,9 +916,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -929,9 +932,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -945,9 +948,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
|
||||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -961,9 +964,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2613,9 +2616,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.10.1",
|
"version": "25.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz",
|
||||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -2646,18 +2649,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
||||||
"integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
|
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.48.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/type-utils": "8.48.0",
|
"@typescript-eslint/type-utils": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.48.0",
|
"@typescript-eslint/utils": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"graphemer": "^1.4.0",
|
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
@@ -2670,7 +2672,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.48.0",
|
"@typescript-eslint/parser": "^8.50.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
@@ -2686,17 +2688,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
||||||
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
|
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.48.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2712,14 +2714,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
||||||
"integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
|
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.48.0",
|
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
||||||
"@typescript-eslint/types": "^8.48.0",
|
"@typescript-eslint/types": "^8.50.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2734,14 +2736,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
||||||
"integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
|
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.48.0"
|
"@typescript-eslint/visitor-keys": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2752,9 +2754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
|
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2769,15 +2771,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
|
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.48.0",
|
"@typescript-eslint/utils": "8.50.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -2794,9 +2796,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
||||||
"integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
|
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2808,16 +2810,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
||||||
"integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
|
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.48.0",
|
"@typescript-eslint/project-service": "8.50.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.48.0",
|
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.4",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
@@ -2875,16 +2877,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
|
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.48.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.48.0"
|
"@typescript-eslint/typescript-estree": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2899,13 +2901,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
||||||
"integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
|
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.48.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3622,14 +3624,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cssstyle": {
|
"node_modules/cssstyle": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.4.tgz",
|
||||||
"integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==",
|
"integrity": "sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/css-color": "^4.0.3",
|
"@asamuzakjp/css-color": "^4.1.0",
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.0.14",
|
"@csstools/css-syntax-patches-for-csstree": "1.0.14",
|
||||||
"css-tree": "^3.1.0"
|
"css-tree": "^3.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4067,9 +4069,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz",
|
||||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
"integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4079,32 +4081,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.25.8",
|
"@esbuild/aix-ppc64": "0.27.1",
|
||||||
"@esbuild/android-arm": "0.25.8",
|
"@esbuild/android-arm": "0.27.1",
|
||||||
"@esbuild/android-arm64": "0.25.8",
|
"@esbuild/android-arm64": "0.27.1",
|
||||||
"@esbuild/android-x64": "0.25.8",
|
"@esbuild/android-x64": "0.27.1",
|
||||||
"@esbuild/darwin-arm64": "0.25.8",
|
"@esbuild/darwin-arm64": "0.27.1",
|
||||||
"@esbuild/darwin-x64": "0.25.8",
|
"@esbuild/darwin-x64": "0.27.1",
|
||||||
"@esbuild/freebsd-arm64": "0.25.8",
|
"@esbuild/freebsd-arm64": "0.27.1",
|
||||||
"@esbuild/freebsd-x64": "0.25.8",
|
"@esbuild/freebsd-x64": "0.27.1",
|
||||||
"@esbuild/linux-arm": "0.25.8",
|
"@esbuild/linux-arm": "0.27.1",
|
||||||
"@esbuild/linux-arm64": "0.25.8",
|
"@esbuild/linux-arm64": "0.27.1",
|
||||||
"@esbuild/linux-ia32": "0.25.8",
|
"@esbuild/linux-ia32": "0.27.1",
|
||||||
"@esbuild/linux-loong64": "0.25.8",
|
"@esbuild/linux-loong64": "0.27.1",
|
||||||
"@esbuild/linux-mips64el": "0.25.8",
|
"@esbuild/linux-mips64el": "0.27.1",
|
||||||
"@esbuild/linux-ppc64": "0.25.8",
|
"@esbuild/linux-ppc64": "0.27.1",
|
||||||
"@esbuild/linux-riscv64": "0.25.8",
|
"@esbuild/linux-riscv64": "0.27.1",
|
||||||
"@esbuild/linux-s390x": "0.25.8",
|
"@esbuild/linux-s390x": "0.27.1",
|
||||||
"@esbuild/linux-x64": "0.25.8",
|
"@esbuild/linux-x64": "0.27.1",
|
||||||
"@esbuild/netbsd-arm64": "0.25.8",
|
"@esbuild/netbsd-arm64": "0.27.1",
|
||||||
"@esbuild/netbsd-x64": "0.25.8",
|
"@esbuild/netbsd-x64": "0.27.1",
|
||||||
"@esbuild/openbsd-arm64": "0.25.8",
|
"@esbuild/openbsd-arm64": "0.27.1",
|
||||||
"@esbuild/openbsd-x64": "0.25.8",
|
"@esbuild/openbsd-x64": "0.27.1",
|
||||||
"@esbuild/openharmony-arm64": "0.25.8",
|
"@esbuild/openharmony-arm64": "0.27.1",
|
||||||
"@esbuild/sunos-x64": "0.25.8",
|
"@esbuild/sunos-x64": "0.27.1",
|
||||||
"@esbuild/win32-arm64": "0.25.8",
|
"@esbuild/win32-arm64": "0.27.1",
|
||||||
"@esbuild/win32-ia32": "0.25.8",
|
"@esbuild/win32-ia32": "0.27.1",
|
||||||
"@esbuild/win32-x64": "0.25.8"
|
"@esbuild/win32-x64": "0.27.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -4673,13 +4675,6 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/graphemer": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||||
@@ -5357,16 +5352,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "27.2.0",
|
"version": "27.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.3.0.tgz",
|
||||||
"integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==",
|
"integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@acemir/cssom": "^0.9.23",
|
"@acemir/cssom": "^0.9.28",
|
||||||
"@asamuzakjp/dom-selector": "^6.7.4",
|
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||||
"cssstyle": "^5.3.3",
|
"cssstyle": "^5.3.4",
|
||||||
"data-urls": "^6.0.0",
|
"data-urls": "^6.0.0",
|
||||||
"decimal.js": "^10.6.0",
|
"decimal.js": "^10.6.0",
|
||||||
"html-encoding-sniffer": "^4.0.0",
|
"html-encoding-sniffer": "^4.0.0",
|
||||||
@@ -5759,9 +5754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
"version": "0.556.0",
|
"version": "0.561.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.556.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
|
||||||
"integrity": "sha512-iOb8dRk7kLaYBZhR2VlV1CeJGxChBgUthpSP8wom9jfj79qovgG6qcSdiy6vkoREKPnbUYzJsCn4o4PtG3Iy+A==",
|
"integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
@@ -6195,6 +6190,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -7263,16 +7259,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.48.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
|
||||||
"integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==",
|
"integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
"@typescript-eslint/parser": "8.48.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.48.0"
|
"@typescript-eslint/utils": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -7397,13 +7393,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.2.2",
|
"version": "7.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
@@ -7802,9 +7798,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.1.12",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.0.tgz",
|
||||||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|||||||
@@ -27,20 +27,20 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.556.0",
|
"lucide-react": "^0.561.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^25.0.2",
|
||||||
"@types/react": "^19.2.4",
|
"@types/react": "^19.2.4",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
@@ -49,11 +49,11 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"jsdom": "^27.2.0",
|
"jsdom": "^27.3.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.48.0",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.3.0",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user