Compare commits

..

1 Commits

Author SHA1 Message Date
Huakun Shen
f25f66954a
chore: bump desktop package version to 0.1.30 2025-03-01 21:49:31 -05:00
187 changed files with 2738 additions and 8666 deletions

View File

@ -11,9 +11,11 @@
"jarvis", "jarvis",
"form-view", "form-view",
"@kksh/desktop", "@kksh/desktop",
"@kksh/supabase",
"@kksh/utils", "@kksh/utils",
"@kksh/extension", "@kksh/extension",
"@kksh/schema", "@kksh/schema",
"@kksh/supabase",
"@kksh/ui" "@kksh/ui"
] ]
} }

View File

@ -87,8 +87,6 @@ jobs:
# pnpm --filter=@kksh/ci run ci-env-check # pnpm --filter=@kksh/ci run ci-env-check
bun packages/ci/scripts/ci-env-check.ts bun packages/ci/scripts/ci-env-check.ts
- name: Build Packages - name: Build Packages
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build run: pnpm build
- name: Get App Version - name: Get App Version
if: matrix.settings.platform == 'windows-latest' if: matrix.settings.platform == 'windows-latest'
@ -100,7 +98,6 @@ jobs:
CI: false CI: false
KUNKUN_PUBLISH: true KUNKUN_PUBLISH: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}

View File

@ -1,121 +0,0 @@
name: "Desktop Test build"
on:
push:
branches:
- "test-build"
tags:
- "v*"
workflow_dispatch:
jobs:
publish-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- platform: "macos-14" # for Arm based macs (M1 and above).
args: "--target aarch64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "macos-13" # for Intel based macs.
args: "--target x86_64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
# Universal Build no longer supported after adding openssl, which is not cross-compilable.
- platform: "macos-14" # for Both Arm and Intel based macs.
args: "--target universal-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "ubuntu-22.04" # for Tauri v1 you could replace this with ubuntu-20.04.
args: "--verbose --config src-tauri/tauri.conf.publish.json"
- platform: "windows-latest"
args: "--verbose --config src-tauri/tauri.conf.publish.json"
runs-on: ${{ matrix.settings.platform }}
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Dependencies (ubuntu only)
if: matrix.settings.platform == 'ubuntu-22.04' # This must match the platform value defined above.
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
- name: Install protobuf (Mac)
if: startsWith(matrix.settings.platform, 'macos')
run: |
brew install protobuf
brew install openssl
- name: Install Protobuf (Ubuntu)
if: matrix.settings.platform == 'ubuntu-22.04'
run: |
sudo apt install -y protobuf-compiler
- uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm" # Set this to npm, yarn or pnpm.
cache-dependency-path: ./pnpm-lock.yaml
- name: Install protoc and openssl for windows
if: matrix.settings.platform == 'windows-latest'
run: |
choco install protoc
choco install openssl
echo OPENSSL_DIR='C:\Program Files\OpenSSL' >> $env:GITHUB_ENV
echo OPENSSL_INCLUDE_DIR='C:\Program Files\OpenSSL\include' >> $env:GITHUB_ENV
echo OPENSSL_LIB_DIR='C:\Program Files\OpenSSL\lib\VC\x64\MDd' >> $env:GITHUB_ENV
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
targets: ${{ matrix.settings.platform == 'macos-14' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Add rust target (macos only)
if: matrix.settings.platform == 'macos-14'
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
- name: Rust Cache
uses: swatinem/rust-cache@v2
with:
workspaces: ". -> target"
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Dependencies
run: pnpm install
- name: Environment Check
run: |
# pnpm --filter=@kksh/ci run ci-env-check
bun packages/ci/scripts/ci-env-check.ts
- name: Build Packages
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
- name: Get App Version
if: matrix.settings.platform == 'windows-latest'
id: appversion
run: |
echo "version=$(node -p "require('./apps/desktop/package.json').version")" >> $env:GITHUB_OUTPUT
- uses: tauri-apps/tauri-action@v0
env:
CI: false
KUNKUN_PUBLISH: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: Kunkun-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
releaseName: "Kunkun v__VERSION__"
releaseBody: "See the assets to download this version and install."
releaseDraft: false
prerelease: false
args: ${{ matrix.settings.args }} ${{ contains(steps.appversion.outputs.version, 'beta') && matrix.settings.platform == 'windows-latest' && '-b nsis' || '' }}
projectPath: "./apps/desktop"

546
Cargo.lock generated
View File

@ -211,12 +211,10 @@ dependencies = [
"anyhow", "anyhow",
"cocoa 0.25.0", "cocoa 0.25.0",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"env_logger",
"glob", "glob",
"image", "image",
"ini", "ini",
"lnk", "lnk",
"log",
"objc", "objc",
"parselnk", "parselnk",
"plist", "plist",
@ -479,15 +477,6 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -714,7 +703,7 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@ -833,31 +822,6 @@ dependencies = [
"piper", "piper",
] ]
[[package]]
name = "bon"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65268237be94042665b92034f979c42d431d2fd998b49809543afe3e66abad1c"
dependencies = [
"bon-macros",
"rustversion",
]
[[package]]
name = "bon-macros"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "803c95b2ecf650eb10b5f87dda6b9f6a1b758cee53245e2b7b825c9b3803a443"
dependencies = [
"darling",
"ident_case",
"prettyplease",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.87",
]
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.3.0" version = "1.3.0"
@ -1808,20 +1772,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728"
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]] [[package]]
name = "data-url" name = "data-url"
version = "0.3.1" version = "0.3.1"
@ -1878,7 +1828,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"pem-rfc7468",
"zeroize", "zeroize",
] ]
@ -2091,12 +2040,6 @@ dependencies = [
"const-random", "const-random",
] ]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "downcast-rs" name = "downcast-rs"
version = "1.2.1" version = "1.2.1"
@ -2182,9 +2125,6 @@ name = "either"
version = "1.13.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
@ -2311,19 +2251,6 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "epoll" name = "epoll"
version = "4.3.3" version = "4.3.3"
@ -2366,17 +2293,6 @@ version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "etcetera"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.3.1" version = "5.3.1"
@ -2655,17 +2571,6 @@ dependencies = [
"futures-util", "futures-util",
] ]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.31" version = "0.3.31"
@ -3356,12 +3261,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.31" version = "0.14.31"
@ -3784,17 +3683,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "is-terminal"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
dependencies = [
"hermit-abi 0.4.0",
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "is-wsl" name = "is-wsl"
version = "0.4.0" version = "0.4.0"
@ -3826,15 +3714,6 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.8" version = "0.4.8"
@ -4055,10 +3934,8 @@ dependencies = [
"tauri-plugin-shell", "tauri-plugin-shell",
"tauri-plugin-shellx", "tauri-plugin-shellx",
"tauri-plugin-single-instance", "tauri-plugin-single-instance",
"tauri-plugin-sql",
"tauri-plugin-store", "tauri-plugin-store",
"tauri-plugin-stronghold", "tauri-plugin-stronghold",
"tauri-plugin-svelte",
"tauri-plugin-system-info", "tauri-plugin-system-info",
"tauri-plugin-updater", "tauri-plugin-updater",
"tauri-plugin-upload", "tauri-plugin-upload",
@ -4074,9 +3951,6 @@ name = "lazy_static"
version = "1.5.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
]
[[package]] [[package]]
name = "lazycell" name = "lazycell"
@ -4184,12 +4058,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.3" version = "0.1.3"
@ -4407,16 +4275,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]] [[package]]
name = "mdns-sd" name = "mdns-sd"
version = "0.11.5" version = "0.11.5"
@ -4795,23 +4653,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.5",
"smallvec",
"zeroize",
]
[[package]] [[package]]
name = "num-complex" name = "num-complex"
version = "0.4.6" version = "0.4.6"
@ -4887,7 +4728,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@ -5406,15 +5246,6 @@ dependencies = [
"hmac", "hmac",
] ]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -5608,17 +5439,6 @@ dependencies = [
"futures-io", "futures-io",
] ]
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
"pkcs8",
"spki",
]
[[package]] [[package]]
name = "pkcs8" name = "pkcs8"
version = "0.10.2" version = "0.10.2"
@ -5841,7 +5661,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.5.0", "heck 0.5.0",
"itertools 0.12.1", "itertools",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -5861,7 +5681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@ -6113,7 +5933,7 @@ dependencies = [
"built", "built",
"cfg-if", "cfg-if",
"interpolate_name", "interpolate_name",
"itertools 0.12.1", "itertools",
"libc", "libc",
"libfuzzer-sys", "libfuzzer-sys",
"log", "log",
@ -6460,26 +6280,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rsa"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.31.0" version = "0.31.0"
@ -7125,9 +6925,6 @@ name = "smallvec"
version = "1.13.2" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
@ -7206,211 +7003,6 @@ dependencies = [
"der", "der",
] ]
[[package]]
name = "sqlformat"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
dependencies = [
"nom",
"unicode_categories",
]
[[package]]
name = "sqlx"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
dependencies = [
"atoi",
"byteorder",
"bytes",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashlink",
"hex",
"indexmap 2.6.0",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlformat",
"thiserror 1.0.66",
"time",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.87",
]
[[package]]
name = "sqlx-macros-core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
dependencies = [
"dotenvy",
"either",
"heck 0.5.0",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.87",
"tempfile",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.6.0",
"byteorder",
"bytes",
"crc",
"digest",
"dotenvy",
"either",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"generic-array",
"hex",
"hkdf",
"hmac",
"itoa 1.0.11",
"log",
"md-5",
"memchr",
"once_cell",
"percent-encoding",
"rand 0.8.5",
"rsa",
"serde",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.66",
"time",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-postgres"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.6.0",
"byteorder",
"crc",
"dotenvy",
"etcetera",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"hex",
"hkdf",
"hmac",
"home",
"itoa 1.0.11",
"log",
"md-5",
"memchr",
"once_cell",
"rand 0.8.5",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.66",
"time",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
dependencies = [
"atoi",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"time",
"tracing",
"url",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -7466,17 +7058,6 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]] [[package]]
name = "strip-ansi-escapes" name = "strip-ansi-escapes"
version = "0.2.0" version = "0.2.0"
@ -8292,9 +7873,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-shellx" name = "tauri-plugin-shellx"
version = "2.0.16" version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b83b29705410452dbfc85f604f3dce2b674047449060b488fcd9f3a907c25ef" checksum = "88a5b6b883070de00f8fd5025aa5b81e3b012a5a3d9f38875cdb45809c71232c"
dependencies = [ dependencies = [
"encoding_rs", "encoding_rs",
"open", "open",
@ -8327,25 +7908,6 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "tauri-plugin-sql"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6ccca89ded6bd2ff49fdad9a5b34bcd624aa223fdfddbab83c85706ee3a4948"
dependencies = [
"futures-core",
"indexmap 2.6.0",
"log",
"serde",
"serde_json",
"sqlx",
"tauri",
"tauri-plugin",
"thiserror 2.0.3",
"time",
"tokio",
]
[[package]] [[package]]
name = "tauri-plugin-store" name = "tauri-plugin-store"
version = "2.2.0" version = "2.2.0"
@ -8383,19 +7945,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "tauri-plugin-svelte"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17e96f88b3c614b98cea3afb5de6e2661d32f82c70423ae125c56a25d62017e6"
dependencies = [
"serde",
"tauri",
"tauri-plugin",
"tauri-store",
"tracing",
]
[[package]] [[package]]
name = "tauri-plugin-system-info" name = "tauri-plugin-system-info"
version = "2.0.9" version = "2.0.9"
@ -8516,52 +8065,6 @@ dependencies = [
"wry", "wry",
] ]
[[package]]
name = "tauri-store"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a33c8afdf92c1b177296c0299f6d20116cbce0fa1e2264819fea8c80fd31774"
dependencies = [
"dashmap",
"futures",
"itertools 0.14.0",
"serde",
"serde_json",
"tauri",
"tauri-store-macros",
"tauri-store-utils",
"thiserror 2.0.3",
"tokio",
"tracing",
]
[[package]]
name = "tauri-store-macros"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8857e4240cf6dbabb15fc2d595e92abba404f0a5cce0f3abbfe9316cac4aa99"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "tauri-store-utils"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14376c237a6632663991634d51a31f128b6b381b94d65e747db2419a513ae6d8"
dependencies = [
"bon",
"semver",
"serde",
"serde_json",
"tauri",
"thiserror 2.0.3",
"tokio",
"tracing",
]
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.1.1" version = "2.1.1"
@ -8644,15 +8147,6 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "thin-slice" name = "thin-slice"
version = "0.1.1" version = "0.1.1"
@ -9211,24 +8705,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-properties"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
@ -9451,12 +8933,6 @@ dependencies = [
"wit-bindgen-rt", "wit-bindgen-rt",
] ]
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.95" version = "0.2.95"
@ -9714,16 +9190,6 @@ dependencies = [
"rustix", "rustix",
] ]
[[package]]
name = "whoami"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [
"redox_syscall",
"wasite",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.3" version = "0.4.3"

View File

@ -20,7 +20,7 @@ tokio-util = "0.7.12"
mdns-sd = "0.11.1" mdns-sd = "0.11.1"
tauri-plugin-network = { path = "./vendors/tauri-plugin-network" } tauri-plugin-network = { path = "./vendors/tauri-plugin-network" }
tauri-plugin-keyring = { path = "./vendors/tauri-plugin-keyring" } tauri-plugin-keyring = { path = "./vendors/tauri-plugin-keyring" }
tauri-plugin-shellx = { version = "2.0.16" } tauri-plugin-shellx = { version = "2.0.15" }
tauri-plugin-clipboard = "2.1.11" tauri-plugin-clipboard = "2.1.11"
mac-security-rs = { path = "./packages/mac-security-rs" } mac-security-rs = { path = "./packages/mac-security-rs" }
log = "0.4.22" log = "0.4.22"

View File

@ -31,7 +31,7 @@
"debug": "^4.4.0", "debug": "^4.4.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"inquirer": "^10.1.2", "inquirer": "^10.1.2",
"valibot": "^1.0.0" "valibot": "^1.0.0-beta.10"
}, },
"files": [ "files": [
"dist" "dist"

View File

@ -27,7 +27,7 @@
"commander": "^12.1.0", "commander": "^12.1.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"valibot": "^1.0.0" "valibot": "^1.0.0-beta.10"
}, },
"files": [ "files": [
"dist" "dist"

View File

@ -1,13 +0,0 @@
import { IconType } from "@kksh/api/models"
import { getExtensionsLatestPublishByIdentifier } from "@kksh/sdk"
const latestPublish = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: "RAG1"
}
})
console.log(latestPublish)
// latestPublish
// console.log(typeof IconEnum.Iconify)
console.log(IconType.options)

View File

@ -1,91 +0,0 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_name": "KunKun",
"secondary_app_name": "KunKun",
"common_edit": "Bearbeiten",
"common_clear": "Löschen",
"common_check": "Prüfen",
"common_install": "Installieren",
"home_command_input_placeholder": "Suchen…",
"home_command_input_dropdown_quit": "Beenden",
"home_command_input_dropdown_developer_title": "Entwickler",
"home_command_input_dropdown_close_window": "Fenster schließen",
"home_command_input_dropdown_toggle_devtools": "Entwicklertools umschalten",
"home_command_input_dropdown_reload_window": "Fenster neu laden",
"home_command_input_dropdown_open_preference": "Einstellungen öffnen",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Entwicklererweiterungen HMR umschalten",
"command_group_heading_dev_ext": "Entwicklererweiterungen",
"command_group_heading_ext": "Erweiterungen",
"command_group_heading_quick_links": "Quick Links",
"settings_menu_settings": "Einstellungen",
"settings_menu_general": "Allgemein",
"settings_menu_app_search_paths": "Verzeichnisse für Programme",
"settings_menu_developer": "Entwickler",
"settings_menu_extensions": "Erweiterungen",
"settings_menu_set_dev_ext": "Dev-Erweiterung festlegen",
"settings_menu_add_dev_ext": "Dev-Erweiterung hinzufügen",
"settings_menu_about": "Über",
"settings_general_launch_at_login": "Beim Systemstart öffnen",
"settings_general_hotkey": "Tastenkombination",
"settings_general_menu_bar_icon": "Menüleiste-Symbol",
"settings_general_hide_on_blur": "Automatisch ausblenden",
"settings_general_extension_auto_upgrade": "Erweiterungen automatisch aktualisieren",
"settings_general_dev_extension_hmr": "Entwicklererweiterungen HMR",
"settings_general_join_beta_updates": "Beta-Updates nutzen",
"settings_general_developer_mode": "Entwickler-Modus",
"settings_general_language": "Sprache",
"settings_general_loading_animation": "Ladeanimation",
"settings_app_search_paths_title": "Zusätzliche Verzeichnisse für die Programm-Suche",
"settings_app_search_paths_add_app_search_path": "Verzeichnis für Programm-Suche hinzufügen",
"settings_app_search_paths_table_col_search_path": "Suchpfad",
"settings_app_search_paths_table_col_depth": "Tiefe",
"settings_app_search_paths_table_col_actions": "Aktionen",
"settings_about_version": "Version",
"settings_about_author": "Autor",
"settings_about_source_code": "Quellcode",
"settings_about_extensions_source_code": "Quellcode für Erweiterungen",
"settings_about_check_for_updates": "Nach Updates suchen",
"settings_set_dev_ext_title": "Verzeichnis der Entwicklererweiterungen",
"settings_set_dev_ext_description": "Hier werden Entwicklererweiterungen installiert.",
"settings_set_dev_ext_enter_path": "Verzeichnis eingeben",
"settings_extensions_title": "Deine Erweiterungen",
"settings_extensions_table_col_name": "Name",
"settings_extensions_table_col_identifier": "Identifikator",
"settings_extensions_table_col_type": "Typ",
"settings_extensions_table_col_version": "Version",
"settings_extensions_table_col_uninstall": "Deinstallieren",
"settings_add_dev_ext_title": "Entwicklererweiterung hinzufügen",
"settings_add_dev_ext_description": "Es gibt vier Möglichkeiten, eine Erweiterung als Entwicklererweiterung zu installieren. Tarball-Archiv, lokales Verzeichnis, URL zu Tarball-Archiv oder NPM-Paketnamen.",
"settings_add_dev_ext_install_from_ext_folders": "Verzeichnis",
"settings_add_dev_ext_install_from_ext_files": "Tarball-Archiv",
"settings_add_dev_ext_drag_and_drop": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop_strike": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop2": "Verzeichnis oder Tarball-Archiv",
"settings_add_dev_ext_install_tarball_from_url": "Tarball-Archiv aus URL installieren",
"troubleshooters_sidebar_title": "Fehlerbehebung",
"troubleshooters_sidebar_extension_loading_title": "Ladevorgang",
"troubleshooters_sidebar_extension_window_title": "Darstellung",
"troubleshooters_sidebar_mdns_debugger_title": "MDNS-Debugger",
"troubleshooters_extension_window_title": "Fehlerbehebung für die Darstellung von Erweiterungen",
"troubleshooters_extension_window_refresh_every_second": "Jede Sekunde neu laden",
"troubleshooters_extension_window_refresh": "Neu laden",
"troubleshooters_extension_window_refreshed": "{count}x neu geladen",
"troubleshooters_extension_loading_title": "Fehlerbehebung für den Ladevorgang von Erweiterungen",
"troubleshooters_extension_loading_table_col_identifier": "Identifikator",
"troubleshooters_extension_loading_table_col_path": "Verzeichnis",
"troubleshooters_extension_loading_table_col_error": "Fehler"
}

View File

@ -40,7 +40,6 @@
"settings_general_join_beta_updates": "Join Beta Updates", "settings_general_join_beta_updates": "Join Beta Updates",
"settings_general_developer_mode": "Developer Mode", "settings_general_developer_mode": "Developer Mode",
"settings_general_language": "Language", "settings_general_language": "Language",
"settings_general_loading_animation": "Loading Animation",
"settings_app_search_paths_title": "Extra App Search Paths", "settings_app_search_paths_title": "Extra App Search Paths",
"settings_app_search_paths_add_app_search_path": "Add App Search Path", "settings_app_search_paths_add_app_search_path": "Add App Search Path",

View File

@ -39,7 +39,6 @@
"settings_general_join_beta_updates": "Participar das Atualizações Beta", "settings_general_join_beta_updates": "Participar das Atualizações Beta",
"settings_general_developer_mode": "Modo Desenvolvedor", "settings_general_developer_mode": "Modo Desenvolvedor",
"settings_general_language": "Idioma", "settings_general_language": "Idioma",
"settings_general_loading_animation": "Animação de Carregamento",
"settings_about_version": "Versão", "settings_about_version": "Versão",
"settings_about_author": "Autor", "settings_about_author": "Autor",

View File

@ -39,7 +39,6 @@
"settings_general_join_beta_updates": "Получать бета-обновления", "settings_general_join_beta_updates": "Получать бета-обновления",
"settings_general_developer_mode": "Режим разработчика", "settings_general_developer_mode": "Режим разработчика",
"settings_general_language": "Язык", "settings_general_language": "Язык",
"settings_general_loading_animation": "Анимация загрузки",
"settings_about_version": "Версия", "settings_about_version": "Версия",
"settings_about_author": "Автор", "settings_about_author": "Автор",

View File

@ -39,7 +39,6 @@
"settings_general_join_beta_updates": "Cài đặt cập nhật thử nghiệm (beta)", "settings_general_join_beta_updates": "Cài đặt cập nhật thử nghiệm (beta)",
"settings_general_developer_mode": "Chế độ nhà phát triển", "settings_general_developer_mode": "Chế độ nhà phát triển",
"settings_general_language": "Ngôn ngữ", "settings_general_language": "Ngôn ngữ",
"settings_general_loading_animation": "Hình ảnh tải",
"settings_about_version": "Phiên bản", "settings_about_version": "Phiên bản",
"settings_about_author": "Tác giả", "settings_about_author": "Tác giả",

View File

@ -40,7 +40,6 @@
"settings_general_join_beta_updates": "加入 Beta 更新", "settings_general_join_beta_updates": "加入 Beta 更新",
"settings_general_developer_mode": "开发者模式", "settings_general_developer_mode": "开发者模式",
"settings_general_language": "语言", "settings_general_language": "语言",
"settings_general_loading_animation": "加载动画",
"settings_app_search_paths_title": "额外应用搜索路径", "settings_app_search_paths_title": "额外应用搜索路径",
"settings_app_search_paths_add_app_search_path": "添加应用搜索路径", "settings_app_search_paths_add_app_search_path": "添加应用搜索路径",

View File

@ -1,6 +1,6 @@
{ {
"name": "@kksh/desktop", "name": "@kksh/desktop",
"version": "0.1.37", "version": "0.1.30",
"description": "", "description": "",
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -17,8 +17,8 @@
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@inlang/paraglide-sveltekit": "0.16.0", "@inlang/paraglide-sveltekit": "0.16.0",
"@kksh/drizzle": "workspace:*",
"@kksh/extension": "workspace:*", "@kksh/extension": "workspace:*",
"@kksh/supabase": "workspace:*",
"@kksh/svelte5": "^0.1.15", "@kksh/svelte5": "^0.1.15",
"@kksh/ui": "workspace:*", "@kksh/ui": "workspace:*",
"@kksh/utils": "workspace:*", "@kksh/utils": "workspace:*",
@ -28,24 +28,20 @@
"@tauri-apps/api": "^2.3.0", "@tauri-apps/api": "^2.3.0",
"@tauri-apps/plugin-autostart": "^2.2.0", "@tauri-apps/plugin-autostart": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-shell": "^2.2.0",
"@tauri-apps/plugin-sql": "^2.2.0",
"@tauri-apps/plugin-stronghold": "^2.2.0", "@tauri-apps/plugin-stronghold": "^2.2.0",
"@tauri-store/svelte": "^2.1.1",
"dompurify": "^3.2.4", "dompurify": "^3.2.4",
"drizzle-orm": "^0.41.0",
"eslint": "^9.21.0", "eslint": "^9.21.0",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"gsap": "^3.12.7", "gsap": "^3.12.7",
"kkrpc": "^0.2.2", "kkrpc": "^0.1.2",
"lz-string": "^1.5.0", "lz-string": "^1.5.0",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"semver": "^7.7.1", "semver": "^7.7.1",
"svelte-inspect-value": "^0.5.0", "svelte-inspect-value": "^0.3.0",
"svelte-sonner": "^0.3.28", "svelte-sonner": "^0.3.28",
"sveltekit-superforms": "^2.23.1", "sveltekit-superforms": "^2.23.1",
"tauri-plugin-clipboard-api": "^2.1.11", "tauri-plugin-clipboard-api": "^2.1.11",
"tauri-plugin-shellx-api": "^2.0.16", "tauri-plugin-shellx-api": "^2.0.15",
"tauri-plugin-svelte": "1.2.1",
"tauri-plugin-user-input-api": "workspace:*", "tauri-plugin-user-input-api": "workspace:*",
"uuid": "^11.1.0" "uuid": "^11.1.0"
}, },

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://inlang.com/schema/project-settings", "$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en", "sourceLanguageTag": "en",
"languageTags": ["en", "zh", "ru", "pt", "vi", "de"], "languageTags": ["en", "zh", "ru", "pt", "vi"],
"modules": [ "modules": [
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",

View File

@ -58,7 +58,6 @@ uuid = "1.14.0"
obfstr = { workspace = true } obfstr = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
tauri-plugin-stronghold = "2.2.0" tauri-plugin-stronghold = "2.2.0"
tauri-plugin-sql = "2"
[target."cfg(target_os = \"macos\")".dependencies] [target."cfg(target_os = \"macos\")".dependencies]
@ -66,10 +65,10 @@ cocoa = "0.24.1"
mac-security-rs = { workspace = true } mac-security-rs = { workspace = true }
objc = "0.2.7" objc = "0.2.7"
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-autostart = "2" tauri-plugin-autostart = "2"
tauri-plugin-cli = "2" tauri-plugin-cli = "2"
tauri-plugin-global-shortcut = "2.0.1" tauri-plugin-global-shortcut = "2.0.1"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
tauri-plugin-updater = "2.0.2" tauri-plugin-updater = "2.0.2"
tauri-plugin-svelte = "2.1.1"

View File

@ -24,7 +24,6 @@
"core:event:default", "core:event:default",
"core:window:default", "core:window:default",
"core:window:allow-set-size", "core:window:allow-set-size",
"core:window:allow-set-enabled",
"core:window:allow-start-dragging", "core:window:allow-start-dragging",
"core:window:allow-set-focus", "core:window:allow-set-focus",
"core:window:allow-toggle-maximize", "core:window:allow-toggle-maximize",

View File

@ -1,5 +0,0 @@
{
"identifier": "svelte",
"windows": ["*"],
"permissions": ["svelte:default", "core:event:default"]
}

View File

@ -27,7 +27,7 @@ use utils::server::tauri_file_server;
pub fn run() { pub fn run() {
let context = tauri::generate_context!(); let context = tauri::generate_context!();
let mut builder = tauri::Builder::default(); let mut builder = tauri::Builder::default();
// let app_data_path = tauri::path::PathResolver::app_data_dir().unwrap();
// let db_key = if cfg!(debug_assertions) { // let db_key = if cfg!(debug_assertions) {
// None // None
// } else { // } else {
@ -108,16 +108,10 @@ pub fn run() {
.build(), .build(),
) )
.plugin(tauri_plugin_cli::init()) .plugin(tauri_plugin_cli::init())
.plugin(
tauri_plugin_sql::Builder::default()
// .add_migrations("sqlite:mydatabase.db", migrations)
.build(),
)
.plugin(tauri_plugin_user_input::init()) .plugin(tauri_plugin_user_input::init())
.plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_svelte::init())
.plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_autostart::init( .plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent, MacosLauncher::LaunchAgent,

View File

@ -5,7 +5,7 @@
"identifier": "sh.kunkun.desktop", "identifier": "sh.kunkun.desktop",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",
"devUrl": "http://localhost:1566", "devUrl": "http://localhost:1420",
"beforeBuildCommand": "pnpm build", "beforeBuildCommand": "pnpm build",
"frontendDist": "../build" "frontendDist": "../build"
}, },
@ -20,7 +20,6 @@
"url": "/app", "url": "/app",
"title": "Kunkun", "title": "Kunkun",
"width": 800, "width": 800,
"label": "main",
"visible": false, "visible": false,
"height": 600, "height": 600,
"decorations": true, "decorations": true,

View File

@ -242,23 +242,6 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
}, },
keywords: ["extension", "troubleshooter"] keywords: ["extension", "troubleshooter"]
}, },
{
name: "ORM Troubleshooter",
icon: {
type: IconEnum.Iconify,
value: "material-symbols:database"
},
description: "",
flags: {
developer: true,
dev: true
},
function: async () => {
appState.clearSearchTerm()
goto(i18n.resolveRoute("/app/troubleshooters/orm"))
},
keywords: ["extension", "troubleshooter", "database", "orm"]
},
{ {
name: "Create Quicklink", name: "Create Quicklink",
icon: { icon: {
@ -428,7 +411,7 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
visible: false visible: false
}) })
setTimeout(() => { setTimeout(() => {
window.show().then(() => window.setFocus()) window.show()
}, 2_000) }, 2_000)
} }
}, },

View File

@ -3,22 +3,19 @@ import { appState } from "@/stores"
import { winExtMap } from "@/stores/winExtMap" import { winExtMap } from "@/stores/winExtMap"
import { helperAPI } from "@/utils/helper" import { helperAPI } from "@/utils/helper"
import { paste } from "@/utils/hotkey" import { paste } from "@/utils/hotkey"
import { decideKkrpcSerialization } from "@/utils/kkrpc"
import { sleep } from "@/utils/time" import { sleep } from "@/utils/time"
import { trimSlash } from "@/utils/url" import { trimSlash } from "@/utils/url"
import { constructExtensionSupportDir } from "@kksh/api" import { constructExtensionSupportDir } from "@kksh/api"
import { spawnExtensionFileServer } from "@kksh/api/commands" import { db, spawnExtensionFileServer } from "@kksh/api/commands"
import type { HeadlessCommand } from "@kksh/api/headless" import type { HeadlessCommand } from "@kksh/api/headless"
import { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models" import { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
import { constructJarvisServerAPIWithPermissions, type IApp } from "@kksh/api/ui" import { constructJarvisServerAPIWithPermissions, type IApp } from "@kksh/api/ui"
import { db } from "@kksh/drizzle"
import { launchNewExtWindow, loadExtensionManifestFromDisk } from "@kksh/extension" import { launchNewExtWindow, loadExtensionManifestFromDisk } from "@kksh/extension"
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server" import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
import { convertFileSrc } from "@tauri-apps/api/core" import { convertFileSrc } from "@tauri-apps/api/core"
import * as path from "@tauri-apps/api/path" import * as path from "@tauri-apps/api/path"
import { getCurrentWindow } from "@tauri-apps/api/window" import { getCurrentWindow } from "@tauri-apps/api/window"
import * as fs from "@tauri-apps/plugin-fs" import * as fs from "@tauri-apps/plugin-fs"
import { info } from "@tauri-apps/plugin-log"
import { platform } from "@tauri-apps/plugin-os" import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { RPCChannel, WorkerParentIO } from "kkrpc/browser" import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
@ -88,7 +85,6 @@ export async function onHeadlessCmdSelect(
const loadedExt = await loadExtensionManifestFromDisk( const loadedExt = await loadExtensionManifestFromDisk(
await path.join(ext.extPath, "package.json") await path.join(ext.extPath, "package.json")
) )
const scriptPath = await path.join(loadedExt.extPath, cmd.main) const scriptPath = await path.join(loadedExt.extPath, cmd.main)
const workerScript = await fs.readTextFile(scriptPath) const workerScript = await fs.readTextFile(scriptPath)
const blob = new Blob([workerScript], { type: "application/javascript" }) const blob = new Blob([workerScript], { type: "application/javascript" })
@ -128,15 +124,8 @@ export async function onHeadlessCmdSelect(
} satisfies IApp } satisfies IApp
} }
const io = new WorkerParentIO(worker) const io = new WorkerParentIO(worker)
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel<typeof serverAPI2, HeadlessCommand>(io, { const rpc = new RPCChannel<typeof serverAPI2, HeadlessCommand>(io, {
expose: serverAPI2, expose: serverAPI2
serialization: {
version: kkrpcSerialization
}
}) })
const workerAPI = rpc.getAPI() const workerAPI = rpc.getAPI()
await workerAPI.load() await workerAPI.load()

View File

@ -12,7 +12,7 @@ import { onQuickLinkSelect } from "./quick-links"
const onExtCmdSelect: OnExtCmdSelect = ( const onExtCmdSelect: OnExtCmdSelect = (
ext: ExtPackageJsonExtra, ext: ExtPackageJsonExtra,
cmd: CustomUiCmd | TemplateUiCmd | HeadlessCmd, cmd: CustomUiCmd | TemplateUiCmd,
{ isDev, hmr }: { isDev: boolean; hmr: boolean } { isDev, hmr }: { isDev: boolean; hmr: boolean }
) => { ) => {
switch (cmd.type) { switch (cmd.type) {

View File

@ -1,37 +0,0 @@
<script lang="ts">
import { appConfig, appState } from "@/stores"
import { cn } from "@/utils"
import { Button } from "@kksh/svelte5"
import { BorderBeam, Constants, Layouts, TauriLink } from "@kksh/ui"
import { goto } from "$app/navigation"
import { ArrowLeftIcon, LoaderCircleIcon } from "lucide-svelte"
import Dance from "../dance/dance.svelte"
let { class: className }: { class?: string } = $props()
function goHome() {
appState.setFullScreenLoading(false)
goto("/app")
}
</script>
<Layouts.Center class={cn("flex h-screen flex-col items-center justify-center", className)}>
<Button
variant="outline"
size="icon"
onclick={goHome}
class={cn(Constants.CLASSNAMES.BACK_BUTTON, "absolute left-4 top-4")}
data-flip-id={Constants.CLASSNAMES.BACK_BUTTON}
>
<ArrowLeftIcon class="size-4" />
</Button>
{#if $appConfig.loadingAnimation === "kunkun-dancing"}
<!-- <DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} /> -->
<Dance class="absolute z-50 h-screen opacity-20" />
{:else}
<!-- <LoadingAnimation delay={300} autoHide={false} show={!uiControl.iframeLoaded} /> -->
<LoaderCircleIcon class="h-24 w-24 animate-spin" />
<span class="font-mono">Loading</span>
{/if}
<BorderBeam size={150} duration={12} />
</Layouts.Center>

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { setAppConfigContext } from "@/context" import { setAppConfigContext } from "@/context"
import { setAppStateContext } from "@/context/appState" import { setAppStateContext } from "@/context/appState"
import type { AppConfigState, AppState } from "@kksh/types" import type { AppConfig, AppState } from "@kksh/types"
import { type Snippet } from "svelte" import type { Snippet } from "svelte"
import type { Writable } from "svelte/store" import type { Writable } from "svelte/store"
const { const {
@ -10,7 +10,7 @@
appState, appState,
children children
}: { }: {
appConfig: Writable<AppConfigState> appConfig: Writable<AppConfig>
appState: Writable<AppState> appState: Writable<AppState>
children: Snippet<[]> children: Snippet<[]>
} = $props() } = $props()

View File

@ -15,8 +15,7 @@
</script> </script>
<DraggableCommandGroup heading="Apps"> <DraggableCommandGroup heading="Apps">
{#each apps.filter((app) => app.name) as app, idx} {#each apps.filter((app) => app.name) as app}
{@const iconPath = platform === "windows" ? (app.icon_path ?? app.app_path_exe) : app.icon_path}
<Command.Item <Command.Item
class="flex justify-between" class="flex justify-between"
onSelect={async () => { onSelect={async () => {
@ -40,14 +39,14 @@
await getCurrentWindow().hide() await getCurrentWindow().hide()
appState.clearSearchTerm() appState.clearSearchTerm()
}} }}
value={`app:${idx}:${app.app_desktop_path}`} value={app.app_desktop_path}
> >
<span class="flex gap-2"> <span class="flex gap-2">
<IconMultiplexer <IconMultiplexer
icon={iconPath icon={app.icon_path
? { ? {
type: IconEnum.RemoteUrl, type: IconEnum.RemoteUrl,
value: convertFileSrc(iconPath, "appicon") value: convertFileSrc(app.icon_path, "appicon")
} }
: { : {
type: IconEnum.Iconify, type: IconEnum.Iconify,
@ -56,7 +55,6 @@
class="!h-5 !w-5 shrink-0" class="!h-5 !w-5 shrink-0"
/> />
<span>{app.name}</span> <span>{app.name}</span>
<!-- <span>{app.app_path_exe}</span> -->
</span> </span>
</Command.Item> </Command.Item>
{/each} {/each}

View File

@ -11,24 +11,20 @@
} from "@/paraglide/runtime" } from "@/paraglide/runtime"
import { appConfig } from "@/stores" import { appConfig } from "@/stores"
import { Select, Switch } from "@kksh/svelte5" import { Select, Switch } from "@kksh/svelte5"
import type { LoadingAnimation } from "@kksh/types"
import * as autoStart from "@tauri-apps/plugin-autostart" import * as autoStart from "@tauri-apps/plugin-autostart"
import { onMount } from "svelte" import { onMount } from "svelte"
import { toast } from "svelte-sonner" import { toast } from "svelte-sonner"
const languages = availableLanguageTags.map((lang) => ({ const languages = availableLanguageTags.map((lang) => ({
value: lang, value: lang,
label: LanguageMap[lang as keyof typeof LanguageMap] ?? lang label: LanguageMap[lang] ?? lang
})) }))
let loadingAnimation = $state<LoadingAnimation>("spinning-circle")
const loadingAnimations = ["spinning-circle", "kunkun-dancing"] as const
let launchAtLogin = $state(false) let launchAtLogin = $state(false)
let language = $state(languageTag()) let language = $state(languageTag())
onMount(() => { onMount(() => {
autoStart.isEnabled().then((enabled) => { autoStart.isEnabled().then((enabled) => {
launchAtLogin = enabled launchAtLogin = enabled
}) })
loadingAnimation = $appConfig.loadingAnimation
}) })
const triggerContent = $derived(languages.find((f) => f.value === language)?.label ?? "Language") const triggerContent = $derived(languages.find((f) => f.value === language)?.label ?? "Language")
</script> </script>
@ -105,31 +101,6 @@
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
</li> </li>
<li>
<span>{m.settings_general_loading_animation()}</span>
<Select.Root type="single" name="loadingAnimation" bind:value={loadingAnimation}>
<Select.Trigger class="w-fit">
{loadingAnimation}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupHeading>Loading Animation</Select.GroupHeading>
{#each loadingAnimations as anim}
<Select.Item
onclick={() => {
appConfig.setLoadingAnimation(anim)
}}
value={anim}
label={anim}
>
{anim}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
</li>
</ul> </ul>
<style scoped> <style scoped>

View File

@ -3,16 +3,16 @@
* It's designed to allow all components to access a shared state. * It's designed to allow all components to access a shared state.
* With context, we can avoid prop drilling, and avoid using stores which makes components hard to encapsulate. * With context, we can avoid prop drilling, and avoid using stores which makes components hard to encapsulate.
*/ */
import type { AppConfigState } from "@kksh/types" import type { AppConfig } from "@/types/appConfig"
import { getContext, setContext } from "svelte" import { getContext, setContext } from "svelte"
import type { Writable } from "svelte/store" import type { Writable } from "svelte/store"
export const APP_CONFIG_CONTEXT_KEY = Symbol("appConfig") export const APP_CONFIG_CONTEXT_KEY = Symbol("appConfig")
export function getAppConfigContext(): Writable<AppConfigState> { export function getAppConfigContext(): Writable<AppConfig> {
return getContext(APP_CONFIG_CONTEXT_KEY) return getContext(APP_CONFIG_CONTEXT_KEY)
} }
export function setAppConfigContext(appConfig: Writable<AppConfigState>) { export function setAppConfigContext(appConfig: Writable<AppConfig>) {
setContext(APP_CONFIG_CONTEXT_KEY, appConfig) setContext(APP_CONFIG_CONTEXT_KEY, appConfig)
} }

View File

@ -1,4 +1,4 @@
import type { AppState } from "@kksh/types" import type { AppState } from "@/types/appState"
import { getContext, setContext } from "svelte" import { getContext, setContext } from "svelte"
import type { Writable } from "svelte/store" import type { Writable } from "svelte/store"

View File

@ -1,60 +0,0 @@
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { db as dbCmd } from "@kksh/api/commands"
// import * as schema from "@kksh/drizzle/schema"
// import { error } from "@tauri-apps/plugin-log"
// import { drizzle } from "drizzle-orm/sqlite-proxy"
// /**
// * Loads the sqlite database via the Tauri Proxy.
// */
// // export const sqlite = await Database.load("sqlite:test.db");
// /**
// * The drizzle database instance.
// */
// export const db = drizzle<typeof schema>(
// async (sql, params, method) => {
// let rows: any = []
// let results = []
// console.log({
// sql,
// params,
// method
// })
// console.log(sql)
// // If the query is a SELECT, use the select method
// if (isSelectQuery(sql)) {
// rows = await dbCmd.select(sql, params).catch((e) => {
// error("SQL Error:", e)
// return []
// })
// } else {
// // Otherwise, use the execute method
// rows = await dbCmd.execute(sql, params).catch((e) => {
// error("SQL Error:", e)
// return []
// })
// return { rows: [] }
// }
// rows = rows.map((row: any) => {
// return Object.values(row)
// })
// // If the method is "all", return all rows
// results = method === "all" ? rows : rows[0]
// return { rows: results }
// },
// // Pass the schema to the drizzle instance
// { schema: schema, logger: true }
// )
// /**
// * Checks if the given SQL query is a SELECT query.
// * @param sql The SQL query to check.
// * @returns True if the query is a SELECT query, false otherwise.
// */
// function isSelectQuery(sql: string): boolean {
// const selectRegex = /^\s*SELECT\b/i
// return selectRegex.test(sql)
// }

View File

@ -1,16 +1,16 @@
import { getExtensionsFolder } from "@/constants" import { getExtensionsFolder } from "@/constants"
import { createTauriSyncStore, type WithSyncStore } from "@/utils/sync-store"
import type { SearchPath } from "@kksh/api/models" import type { SearchPath } from "@kksh/api/models"
import { updateTheme, type ThemeConfig } from "@kksh/svelte5" import { updateTheme, type ThemeConfig } from "@kksh/svelte5"
import { LoadingAnimation, PersistedAppConfig, type AppConfigState } from "@kksh/types" import { PersistedAppConfig, type AppConfig } from "@kksh/types"
import { debug, error, info } from "@tauri-apps/plugin-log" import { debug, error } from "@tauri-apps/plugin-log"
import * as os from "@tauri-apps/plugin-os" import * as os from "@tauri-apps/plugin-os"
import { load } from "@tauri-apps/plugin-store" import { load } from "@tauri-apps/plugin-store"
import { Store } from "@tauri-store/svelte"
import { toast } from "svelte-sonner"
import { get, writable } from "svelte/store" import { get, writable } from "svelte/store"
import * as v from "valibot" import * as v from "valibot"
import { setLanguageTag } from "../paraglide/runtime"
export const defaultAppConfig: AppConfigState = { export const defaultAppConfig: AppConfig = {
isInitialized: false, isInitialized: false,
platform: "macos", platform: "macos",
language: "en", language: "en",
@ -29,15 +29,14 @@ export const defaultAppConfig: AppConfigState = {
joinBetaProgram: false, joinBetaProgram: false,
onBoarded: false, onBoarded: false,
developerMode: false, developerMode: false,
appSearchPaths: [], appSearchPaths: []
loadingAnimation: "kunkun-dancing"
} }
export const appConfigLoaded = writable(false) export const appConfigLoaded = writable(false)
interface AppConfigAPI { interface AppConfigAPI {
init: () => Promise<void> init: () => Promise<void>
get: () => AppConfigState get: () => AppConfig
setTheme: (theme: ThemeConfig) => void setTheme: (theme: ThemeConfig) => void
setDevExtensionPath: (devExtensionPath: string | null) => void setDevExtensionPath: (devExtensionPath: string | null) => void
setTriggerHotkey: (triggerHotkey: string[]) => void setTriggerHotkey: (triggerHotkey: string[]) => void
@ -47,63 +46,72 @@ interface AppConfigAPI {
removeAppSearchPath: (appSearchPath: SearchPath) => void removeAppSearchPath: (appSearchPath: SearchPath) => void
} }
class AppConfigStore extends Store<AppConfigState> implements AppConfigAPI { function createAppConfig(): WithSyncStore<AppConfig & { language: string }> & AppConfigAPI {
constructor() { const store = createTauriSyncStore("app-config", defaultAppConfig)
super("app-config", defaultAppConfig, {
saveOnChange: true async function init() {
})
this.start().catch((err) => {
error("Failed to start app config store", err)
toast.error("Failed to start app config store", { description: err.message })
})
}
async init() {
debug("Initializing app config") debug("Initializing app config")
const persistStore = await load("kk-config.json", { autoSave: true })
let loadedConfig = await persistStore.get("config")
if (typeof loadedConfig === "object") {
loadedConfig = { ...defaultAppConfig, ...loadedConfig }
}
const parseRes = v.safeParse(PersistedAppConfig, loadedConfig)
if (parseRes.success) {
console.log("Parse Persisted App Config Success", parseRes.output)
const extensionsInstallDir = await getExtensionsFolder() const extensionsInstallDir = await getExtensionsFolder()
this.update((config) => ({ store.update((config) => ({
...config, ...config,
...parseRes.output,
isInitialized: true, isInitialized: true,
platform: os.platform(), extensionsInstallDir,
extensionsInstallDir platform: os.platform()
})) }))
} else {
error("Failed to parse app config, going to remove it and reinitialize")
console.error(v.flatten<typeof PersistedAppConfig>(parseRes.issues))
await persistStore.clear()
await persistStore.set("config", v.parse(PersistedAppConfig, defaultAppConfig))
}
appConfigLoaded.set(true) appConfigLoaded.set(true)
store.subscribe(async (config) => {
console.log("Saving app config", config)
await persistStore.set("config", config)
updateTheme(config.theme)
})
} }
get() { return {
return get(this) ...store,
} get: () => get(store),
setTheme(theme: ThemeConfig) { setTheme: (theme: ThemeConfig) => store.update((config) => ({ ...config, theme })),
this.update((config) => ({ ...config, theme })) setDevExtensionPath: (devExtensionPath: string | null) => {
} console.log("setDevExtensionPath", devExtensionPath)
setDevExtensionPath(devExtensionPath: string | null) { store.update((config) => ({ ...config, devExtensionPath }))
info(`setDevExtensionPath ${devExtensionPath}`) },
this.update((config) => ({ ...config, devExtensionPath })) setTriggerHotkey: (triggerHotkey: string[]) => {
} store.update((config) => ({ ...config, triggerHotkey }))
setTriggerHotkey(triggerHotkey: string[]) { },
this.update((config) => ({ ...config, triggerHotkey })) setOnBoarded: (onBoarded: boolean) => {
} store.update((config) => ({ ...config, onBoarded }))
setOnBoarded(onBoarded: boolean) { },
this.update((config) => ({ ...config, onBoarded })) setLanguage: (language: string) => {
} store.update((config) => ({ ...config, language }))
setLanguage(language: string) { },
this.update((config) => ({ ...config, language })) addAppSearchPath: (appSearchPath: SearchPath) => {
} store.update((config) => ({
addAppSearchPath(appSearchPath: SearchPath) {
this.update((config) => ({
...config, ...config,
appSearchPaths: [...config.appSearchPaths, appSearchPath] appSearchPaths: [...config.appSearchPaths, appSearchPath]
})) }))
} },
removeAppSearchPath(appSearchPath: SearchPath) { removeAppSearchPath: (appSearchPath: SearchPath) => {
this.update((config) => ({ store.update((config) => ({
...config, ...config,
appSearchPaths: config.appSearchPaths.filter((path) => path.path !== appSearchPath.path) appSearchPaths: config.appSearchPaths.filter((path) => path.path !== appSearchPath.path)
})) }))
} },
setLoadingAnimation(loadingAnimation: LoadingAnimation) { init
this.update((config) => ({ ...config, loadingAnimation }))
} }
} }
// export const appConfig = createAppConfig() export const appConfig = createAppConfig()
export const appConfig = new AppConfigStore()

View File

@ -8,8 +8,7 @@ export const defaultAppState: AppState = {
loadingBar: false, loadingBar: false,
defaultAction: "", defaultAction: "",
actionPanel: undefined, actionPanel: undefined,
lockHideOnBlur: false, // when dialog is open, we don't hide the app, we lock the hide on blur and unlock when dialog is closed lockHideOnBlur: false // when dialog is open, we don't hide the app, we lock the hide on blur and unlock when dialog is closed
fullScreenLoading: false
} }
interface AppStateAPI { interface AppStateAPI {
@ -19,7 +18,6 @@ interface AppStateAPI {
setDefaultAction: (defaultAction: string | null) => void setDefaultAction: (defaultAction: string | null) => void
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => void setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => void
setLockHideOnBlur: (lockHideOnBlur: boolean) => void setLockHideOnBlur: (lockHideOnBlur: boolean) => void
setFullScreenLoading: (fullScreenLoading: boolean) => void
} }
function createAppState(): Writable<AppState> & AppStateAPI { function createAppState(): Writable<AppState> & AppStateAPI {
@ -42,9 +40,6 @@ function createAppState(): Writable<AppState> & AppStateAPI {
}, },
setLockHideOnBlur: (lockHideOnBlur: boolean) => { setLockHideOnBlur: (lockHideOnBlur: boolean) => {
store.update((state) => ({ ...state, lockHideOnBlur })) store.update((state) => ({ ...state, lockHideOnBlur }))
},
setFullScreenLoading: (fullScreenLoading: boolean) => {
store.update((state) => ({ ...state, fullScreenLoading }))
} }
} }
} }

View File

@ -7,7 +7,7 @@ import Fuse from "fuse.js"
import { derived, get, writable } from "svelte/store" import { derived, get, writable } from "svelte/store"
import { appState } from "./appState" import { appState } from "./appState"
const fuse = new Fuse<AppInfo>([], { export const fuse = new Fuse<AppInfo>([], {
includeScore: true, includeScore: true,
threshold: 0.2, threshold: 0.2,
keys: ["name"] keys: ["name"]

View File

@ -1,5 +1,5 @@
import { db } from "@kksh/api/commands"
import type { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models" import type { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import * as extAPI from "@kksh/extension" import * as extAPI from "@kksh/extension"
import * as path from "@tauri-apps/api/path" import * as path from "@tauri-apps/api/path"
import Fuse from "fuse.js" import Fuse from "fuse.js"

View File

@ -6,7 +6,7 @@ import Fuse from "fuse.js"
import { derived, get, writable, type Writable } from "svelte/store" import { derived, get, writable, type Writable } from "svelte/store"
import { appState } from "./appState" import { appState } from "./appState"
const fuse = new Fuse<QuickLink>([], { export const fuse = new Fuse<QuickLink>([], {
includeScore: true, includeScore: true,
threshold: 0.2, threshold: 0.2,
keys: ["name"] keys: ["name"]

View File

@ -1,13 +1,19 @@
import { SupabaseAPI } from "@kksh/supabase/api"
import type { Database } from "@kksh/supabase/types"
import * as sb from "@supabase/supabase-js" import * as sb from "@supabase/supabase-js"
import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./constants" import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./constants"
// export const supabase = createSB(SUPABASE_URL, SUPABASE_ANON_KEY) // export const supabase = createSB(SUPABASE_URL, SUPABASE_ANON_KEY)
export const supabase: sb.SupabaseClient = sb.createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { export const supabase: sb.SupabaseClient<Database> = sb.createClient<Database>(
SUPABASE_URL,
SUPABASE_ANON_KEY,
{
auth: { auth: {
flowType: "pkce" flowType: "pkce"
} }
}) }
)
export const storage = supabase.storage export const storage = supabase.storage
export const supabaseExtensionsStorage = supabase.storage.from("extensions") export const supabaseExtensionsStorage = supabase.storage.from("extensions")
// export const supabaseAPI = new SupabaseAPI(supabase) export const supabaseAPI = new SupabaseAPI(supabase)

View File

@ -1,44 +0,0 @@
import { proxyDB, schema } from "@kksh/drizzle"
import { getExtClipboard } from "@kksh/drizzle/api"
import { error, info } from "@tauri-apps/plugin-log"
import * as orm from "drizzle-orm"
/**
* For now, simply delete all clipboard data older than 10 days
*/
export async function cleanClipboard() {
const nDays = 10
const clipboardExt = await getExtClipboard()
const nDaysAgo = new Date()
nDaysAgo.setDate(nDaysAgo.getDate() - nDays)
try {
// Select data older than 10 days to check what will be deleted
const oldClipboardData = await proxyDB
.select({ count: orm.count() })
.from(schema.extensionData)
.where(
orm.and(
orm.eq(schema.extensionData.extId, clipboardExt.extId),
orm.lt(schema.extensionData.createdAt, nDaysAgo.toISOString())
)
)
const nLinesToDelete = oldClipboardData.at(0)?.count ?? 0
console.info(`Found ${nLinesToDelete} clipboard entries older than ${nDays} days to clean up`)
info(`Found ${nLinesToDelete} clipboard entries older than ${nDays} days to clean up`)
// Now delete the old data
const deleted = await proxyDB
.delete(schema.extensionData)
.where(
orm.and(
orm.eq(schema.extensionData.extId, clipboardExt.extId),
orm.lt(schema.extensionData.createdAt, nDaysAgo.toISOString())
)
)
console.log("deleted", deleted)
} catch (e) {
error(`Error during clipboard cleanup: ${e}`)
}
}

View File

@ -1,13 +0,0 @@
import { proxyDB } from "@kksh/drizzle"
import { error, info } from "@tauri-apps/plugin-log"
import { sql } from "drizzle-orm"
export async function vacuumSqlite() {
const statement = sql`VACUUM`
try {
await proxyDB.run(statement)
info("Vacuumed sqlite")
} catch (error) {
console.error(`Failed to vacuum sqlite: ${error}`)
}
}

View File

@ -48,7 +48,8 @@ export async function registerAppHotkey(hotkeyStr: string) {
mainWin.setFocus() mainWin.setFocus()
} }
} else { } else {
mainWin.show().then(() => mainWin.setFocus()) mainWin.show()
mainWin.setFocus()
} }
} }
}) })

View File

@ -1,16 +1,14 @@
import { appConfig, extensions } from "@/stores" import { appConfig, extensions } from "@/stores"
import { getCurrentWindow } from "@tauri-apps/api/window" import { getCurrentWindow } from "@tauri-apps/api/window"
import { error, info } from "@tauri-apps/plugin-log" import { info } from "@tauri-apps/plugin-log"
import { dev } from "$app/environment" import { dev } from "$app/environment"
import { cleanClipboard } from "./clipboard"
import { vacuumSqlite } from "./db"
import { mapKeyToTauriKey, registerAppHotkey } from "./hotkey" import { mapKeyToTauriKey, registerAppHotkey } from "./hotkey"
import { listenToReloadOneExtension } from "./tauri-events" import { listenToReloadOneExtension } from "./tauri-events"
/** /**
* Initialize the app * Initialize the app
*/ */
export async function init() { export function init() {
const window = getCurrentWindow() const window = getCurrentWindow()
if (window.label === "main") { if (window.label === "main") {
initMainWindow() initMainWindow()
@ -19,14 +17,7 @@ export async function init() {
extensions.reloadExtension(extPath) extensions.reloadExtension(extPath)
}) })
} }
await cleanClipboard()
.then(() => {
info("Cleaned clipboard")
})
.catch((e) => {
error(`Failed to clean clipboard: ${e}`)
})
vacuumSqlite()
if (!dev) { if (!dev) {
// document.addEventListener("contextmenu", function (event) { // document.addEventListener("contextmenu", function (event) {
// event.preventDefault() // event.preventDefault()

View File

@ -97,7 +97,7 @@ export async function globalKeyDownHandler(e: KeyboardEvent) {
await appWin.hide() await appWin.hide()
location.reload() location.reload()
setTimeout(() => { setTimeout(() => {
appWin.show().then(() => appWin.setFocus()) appWin.show()
}, 1_000) }, 1_000)
} }
} }

View File

@ -1,19 +0,0 @@
import { parseAPIVersion } from "@kksh/extension/load"
import type { ExtPackageJsonExtra } from "@kunkunapi/src/models/manifest"
import semver from "semver"
/**
* Decide the serialization method for kkrpc based on the api version
* apiVersion is populated in loadExtensionManifestFromDisk, but we parse it again
* @param apiVersion - The version of the @kksh/api
* @returns "superjson" or "json"
*/
export function decideKkrpcSerialization(ext: ExtPackageJsonExtra): "superjson" | "json" {
const apiVersion = parseAPIVersion(ext.dependencies || {})
if (apiVersion && semver.lte(apiVersion, "0.1.5")) {
// 0.1.6 is the first version that supports superjson and default to use superjson
return "json"
}
// fallback default to use superjson, some extensions might not install @kksh/api
return "superjson"
}

View File

@ -1,7 +1,8 @@
import { extensions } from "@/stores" import { extensions } from "@/stores"
import { supabaseAPI } from "@/supabase"
import { isCompatible } from "@kksh/api" import { isCompatible } from "@kksh/api"
import type { ExtPackageJsonExtra } from "@kksh/api/models" import type { ExtPackageJsonExtra } from "@kksh/api/models"
import { getExtensionsLatestPublishByIdentifier } from "@kksh/sdk" import { greaterThan } from "@std/semver"
import { relaunch } from "@tauri-apps/plugin-process" import { relaunch } from "@tauri-apps/plugin-process"
import { check } from "@tauri-apps/plugin-updater" import { check } from "@tauri-apps/plugin-updater"
import { gt } from "semver" import { gt } from "semver"
@ -31,22 +32,11 @@ export async function checkSingleExtensionUpdate(
installedExt: ExtPackageJsonExtra, installedExt: ExtPackageJsonExtra,
autoupgrade: boolean autoupgrade: boolean
) { ) {
const { const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
data: sbExt, installedExt.kunkun.identifier
error,
response
} = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: "RAG"
}
})
// const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
// installedExt.kunkun.identifier
// )
if (error) {
return toast.error(
`Failed to check update for ${installedExt.kunkun.identifier}: ${error} (${response.status})`
) )
if (error) {
return toast.error(`Failed to check update for ${installedExt.kunkun.identifier}: ${error}`)
} }
if (!sbExt) { if (!sbExt) {
@ -59,7 +49,10 @@ export async function checkSingleExtensionUpdate(
) { ) {
if (autoupgrade) { if (autoupgrade) {
await extensions await extensions
.upgradeStoreExtension(sbExt.identifier, sbExt.tarball_path) .upgradeStoreExtension(
sbExt.identifier,
supabaseAPI.translateExtensionFilePathToUrl(sbExt.tarball_path)
)
.then(() => { .then(() => {
toast.success(`${sbExt.name} upgraded`, { toast.success(`${sbExt.name} upgraded`, {
description: `From ${installedExt.version} to ${sbExt.version}` description: `From ${installedExt.version} to ${sbExt.version}`

View File

@ -12,7 +12,6 @@
<svelte:window on:keydown={handleKeyDown} /> <svelte:window on:keydown={handleKeyDown} />
<div class="fixed h-12 w-full" data-tauri-drag-region></div>
<Layouts.Center class="min-h-screen py-5"> <Layouts.Center class="min-h-screen py-5">
<Error.RawErrorJSONPreset <Error.RawErrorJSONPreset
title="Error" title="Error"

View File

@ -2,8 +2,6 @@
import { ParaglideJS } from "@inlang/paraglide-sveltekit" import { ParaglideJS } from "@inlang/paraglide-sveltekit"
import { i18n } from "$lib/i18n" import { i18n } from "$lib/i18n"
import "../app.css" import "../app.css"
import FullScreenLoading from "@/components/common/FullScreenLoading.svelte"
import { appState } from "@/stores/appState"
import { ModeWatcher, ThemeWrapper } from "@kksh/svelte5" import { ModeWatcher, ThemeWrapper } from "@kksh/svelte5"
import { Toaster } from "svelte-sonner" import { Toaster } from "svelte-sonner"
@ -14,9 +12,6 @@
<ModeWatcher /> <ModeWatcher />
<Toaster richColors closeButton /> <Toaster richColors closeButton />
<ThemeWrapper> <ThemeWrapper>
{#if $appState.fullScreenLoading}
<FullScreenLoading class="bg-background absolute inset-0 z-50" />
{/if}
{@render children()} {@render children()}
</ThemeWrapper> </ThemeWrapper>
</ParaglideJS> </ParaglideJS>

View File

@ -1,15 +1,5 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { browser } from "$app/environment"
// Tauri doesn't have a Node.js server to do proper SSR // Tauri doesn't have a Node.js server to do proper SSR
// so we will use adapter-static to prerender the app (SSG) // so we will use adapter-static to prerender the app (SSG)
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
export const prerender = true export const prerender = true
export const ssr = false export const ssr = false
export const load = () => {
if (browser) {
const win = getCurrentWebviewWindow()
return { win }
}
}

View File

@ -46,14 +46,13 @@
}) })
}) })
let { children, data } = $props() let { children } = $props()
const unlisteners: UnlistenFn[] = [] const unlisteners: UnlistenFn[] = []
onDestroy(() => { onDestroy(() => {
unlisteners.forEach((unlistener) => unlistener()) unlisteners.forEach((unlistener) => unlistener())
}) })
onMount(async () => { onMount(async () => {
await attachConsole().then((unlistener) => unlisteners.push(unlistener)) attachConsole().then((unlistener) => unlisteners.push(unlistener))
initDeeplink().then((unlistener) => unlisteners.push(unlistener)) initDeeplink().then((unlistener) => unlisteners.push(unlistener))
shellx shellx
.fixPathEnv() .fixPathEnv()
@ -101,7 +100,7 @@
}) })
) )
} }
data.win?.show().then(() => data.win?.setFocus()) getCurrentWebviewWindow().show()
}) })
</script> </script>

View File

@ -1,11 +1,7 @@
import { getExtensionsFolder, IS_IN_TAURI } from "@/constants" import { getExtensionsFolder, IS_IN_TAURI } from "@/constants"
import * as path from "@tauri-apps/api/path"
import { error } from "@tauri-apps/plugin-log" import { error } from "@tauri-apps/plugin-log"
import { setStoreCollectionPath } from "@tauri-store/svelte"
import type { LayoutLoad } from "./$types" import type { LayoutLoad } from "./$types"
export const load: LayoutLoad = async () => { export const load: LayoutLoad = async () => {
const appDataPath = await path.appDataDir() return { extsInstallDir: IS_IN_TAURI ? await getExtensionsFolder() : "" }
await setStoreCollectionPath(await path.join(appDataPath, "kk-config"))
return { extsInstallDir: IS_IN_TAURI ? await getExtensionsFolder() : "", appDataPath }
} }

View File

@ -31,22 +31,14 @@
SystemCmds SystemCmds
} from "@kksh/ui/main" } from "@kksh/ui/main"
import { cn } from "@kksh/ui/utils" import { cn } from "@kksh/ui/utils"
import { Ext } from "@kunkunapi/src/models/extension"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow" import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { getCurrentWindow, Window } from "@tauri-apps/api/window" import { getCurrentWindow, Window } from "@tauri-apps/api/window"
import { platform } from "@tauri-apps/plugin-os" import { platform } from "@tauri-apps/plugin-os"
import { exit } from "@tauri-apps/plugin-process" import { exit } from "@tauri-apps/plugin-process"
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { import { ArrowBigUpIcon, CircleXIcon, EllipsisVerticalIcon, RefreshCcwIcon } from "lucide-svelte"
ArrowBigUpIcon,
CircleXIcon,
EllipsisVerticalIcon,
RefreshCcwIcon,
SettingsIcon
} from "lucide-svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import { Inspect } from "svelte-inspect-value" import { Inspect } from "svelte-inspect-value"
import * as v from "valibot"
const win = getCurrentWindow() const win = getCurrentWindow()
let inputEle: HTMLInputElement | null = $state(null) let inputEle: HTMLInputElement | null = $state(null)
@ -66,16 +58,12 @@
if (splashscreenWin) { if (splashscreenWin) {
splashscreenWin.close() splashscreenWin.close()
} }
win.show().then(() => win.setFocus()) win.show()
}) })
win.onFocusChanged(({ payload: focused }) => { win.onFocusChanged(({ payload: focused }) => {
if (focused) { if (focused) {
win win.show()
.show()
.then(() => win.setFocus())
.finally(() => {
inputEle?.focus() inputEle?.focus()
})
} }
}) })
inputEle?.focus() inputEle?.focus()
@ -83,7 +71,6 @@
// wait for appConfig store to be loaded, it's async and saved to disk when changed, so we use another store appConfigLoaded // wait for appConfig store to be loaded, it's async and saved to disk when changed, so we use another store appConfigLoaded
// to keep track of the loading status // to keep track of the loading status
if (loaded) { if (loaded) {
console.log("appConfig.get().onBoarded", appConfig.get().onBoarded)
if (!appConfig.get().onBoarded) { if (!appConfig.get().onBoarded) {
setTimeout(() => { setTimeout(() => {
goto(i18n.resolveRoute("/app/help/onboarding")) goto(i18n.resolveRoute("/app/help/onboarding"))
@ -116,7 +103,6 @@
<Inspect name="devStoreExtCmds" value={$devStoreExtCmds} /> <Inspect name="devStoreExtCmds" value={$devStoreExtCmds} />
<Inspect name="$appState.searchTerm" value={$appState.searchTerm} /> <Inspect name="$appState.searchTerm" value={$appState.searchTerm} />
--> -->
<Command.Root <Command.Root
class={cn("h-screen rounded-lg shadow-md")} class={cn("h-screen rounded-lg shadow-md")}
bind:value={$appState.highlightedCmd} bind:value={$appState.highlightedCmd}
@ -192,8 +178,8 @@
<span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R </span> <span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R </span>
</DropdownMenu.Shortcut> </DropdownMenu.Shortcut>
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item onclick={() => goto(i18n.resolveRoute("/app/settings"))}> <DropdownMenu.Item onclick={() => location.reload()}>
<SettingsIcon class="mr-2 h-4 w-4 text-green-500" /> <RefreshCcwIcon class="mr-2 h-4 w-4 text-green-500" />
{m.home_command_input_dropdown_open_preference()} {m.home_command_input_dropdown_open_preference()}
<DropdownMenu.Shortcut> <DropdownMenu.Shortcut>
{#if platform() === "macos"} {#if platform() === "macos"}

View File

@ -3,9 +3,8 @@
import { goHome } from "@/utils/route" import { goHome } from "@/utils/route"
import { listenToNewClipboardItem, listenToWindowFocus } from "@/utils/tauri-events" import { listenToNewClipboardItem, listenToWindowFocus } from "@/utils/tauri-events"
import Icon from "@iconify/svelte" import Icon from "@iconify/svelte"
import { ClipboardContentType } from "@kksh/api/commands" import { ClipboardContentType, db } from "@kksh/api/commands"
import { SearchModeEnum, SQLSortOrderEnum, type ExtData } from "@kksh/api/models" import { SearchModeEnum, SQLSortOrderEnum, type ExtData } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { Button, Command, Resizable } from "@kksh/svelte5" import { Button, Command, Resizable } from "@kksh/svelte5"
import { Constants } from "@kksh/ui" import { Constants } from "@kksh/ui"
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main" import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { cn } from "@/utils" import { cn } from "@/utils"
import { db } from "@kksh/api/commands"
import type { ExtData } from "@kksh/api/models" import type { ExtData } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { Resizable, Separator } from "@kksh/svelte5" import { Resizable, Separator } from "@kksh/svelte5"
import { convertFileSrc } from "@tauri-apps/api/core" import { convertFileSrc } from "@tauri-apps/api/core"
import DOMPurify from "dompurify" import DOMPurify from "dompurify"

View File

@ -8,8 +8,6 @@
import * as userInput from "tauri-plugin-user-input-api" import * as userInput from "tauri-plugin-user-input-api"
import { type InputEvent } from "tauri-plugin-user-input-api" import { type InputEvent } from "tauri-plugin-user-input-api"
let { data } = $props()
const SymbolMap = { const SymbolMap = {
Alt: "⎇", Alt: "⎇",
AltGr: "⌥", AltGr: "⌥",
@ -99,7 +97,10 @@
} }
$effect(() => { $effect(() => {
data.win?.show().then(() => data.win?.setFocus()) const win = getCurrentWebviewWindow()
if (win) {
win.show()
}
userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease]) userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease])
userInput.startListening((evt: InputEvent) => { userInput.startListening((evt: InputEvent) => {

View File

@ -8,24 +8,23 @@
import * as clipboard from "tauri-plugin-clipboard-api" import * as clipboard from "tauri-plugin-clipboard-api"
let image = $state<string | null>(null) let image = $state<string | null>(null)
let { data } = $props() const appWin = getCurrentWebviewWindow()
let originalSize = $state<{ width: number; height: number } | null>(null) let originalSize = $state<{ width: number; height: number } | null>(null)
let originalScaleFactor = $state<number | null>(null) let originalScaleFactor = $state<number | null>(null)
let scale = $state<number>(1) let scale = $state<number>(1)
let currentSize = $derived( let currentSize = $derived(
originalSize ? { width: originalSize.width * scale, height: originalSize.height * scale } : null originalSize ? { width: originalSize.width * scale, height: originalSize.height * scale } : null
) )
const win = getCurrentWebviewWindow()
$effect(() => { $effect(() => {
if (currentSize) { if (currentSize) {
win.setSize(new LogicalSize(currentSize.width, currentSize.height)) appWin.setSize(new LogicalSize(currentSize.width, currentSize.height))
} }
}) })
async function getWindowSize() { async function getWindowSize() {
const size = await win.outerSize() const size = await appWin.outerSize()
const scaleFactor = originalScaleFactor ?? (await win.scaleFactor()) const scaleFactor = originalScaleFactor ?? (await appWin.scaleFactor())
const logicalSize = size.toLogical(scaleFactor) const logicalSize = size.toLogical(scaleFactor)
return { logicalSize, scaleFactor } return { logicalSize, scaleFactor }
} }
@ -37,7 +36,7 @@
image = b64 image = b64
}) })
.finally(() => { .finally(() => {
data.win?.show().then(() => data.win?.setFocus()) appWin.show()
}) })
const { logicalSize, scaleFactor } = await getWindowSize() const { logicalSize, scaleFactor } = await getWindowSize()
originalSize = { width: logicalSize.width, height: logicalSize.height } originalSize = { width: logicalSize.width, height: logicalSize.height }
@ -68,13 +67,13 @@
<svelte:window <svelte:window
on:keydown={(e) => { on:keydown={(e) => {
if (e.key === "Escape") { if (e.key === "Escape") {
win.close() appWin.close()
} }
}} }}
/> />
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => win.close()}> <Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => appWin.close()}
<CircleX /> ><CircleX /></Button
</Button> >
<main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region> <main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region>
{#if image} {#if image}
<img <img

View File

@ -2,13 +2,14 @@
import { getExtensionsFolder } from "@/constants" import { getExtensionsFolder } from "@/constants"
import { appState, extensions } from "@/stores" import { appState, extensions } from "@/stores"
import { keys } from "@/stores/keys" import { keys } from "@/stores/keys"
import { supabaseAPI } from "@/supabase"
import { goBackOnEscapeClearSearchTerm, goHomeOnEscapeClearSearchTerm } from "@/utils/key"
import { goBack, goHome } from "@/utils/route" import { goBack, goHome } from "@/utils/route"
import { Action as ActionSchema, ExtensionStoreListItem, ExtPublish } from "@kksh/api/models" import { Action as ActionSchema } from "@kksh/api/models"
import { Action } from "@kksh/api/ui" import { Action } from "@kksh/api/ui"
import { import { SBExt } from "@kksh/supabase/models"
getExtensionsLatestPublishByIdentifier, import type { ExtPublishMetadata } from "@kksh/supabase/models"
postExtensionsIncrementDownloads import { type Tables } from "@kksh/supabase/types"
} from "@kksh/sdk"
import { Button, Command } from "@kksh/svelte5" import { Button, Command } from "@kksh/svelte5"
import { Constants } from "@kksh/ui" import { Constants } from "@kksh/ui"
import { ExtListItem } from "@kksh/ui/extension" import { ExtListItem } from "@kksh/ui/extension"
@ -16,9 +17,8 @@
import { platform } from "@tauri-apps/plugin-os" import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { ArrowLeft } from "lucide-svelte" import { ArrowLeft } from "lucide-svelte"
import { onMount, type Snippet } from "svelte" import type { Snippet } from "svelte"
import { toast } from "svelte-sonner" import { toast } from "svelte-sonner"
import type { Action as SvelteAction } from "svelte/action"
import { getInstallExtras } from "./[identifier]/helper.js" import { getInstallExtras } from "./[identifier]/helper.js"
let { data } = $props() let { data } = $props()
@ -38,89 +38,51 @@
// ) // )
// } // }
onMount(() => { function onExtItemSelected(ext: SBExt) {
// setTimeout(() => {
// console.log("focus", listviewInputRef)
// listviewInputRef?.focus()
// }, 3_000)
})
const inputAction: SvelteAction = (node) => {
// the node has been mounted in the DOM
$effect(() => {
// setup goes here
console.log("inputAction", node)
listviewInputRef?.focus()
return () => {
// teardown goes here
}
})
}
$effect(() => {
function docKeyDown(e: KeyboardEvent) {
if (e.key === "/") {
listviewInputRef?.focus()
}
}
document.addEventListener("keydown", docKeyDown)
return () => {
document.removeEventListener("keydown", docKeyDown)
}
})
function onExtItemSelected(ext: ExtensionStoreListItem) {
goto(`./store/${ext.identifier}`) goto(`./store/${ext.identifier}`)
} }
async function onExtItemUpgrade(ext: ExtensionStoreListItem) { async function onExtItemUpgrade(ext: SBExt) {
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({ const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
path: { if (res.error)
identifier: ext.identifier
}
})
if (error)
return toast.error("Fail to get latest extension", { return toast.error("Fail to get latest extension", {
description: error.error description: res.error.message
}) })
const installExtras = await getInstallExtras(data?.metadata) const tarballUrl = res.data.tarball_path.startsWith("http")
? res.data.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
const installExtras = await getInstallExtras(
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
)
return extensions return extensions
.upgradeStoreExtension(ext.identifier, data.tarball_path, installExtras) .upgradeStoreExtension(ext.identifier, tarballUrl, installExtras)
.then((newExt) => { .then((newExt) => {
toast.success(`${ext.name} Upgraded to ${newExt.version}`) toast.success(`${ext.name} Upgraded to ${newExt.version}`)
}) })
} }
async function onExtItemInstall(ext: ExtensionStoreListItem) { async function onExtItemInstall(ext: SBExt) {
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({ const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
path: { if (res.error)
identifier: ext.identifier
}
})
if (error)
return toast.error("Fail to get latest extension", { return toast.error("Fail to get latest extension", {
description: error.error description: res.error.message
}) })
const installExtras = await getInstallExtras(data?.metadata) const tarballUrl = res.data.tarball_path.startsWith("http")
? res.data.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
const installExtras = await getInstallExtras(
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
)
const installDir = await getExtensionsFolder() const installDir = await getExtensionsFolder()
return extensions return extensions
.installFromTarballUrl(data.tarball_path, installDir, installExtras) .installFromTarballUrl(tarballUrl, installDir, installExtras)
.then(() => toast.success(`Plugin ${ext.name} Installed`)) .then(() => toast.success(`Plugin ${ext.name} Installed`))
.then(() => .then(() =>
postExtensionsIncrementDownloads({ supabaseAPI.incrementDownloads({
body: {
identifier: ext.identifier, identifier: ext.identifier,
version: ext.version version: ext.version
}
}) })
.then(({ error }) => {
if (error) {
console.error(error)
}
})
.catch(console.error)
) )
} }
@ -164,7 +126,7 @@
}) })
</script> </script>
<svelte:window {onkeydown} /> <svelte:window on:keydown={onkeydown} />
{#snippet leftSlot()} {#snippet leftSlot()}
<Button <Button
variant="outline" variant="outline"
@ -180,7 +142,7 @@
<CustomCommandInput <CustomCommandInput
bind:ref={listviewInputRef} bind:ref={listviewInputRef}
autofocus autofocus
placeholder="Type / to focus" placeholder="Type a command or search..."
leftSlot={leftSlot as Snippet} leftSlot={leftSlot as Snippet}
bind:value={$appState.searchTerm} bind:value={$appState.searchTerm}
onkeydown={(e) => { onkeydown={(e) => {
@ -205,9 +167,7 @@
{ext} {ext}
installedVersion={$installedExtsMap[ext.identifier]} installedVersion={$installedExtsMap[ext.identifier]}
isUpgradable={!!$upgradableExpsMap[ext.identifier]} isUpgradable={!!$upgradableExpsMap[ext.identifier]}
onSelect={() => { onSelect={() => {}}
onExtItemSelected(ext)
}}
onUpgrade={() => onExtItemUpgrade(ext)} onUpgrade={() => onExtItemUpgrade(ext)}
onInstall={() => onExtItemInstall(ext)} onInstall={() => onExtItemInstall(ext)}
/> />

View File

@ -1,47 +1,43 @@
import { appConfig, appState, extensions, installedStoreExts } from "@/stores" import { appConfig, extensions, installedStoreExts } from "@/stores"
import { goHome } from "@/utils/route" import { supabaseAPI } from "@/supabase"
// import { supabaseAPI } from "@/supabase" import type { ExtPackageJsonExtra } from "@kksh/api/models"
import type { ExtensionStoreListItem, ExtPackageJsonExtra } from "@kksh/api/models"
import { isExtPathInDev, isUpgradable } from "@kksh/extension" import { isExtPathInDev, isUpgradable } from "@kksh/extension"
import { getExtensionsStoreList } from "@kksh/sdk" import { SBExt } from "@kksh/supabase/models"
import { toast } from "svelte-sonner" import { error } from "@sveltejs/kit"
import { derived, get, type Readable } from "svelte/store" import { derived, get, type Readable } from "svelte/store"
import type { PageLoad } from "./$types" import type { PageLoad } from "./$types"
export const load: PageLoad = (): Promise<{ export const load: PageLoad = async (): Promise<{
storeExtList: ExtensionStoreListItem[] storeExtList: SBExt[]
installedStoreExts: Readable<ExtPackageJsonExtra[]> installedStoreExts: Readable<ExtPackageJsonExtra[]>
installedExtsMap: Readable<Record<string, string>> installedExtsMap: Readable<Record<string, string>>
upgradableExpsMap: Readable<Record<string, boolean>> upgradableExpsMap: Readable<Record<string, boolean>>
}> => { }> => {
appState.setFullScreenLoading(true) const storeExtList = await supabaseAPI.getExtList()
return getExtensionsStoreList() // map identifier to extItem
.then(({ data: storeExtList, error, response }) => {
storeExtList = storeExtList ?? []
if (error) {
toast.error(`Failed to load extension store: ${error} (${response.status})`)
goHome()
}
const storeExtsMap = Object.fromEntries(storeExtList.map((ext) => [ext.identifier, ext])) const storeExtsMap = Object.fromEntries(storeExtList.map((ext) => [ext.identifier, ext]))
const _appConfig = get(appConfig)
// const installedStoreExts = derived(extensions, ($extensions) => {
// if (!_appConfig.extensionPath) return []
// return $extensions.filter((ext) => !isExtPathInDev(_appConfig.extensionPath!, ext.extPath))
// })
// map installed extension identifier to version
const installedExtsMap = derived(installedStoreExts, ($exts) => const installedExtsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version])) Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version]))
) )
const upgradableExpsMap = derived(installedStoreExts, ($exts) => const upgradableExpsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries( Object.fromEntries(
$exts.map((ext) => { $exts.map((ext) => {
const dbExt: ExtensionStoreListItem | undefined = storeExtsMap[ext.kunkun.identifier] const dbExt: SBExt | undefined = storeExtsMap[ext.kunkun.identifier]
return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false] return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false]
}) })
) )
) )
return { return {
storeExtList, storeExtList,
installedStoreExts, installedStoreExts,
installedExtsMap, installedExtsMap,
upgradableExpsMap upgradableExpsMap
} }
})
.finally(() => {
appState.setFullScreenLoading(false)
})
} }

View File

@ -2,8 +2,11 @@
import { getExtensionsFolder } from "@/constants.js" import { getExtensionsFolder } from "@/constants.js"
import { i18n } from "@/i18n.js" import { i18n } from "@/i18n.js"
import { extensions, installedStoreExts } from "@/stores/extensions.js" import { extensions, installedStoreExts } from "@/stores/extensions.js"
import { DBExtension, ExtensionStoreListItem, ExtPackageJson, ExtPublish } from "@kksh/api/models" import { supabaseAPI } from "@/supabase"
import { postExtensionsIncrementDownloads } from "@kksh/sdk" import { goBack } from "@/utils/route.js"
import { ExtPackageJson } from "@kksh/api/models"
import { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
import { Button } from "@kksh/svelte5" import { Button } from "@kksh/svelte5"
import { cn } from "@kksh/svelte5/utils" import { cn } from "@kksh/svelte5/utils"
import { Constants } from "@kksh/ui" import { Constants } from "@kksh/ui"
@ -19,8 +22,10 @@
import { getInstallExtras } from "./helper" import { getInstallExtras } from "./helper"
const { data } = $props() const { data } = $props()
const extPublish = $derived(data.extPublish) const extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata } = $derived(
const ext = $derived(data.ext) data.extPublish
)
const ext: Tables<"extensions"> = $derived(data.ext)
const manifest = $derived(data.manifest) const manifest = $derived(data.manifest)
const installedExt = storeDerived(installedStoreExts, ($e) => { const installedExt = storeDerived(installedStoreExts, ($e) => {
return $e.find((e) => e.kunkun.identifier === extPublish.identifier) return $e.find((e) => e.kunkun.identifier === extPublish.identifier)
@ -72,29 +77,26 @@
}, 500) }, 500)
}) })
const demoImages = $derived(extPublish.demo_images) const demoImages = $derived(
extPublish.demo_images.map((src) => supabaseAPI.translateExtensionFilePathToUrl(src))
)
async function onInstallSelected() { async function onInstallSelected() {
loading.install = true loading.install = true
const installExtras = await getInstallExtras(extPublish.metadata) const tarballUrl = extPublish.tarball_path.startsWith("http")
? extPublish.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
const installExtras = await getInstallExtras(extPublish)
const installDir = await getExtensionsFolder() const installDir = await getExtensionsFolder()
return extensions return extensions
.installFromTarballUrl(extPublish.tarball_path, installDir, installExtras) .installFromTarballUrl(tarballUrl, installDir, installExtras)
.then(() => toast.success(`Plugin ${extPublish.name} Installed`)) .then(() => toast.success(`Plugin ${extPublish.name} Installed`))
.then((loadedExt) => { .then((loadedExt) => {
info(`Successfully installed ${extPublish.name}`) info(`Successfully installed ${extPublish.name}`)
postExtensionsIncrementDownloads({ supabaseAPI.incrementDownloads({
body: {
identifier: extPublish.identifier, identifier: extPublish.identifier,
version: extPublish.version version: extPublish.version
}
}) })
.then(({ error }) => {
if (error) {
console.error(error)
}
})
.catch(console.error)
showBtn.install = false showBtn.install = false
showBtn.uninstall = true showBtn.uninstall = true
}) })
@ -109,8 +111,9 @@
function onUpgradeSelected() { function onUpgradeSelected() {
loading.upgrade = true loading.upgrade = true
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
return extensions return extensions
.upgradeStoreExtension(extPublish.identifier, extPublish.tarball_path) .upgradeStoreExtension(extPublish.identifier, tarballUrl)
.then((newExt) => { .then((newExt) => {
toast.success( toast.success(
`${extPublish.name} Upgraded from ${$installedExt?.version} to ${newExt.version}` `${extPublish.name} Upgraded from ${$installedExt?.version} to ${newExt.version}`

View File

@ -1,57 +1,53 @@
import { appState } from "@/stores" import { extensions } from "@/stores"
import { DBExtension, ExtPublish, KunkunExtManifest } from "@kksh/api/models" import { supabaseAPI } from "@/supabase"
import { getExtensionsByIdentifier, getExtensionsLatestPublishByIdentifier } from "@kksh/sdk" import { KunkunExtManifest, type ExtPackageJsonExtra } from "@kksh/api/models"
import { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
import { error } from "@sveltejs/kit" import { error } from "@sveltejs/kit"
import { toast } from "svelte-sonner"
import * as v from "valibot" import * as v from "valibot"
import type { PageLoad } from "./$types" import type { PageLoad } from "./$types"
export const load: PageLoad = ({ export const load: PageLoad = async ({
params params
}): Promise<{ }): Promise<{
extPublish: ExtPublish extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
ext: DBExtension ext: Tables<"extensions">
manifest: KunkunExtManifest manifest: KunkunExtManifest
params: { params: {
identifier: string identifier: string
} }
}> => { }> => {
appState.setFullScreenLoading(true) const { error: dbError, data: extPublish } = await supabaseAPI.getLatestExtPublish(
return getExtensionsLatestPublishByIdentifier({ params.identifier
path: { )
identifier: params.identifier const metadataParse = v.safeParse(ExtPublishMetadata, extPublish?.metadata ?? {})
} if (dbError) {
}) return error(400, {
.then(async ({ data: extPublish, error: err, response }) => { message: dbError.message
if (err || !extPublish) {
return error(response.status, {
message: "Failed to get extension publish"
}) })
} }
const { const metadata = metadataParse.success ? metadataParse.output : {}
data: ext, const parseManifest = v.safeParse(KunkunExtManifest, extPublish.manifest)
error: extError, if (!parseManifest.success) {
response: extRes const errMsg = "Invalid extension manifest, you may need to upgrade your app."
} = await getExtensionsByIdentifier({ toast.error(errMsg)
path: { throw error(400, errMsg)
identifier: params.identifier
} }
})
if (extError || !ext) { const { data: ext, error: extError } = await supabaseAPI.getExtension(params.identifier)
console.error(extError) if (extError) {
return error(extRes.status, { return error(400, {
message: extError.error || "Failed to get extension" message: extError.message
}) })
} }
return { return {
extPublish: v.parse(ExtPublish, extPublish), extPublish: { ...extPublish, metadata },
ext, ext,
manifest: v.parse(KunkunExtManifest, extPublish.manifest), params,
params manifest: parseManifest.output
} }
})
.finally(() => {
appState.setFullScreenLoading(false)
})
} }
export const csr = true export const csr = true

View File

@ -1,15 +1,15 @@
import type { ExtPublishMetadata } from "@kunkunapi/src/models" import type { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
export async function getInstallExtras(extMetadata?: { export async function getInstallExtras(
sourceType?: string ext: Tables<"ext_publish"> & { metadata?: ExtPublishMetadata }
source?: string ): Promise<{ overwritePackageJson?: string }> {
}): Promise<{ overwritePackageJson?: string }> {
const extras: { overwritePackageJson?: string } = {} const extras: { overwritePackageJson?: string } = {}
if (extMetadata?.sourceType) { if (ext.metadata?.sourceType) {
if (extMetadata?.sourceType === "jsr") { if (ext.metadata?.sourceType === "jsr") {
if (extMetadata?.source) { if (ext.metadata?.source) {
try { try {
const res = await fetch(`${extMetadata.source}/package.json`) const res = await fetch(`${ext.metadata.source}/package.json`)
const pkgJsonContent = await res.text() const pkgJsonContent = await res.text()
extras.overwritePackageJson = pkgJsonContent extras.overwritePackageJson = pkgJsonContent
} catch (error) { } catch (error) {

View File

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import DanceTransition from "@/components/dance/dance-transition.svelte" import DanceTransition from "@/components/dance/dance-transition.svelte"
import { i18n } from "@/i18n" import { i18n } from "@/i18n"
import { appConfig, appState, winExtMap } from "@/stores" import { appConfig, winExtMap } from "@/stores"
import { helperAPI } from "@/utils/helper" import { helperAPI } from "@/utils/helper"
import { paste } from "@/utils/hotkey" import { paste } from "@/utils/hotkey"
import { goBackOnEscape } from "@/utils/key" import { goBackOnEscape } from "@/utils/key"
import { decideKkrpcSerialization } from "@/utils/kkrpc"
import { goHome } from "@/utils/route" import { goHome } from "@/utils/route"
import { positionToCssStyleString, positionToTailwindClasses } from "@/utils/style" import { positionToCssStyleString, positionToTailwindClasses } from "@/utils/style"
import { sleep } from "@/utils/time" import { sleep } from "@/utils/time"
import { isInMainWindow } from "@/utils/window" import { isInMainWindow } from "@/utils/window"
import { db } from "@kksh/api/commands"
import { CustomPosition, ThemeColor, type Position } from "@kksh/api/models" import { CustomPosition, ThemeColor, type Position } from "@kksh/api/models"
import { import {
constructJarvisServerAPIWithPermissions, constructJarvisServerAPIWithPermissions,
@ -18,7 +18,6 @@
type IUiCustom type IUiCustom
} from "@kksh/api/ui" } from "@kksh/api/ui"
import { toast, type IUiCustomServer1, type IUiCustomServer2 } from "@kksh/api/ui/custom" import { toast, type IUiCustomServer1, type IUiCustomServer2 } from "@kksh/api/ui/custom"
import { db } from "@kksh/drizzle"
import { Button } from "@kksh/svelte5" import { Button } from "@kksh/svelte5"
import { cn } from "@kksh/ui/utils" import { cn } from "@kksh/ui/utils"
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server" import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
@ -28,7 +27,6 @@
} from "@kunkunapi/src/events" } from "@kunkunapi/src/events"
import { emitTo } from "@tauri-apps/api/event" import { emitTo } from "@tauri-apps/api/event"
import { getCurrentWindow } from "@tauri-apps/api/window" import { getCurrentWindow } from "@tauri-apps/api/window"
import { info } from "@tauri-apps/plugin-log"
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { IframeParentIO, RPCChannel } from "kkrpc/browser" import { IframeParentIO, RPCChannel } from "kkrpc/browser"
import { ArrowLeftIcon, MoveIcon, RefreshCcwIcon, XIcon } from "lucide-svelte" import { ArrowLeftIcon, MoveIcon, RefreshCcwIcon, XIcon } from "lucide-svelte"
@ -38,6 +36,7 @@
let { data }: { data: PageData } = $props() let { data }: { data: PageData } = $props()
const { loadedExt, url, extPath, extInfoInDB } = data const { loadedExt, url, extPath, extInfoInDB } = data
let extSpawnedProcesses = $state<number[]>([]) let extSpawnedProcesses = $state<number[]>([])
const appWin = getCurrentWindow()
let iframeRef: HTMLIFrameElement let iframeRef: HTMLIFrameElement
let uiControl = $state<{ let uiControl = $state<{
iframeLoaded: boolean iframeLoaded: boolean
@ -64,7 +63,7 @@
if (isInMainWindow()) { if (isInMainWindow()) {
goto(i18n.resolveRoute("/app/")) goto(i18n.resolveRoute("/app/"))
} else { } else {
data.win?.close() appWin.close()
} }
}, },
hideBackButton: async () => { hideBackButton: async () => {
@ -130,7 +129,7 @@
}, },
getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses), getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses),
paste: async () => { paste: async () => {
await data.win?.hide() await appWin.hide()
await sleep(200) await sleep(200)
return paste() return paste()
} }
@ -154,7 +153,7 @@
if (isInMainWindow()) { if (isInMainWindow()) {
goHome() goHome()
} else { } else {
data.win?.close() appWin.close()
} }
} }
@ -162,27 +161,16 @@
setTimeout(() => { setTimeout(() => {
iframeRef.focus() iframeRef.focus()
uiControl.iframeLoaded = true uiControl.iframeLoaded = true
appState.setFullScreenLoading(false)
}, 300) }, 300)
} }
onMount(() => { onMount(() => {
appState.setFullScreenLoading(true)
setTimeout(() => { setTimeout(() => {
data.win?.setFocus() appWin.show()
}, 200) }, 200)
if (iframeRef?.contentWindow) { if (iframeRef?.contentWindow) {
const io = new IframeParentIO(iframeRef.contentWindow) const io = new IframeParentIO(iframeRef.contentWindow)
const kkrpcSerialization = decideKkrpcSerialization(loadedExt) const rpc = new RPCChannel(io, { expose: serverAPI2 })
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel(io, {
expose: serverAPI2,
serialization: {
version: kkrpcSerialization
}
})
} else { } else {
toast.warning("iframeRef.contentWindow not available") toast.warning("iframeRef.contentWindow not available")
} }
@ -195,7 +183,7 @@
}) })
onDestroy(() => { onDestroy(() => {
winExtMap.unregisterExtensionFromWindow(data.win?.label ?? "") winExtMap.unregisterExtensionFromWindow(appWin.label)
}) })
</script> </script>
@ -208,7 +196,7 @@
onclick={onBackBtnClicked} onclick={onBackBtnClicked}
style={`${positionToCssStyleString(uiControl.backBtnPosition)}`} style={`${positionToCssStyleString(uiControl.backBtnPosition)}`}
> >
{#if data.win?.label === "main"} {#if appWin.label === "main"}
<ArrowLeftIcon class="w-4" /> <ArrowLeftIcon class="w-4" />
{:else} {:else}
<XIcon class="w-4" /> <XIcon class="w-4" />
@ -239,6 +227,7 @@
{/if} {/if}
<main class="h-screen"> <main class="h-screen">
<DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} />
<iframe <iframe
bind:this={iframeRef} bind:this={iframeRef}
class={cn("h-full", { class={cn("h-full", {

View File

@ -1,7 +1,7 @@
import { KunkunIframeExtParams } from "@/cmds/ext" import { KunkunIframeExtParams } from "@/cmds/ext"
import { i18n } from "@/i18n" import { i18n } from "@/i18n"
import { db, unregisterExtensionWindow } from "@kksh/api/commands"
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models" import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { loadExtensionManifestFromDisk } from "@kksh/extension" import { loadExtensionManifestFromDisk } from "@kksh/extension"
import { error as svError } from "@sveltejs/kit" import { error as svError } from "@sveltejs/kit"
import { join } from "@tauri-apps/api/path" import { join } from "@tauri-apps/api/path"

View File

@ -5,7 +5,6 @@
import { winExtMap } from "@/stores/winExtMap.js" import { winExtMap } from "@/stores/winExtMap.js"
import { helperAPI } from "@/utils/helper.js" import { helperAPI } from "@/utils/helper.js"
import { paste } from "@/utils/hotkey" import { paste } from "@/utils/hotkey"
import { decideKkrpcSerialization } from "@/utils/kkrpc.js"
import { import {
emitReloadOneExtension, emitReloadOneExtension,
listenToFileDrop, listenToFileDrop,
@ -13,6 +12,7 @@
} from "@/utils/tauri-events.js" } from "@/utils/tauri-events.js"
import { sleep } from "@/utils/time.js" import { sleep } from "@/utils/time.js"
import { isInMainWindow } from "@/utils/window.js" import { isInMainWindow } from "@/utils/window.js"
import { db } from "@kksh/api/commands"
import { import {
constructJarvisServerAPIWithPermissions, constructJarvisServerAPIWithPermissions,
type IApp, type IApp,
@ -28,7 +28,6 @@
type IComponent, type IComponent,
type TemplateUiCommand type TemplateUiCommand
} from "@kksh/api/ui/template" } from "@kksh/api/ui/template"
import { db } from "@kksh/drizzle"
import { Button, Form } from "@kksh/svelte5" import { Button, Form } from "@kksh/svelte5"
import { LoadingBar } from "@kksh/ui" import { LoadingBar } from "@kksh/ui"
import { Templates } from "@kksh/ui/extension" import { Templates } from "@kksh/ui/extension"
@ -44,7 +43,7 @@
import { getCurrentWindow } from "@tauri-apps/api/window" import { getCurrentWindow } from "@tauri-apps/api/window"
import * as fs from "@tauri-apps/plugin-fs" import * as fs from "@tauri-apps/plugin-fs"
import { readTextFile } from "@tauri-apps/plugin-fs" import { readTextFile } from "@tauri-apps/plugin-fs"
import { debug, info } from "@tauri-apps/plugin-log" import { debug } from "@tauri-apps/plugin-log"
import { platform } from "@tauri-apps/plugin-os" import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { RPCChannel, WorkerParentIO } from "kkrpc/browser" import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
@ -52,6 +51,7 @@
import Inspect from "svelte-inspect-value" import Inspect from "svelte-inspect-value"
import { type CommandEvent } from "tauri-plugin-shellx-api" import { type CommandEvent } from "tauri-plugin-shellx-api"
import * as v from "valibot" import * as v from "valibot"
import Listview2 from "./listview2.svelte"
const { data } = $props() const { data } = $props()
let listviewInputRef = $state<HTMLInputElement | null>(null) let listviewInputRef = $state<HTMLInputElement | null>(null)
@ -271,15 +271,8 @@
} satisfies IApp } satisfies IApp
} }
const io = new WorkerParentIO(worker) const io = new WorkerParentIO(worker)
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel<typeof serverAPI2, TemplateUiCommand>(io, { const rpc = new RPCChannel<typeof serverAPI2, TemplateUiCommand>(io, {
expose: serverAPI2, expose: serverAPI2
serialization: {
version: kkrpcSerialization
}
}) })
workerAPI = rpc.getAPI() workerAPI = rpc.getAPI()
await workerAPI.load() await workerAPI.load()
@ -310,7 +303,7 @@
onMount(async () => { onMount(async () => {
setTimeout(() => { setTimeout(() => {
appState.setLoadingBar(true) appState.setLoadingBar(true)
appWin.show().then(() => appWin.setFocus()) appWin.show()
}, 100) }, 100)
unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => { unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => {
debug("Refreshing Worker Extension") debug("Refreshing Worker Extension")

View File

@ -1,7 +1,7 @@
import { KunkunTemplateExtParams } from "@/cmds/ext" import { KunkunTemplateExtParams } from "@/cmds/ext"
import { i18n } from "@/i18n" import { i18n } from "@/i18n"
import type { ExtPackageJsonExtra } from "@kksh/api/models" import { db, unregisterExtensionWindow } from "@kksh/api/commands"
import { db } from "@kksh/drizzle" import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
import { loadExtensionManifestFromDisk } from "@kksh/extension" import { loadExtensionManifestFromDisk } from "@kksh/extension"
import { error as sbError, error as svError } from "@sveltejs/kit" import { error as sbError, error as svError } from "@sveltejs/kit"
import { join } from "@tauri-apps/api/path" import { join } from "@tauri-apps/api/path"

View File

@ -0,0 +1,8 @@
<script lang="ts">
import { ListSchema } from "@kunkunapi/src/models"
let { listViewContent }: { listViewContent: ListSchema.List } = $props()
let detailWidth = $derived(listViewContent.detail ? (listViewContent.detail?.width ?? 70) : 0)
</script>
<div>detailWidth: {detailWidth}</div>

View File

@ -8,6 +8,7 @@
import { goto } from "$app/navigation" import { goto } from "$app/navigation"
import { ArrowRightIcon } from "lucide-svelte" import { ArrowRightIcon } from "lucide-svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import { fade } from "svelte/transition"
import { whereIsCommand } from "tauri-plugin-shellx-api" import { whereIsCommand } from "tauri-plugin-shellx-api"
import { Step } from "./steps" import { Step } from "./steps"
@ -25,22 +26,18 @@
} }
$effect(() => { $effect(() => {
if (step > Step.GeneralSettings) { if (step === Step.DenoInstall) {
if (denoPath) {
step++
}
} else if (step === Step.FFmpegInstall) {
if (ffmpegPath) {
step++
}
} else if (step > Step.FFmpegInstall) {
appConfig.setOnBoarded(true) appConfig.setOnBoarded(true)
goto(i18n.resolveRoute("/app")) goto(i18n.resolveRoute("/app"))
} }
// if (step === Step.DenoInstall) {
// if (denoPath) {
// step++
// }
// } else if (step === Step.FFmpegInstall) {
// if (ffmpegPath) {
// step++
// }
// } else if (step > Step.FFmpegInstall) {
// appConfig.setOnBoarded(true)
// goto(i18n.resolveRoute("/app"))
// }
}) })
</script> </script>

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as m from "@/paraglide/messages" import * as m from "@/paraglide/messages"
import { db } from "@kksh/drizzle" import { db } from "@kksh/api/commands"
import { loadExtensionManifestFromDisk } from "@kksh/extension" import { loadExtensionManifestFromDisk } from "@kksh/extension"
import { Button, Dialog, Table } from "@kksh/svelte5" import { Button, Dialog, Table } from "@kksh/svelte5"
import { join } from "@tauri-apps/api/path" import { join } from "@tauri-apps/api/path"

View File

@ -1,124 +0,0 @@
<script lang="ts">
import {
getAllCmds,
getAllExtensions,
getExtensionDataById,
getUniqueExtensionByIdentifier,
getUniqueExtensionByPath,
searchExtensionData,
updateCmdByID
} from "@kksh/drizzle/api"
import * as schema from "@kksh/drizzle/schema"
import { Button, Input } from "@kksh/svelte5"
import { CmdTypeEnum, Ext } from "@kunkunapi/src/models/extension"
import { SearchModeEnum, SQLSortOrderEnum } from "@kunkunapi/src/models/sql"
// import * as orm from "drizzle-orm"
import { Inspect } from "svelte-inspect-value"
import { toast } from "svelte-sonner"
import * as v from "valibot"
let searchText = $state("")
/* eslint-disable */
let data: any = $state(null)
let inspectTitle = $state("")
</script>
<main class="container space-y-2">
<Button
onclick={async () => {
getAllCmds()
.then((cmds) => {
console.log(cmds)
data = cmds
inspectTitle = "All Commands"
})
.catch((e) => {
console.error(e)
toast.error("Failed to get all commands", {
description: "See console for more details"
})
})
}}
>
Get All Commands
</Button>
<Button
onclick={() => {
getAllExtensions()
.then((exts) => {
data = exts
inspectTitle = "All Extensions"
})
.catch((e) => {
console.error(e)
toast.error("Failed to get all extensions", {
description: "See console for more details"
})
})
}}
>
Get All Extensions
</Button>
<Button
onclick={async () => {
// get all extensions with path not null
const exts = await getAllExtensions()
for (const ext of exts) {
if (ext.path === null) continue
const _ext = await getUniqueExtensionByIdentifier(ext.identifier)
console.log(_ext)
if (ext.path) {
const __ext = await getUniqueExtensionByPath(ext.path)
console.log(__ext)
}
}
// data = exts
}}
>
Get Unique Extension By Identifier and Path
</Button>
<!-- <Button
onclick={async () => {
updateCmdByID({
cmdId: 1,
name: "google",
cmdType: CmdTypeEnum.QuickLink,
data: `{"link":"https://google.com/search?query={argument}","icon":{"type":"remote-url","value":"https://google.com/favicon.ico","invert":false}}`,
enabled: true
})
}}
>
Update Command By ID
</Button> -->
<Button
onclick={async () => {
const _data = await getExtensionDataById(1, ["search_text", "data"])
data = _data
inspectTitle = "Extension Data"
}}
>
Get Extension Data By ID
</Button>
<form
class="flex gap-1"
onsubmit={async (e) => {
e.preventDefault()
const _data = await searchExtensionData({
extId: 1,
searchMode: SearchModeEnum.FTS,
searchText: searchText,
orderByCreatedAt: SQLSortOrderEnum.Desc,
limit: 10,
fields: ["search_text", "data"]
})
console.log(_data)
data = _data
inspectTitle = "Search Results"
}}
>
<Input class="" bind:value={searchText} placeholder="Search Text" />
<Button class="" type="submit">Search Extension Data</Button>
</form>
<Inspect name={inspectTitle} value={data} expandLevel={2} />
</main>

View File

@ -6,7 +6,6 @@
import { Constants } from "@kksh/ui" import { Constants } from "@kksh/ui"
import { ArrowLeftIcon } from "lucide-svelte" import { ArrowLeftIcon } from "lucide-svelte"
import AppWindow from "lucide-svelte/icons/app-window" import AppWindow from "lucide-svelte/icons/app-window"
import DB from "lucide-svelte/icons/database"
import Loader from "lucide-svelte/icons/loader" import Loader from "lucide-svelte/icons/loader"
import Network from "lucide-svelte/icons/network" import Network from "lucide-svelte/icons/network"
@ -26,11 +25,6 @@
title: m.troubleshooters_sidebar_mdns_debugger_title(), title: m.troubleshooters_sidebar_mdns_debugger_title(),
url: i18n.resolveRoute("/app/troubleshooters/mdns-debugger"), url: i18n.resolveRoute("/app/troubleshooters/mdns-debugger"),
icon: Network icon: Network
},
{
title: "ORM",
url: i18n.resolveRoute("/app/troubleshooters/orm"),
icon: DB
} }
] ]
let currentItem = $state(items.find((item) => window.location.pathname === item.url)) let currentItem = $state(items.find((item) => window.location.pathname === item.url))

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Layouts } from "@kksh/ui" import { Layouts } from "@kksh/ui"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { onMount } from "svelte" import { onMount } from "svelte"
let { data } = $props() onMount(async () => {
const mainWin = await getCurrentWindow()
onMount(() => { mainWin.show()
data.win?.show().then(() => data.win?.setFocus())
}) })
</script> </script>

View File

@ -86,18 +86,12 @@ const config: Config = {
"caret-blink": { "caret-blink": {
"0%,70%,100%": { opacity: "1" }, "0%,70%,100%": { opacity: "1" },
"20%,50%": { opacity: "0" } "20%,50%": { opacity: "0" }
},
"border-beam": {
"100%": {
"offset-distance": "100%"
}
} }
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
"caret-blink": "caret-blink 1.25s ease-out infinite", "caret-blink": "caret-blink 1.25s ease-out infinite"
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear"
} }
} }
}, },

View File

@ -9,8 +9,7 @@
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler", "moduleResolution": "bundler"
"allowImportingTsExtensions": true
}, },
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files

View File

@ -14,7 +14,7 @@ export default defineConfig(async () => ({
clearScreen: false, clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available // 2. tauri expects a fixed port, fail if that port is not available
server: { server: {
port: 1566, port: 1420,
strictPort: true, strictPort: true,
host: host || false, host: host || false,
hmr: host hmr: host

2346
deno.lock generated

File diff suppressed because it is too large Load Diff

4
deno.ts Normal file
View File

@ -0,0 +1,4 @@
let idx = 0
setInterval(() => {
console.log(idx++)
}, 500)

View File

@ -22,15 +22,13 @@
"typescript": "^5.0.0", "typescript": "^5.0.0",
"verify-package-export": "^0.0.3" "verify-package-export": "^0.0.3"
}, },
"packageManager": "pnpm@10.7.0", "packageManager": "pnpm@10.4.1",
"engines": { "engines": {
"node": ">=22" "node": ">=22"
}, },
"dependencies": { "dependencies": {
"@changesets/cli": "^2.28.1", "@changesets/cli": "^2.28.1",
"@hey-api/client-fetch": "^0.8.3",
"@iconify/svelte": "^4.2.0", "@iconify/svelte": "^4.2.0",
"@kksh/sdk": "^0.0.3",
"@supabase/supabase-js": "^2.49.1", "@supabase/supabase-js": "^2.49.1",
"@tauri-apps/api": "^2.3.0", "@tauri-apps/api": "^2.3.0",
"@tauri-apps/cli": "^2.3.1", "@tauri-apps/cli": "^2.3.1",
@ -50,7 +48,7 @@
"tauri-plugin-keyring-api": "workspace:*", "tauri-plugin-keyring-api": "workspace:*",
"tauri-plugin-network-api": "workspace:*", "tauri-plugin-network-api": "workspace:*",
"tauri-plugin-system-info-api": "workspace:*", "tauri-plugin-system-info-api": "workspace:*",
"valibot": "^1.0.0", "valibot": "^1.0.0-beta.11",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"workspaces": [ "workspaces": [

View File

@ -53,16 +53,3 @@
### Patch Changes ### Patch Changes
- More Icon Options - More Icon Options
## 0.1.6
### Patch Changes
- Upgrade kkrpc to 0.2.1, which uses superjson for serialization
## 0.1.7
### Patch Changes
- Upgrade kkrpc to 0.2.2, supports both json and superjson serialization, for backward compatibility
- The previous version breaks extension compatibility.

View File

@ -1,6 +1,6 @@
{ {
"name": "@kksh/api", "name": "@kksh/api",
"version": "0.1.7", "version": "0.1.5",
"type": "module", "type": "module",
"repository": { "repository": {
"type": "git", "type": "git",
@ -65,17 +65,17 @@
"@tauri-apps/plugin-store": "^2.2.0", "@tauri-apps/plugin-store": "^2.2.0",
"@tauri-apps/plugin-updater": "^2.3.0", "@tauri-apps/plugin-updater": "^2.3.0",
"@tauri-apps/plugin-upload": "^2.2.1", "@tauri-apps/plugin-upload": "^2.2.1",
"kkrpc": "^0.2.2", "kkrpc": "^0.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"minimatch": "^10.0.1", "minimatch": "^10.0.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"semver": "^7.6.3", "semver": "^7.6.3",
"svelte-sonner": "^0.3.28", "svelte-sonner": "^0.3.28",
"tauri-api-adapter": "^0.3.27", "tauri-api-adapter": "^0.3.23",
"tauri-plugin-network-api": "2.0.5", "tauri-plugin-network-api": "2.0.5",
"tauri-plugin-shellx-api": "^2.0.16", "tauri-plugin-shellx-api": "^2.0.15",
"tauri-plugin-system-info-api": "2.0.8", "tauri-plugin-system-info-api": "2.0.8",
"valibot": "^1.0.0" "valibot": "^1.0.0-beta.10"
}, },
"files": [ "files": [
"src", "src",

View File

@ -20,10 +20,9 @@ import type {
IPath as ITauriPath IPath as ITauriPath
} from "tauri-api-adapter" } from "tauri-api-adapter"
import * as v from "valibot" import * as v from "valibot"
import { KV, type JarvisExtDB } from "../commands/db"
import type { fileSearch } from "../commands/fileSearch" import type { fileSearch } from "../commands/fileSearch"
import { type AppInfo } from "../models/apps" import { type AppInfo } from "../models/apps"
import { type ExtData } from "../models/extension"
import { ExtDataField, SearchMode, SQLSortOrder } from "../models/sql"
import type { LightMode, Position, Radius, ThemeColor } from "../models/styles" import type { LightMode, Position, Radius, ThemeColor } from "../models/styles"
import type { DenoSysOptions } from "../permissions/schema" import type { DenoSysOptions } from "../permissions/schema"
@ -155,34 +154,23 @@ export interface IUiCustom {
} }
export interface IDb { export interface IDb {
add: (data: { data: string; dataType?: string; searchText?: string }) => Promise<void> add: typeof JarvisExtDB.prototype.add
delete: (dataId: number) => Promise<void> delete: typeof JarvisExtDB.prototype.delete
search: (searchParams: { search: typeof JarvisExtDB.prototype.search
dataId?: number retrieveAll: typeof JarvisExtDB.prototype.retrieveAll
searchMode?: SearchMode retrieveAllByType: typeof JarvisExtDB.prototype.retrieveAllByType
dataType?: string deleteAll: typeof JarvisExtDB.prototype.deleteAll
searchText?: string update: typeof JarvisExtDB.prototype.update
afterCreatedAt?: Date
beforeCreatedAt?: Date
limit?: number
orderByCreatedAt?: SQLSortOrder
orderByUpdatedAt?: SQLSortOrder
fields?: ExtDataField[]
}) => Promise<ExtData[]>
retrieveAll: (options: { fields?: ExtDataField[] }) => Promise<ExtData[]>
retrieveAllByType: (dataType: string) => Promise<ExtData[]>
deleteAll: () => Promise<void>
update: (data: { dataId: number; data: string; searchText?: string }) => Promise<void>
} }
/** /**
* A key-value store built on top of the Database API (based on sqlite) * A key-value store built on top of the Database API (based on sqlite)
*/ */
export interface IKV { export interface IKV {
get: <T = string>(key: string) => Promise<T | null | undefined> get: typeof KV.prototype.get
set: (key: string, value: string) => Promise<void> set: typeof KV.prototype.set
exists: (key: string) => Promise<boolean> exists: typeof KV.prototype.exists
delete: (key: string) => Promise<void> delete: typeof KV.prototype.delete
} }
export interface IFs { export interface IFs {

View File

@ -169,8 +169,8 @@ export function constructShellApi(
const onEvent = new Channel<CommandEvent<O>>() const onEvent = new Channel<CommandEvent<O>>()
onEvent.onmessage = cb onEvent.onmessage = cb
return invoke<number>("plugin:shellx|spawn", { return invoke<number>("plugin:shellx|spawn", {
program, program: "deno",
args, args: ["run", "/Users/hk/Dev/kunkun/deno.ts"],
options, options,
onEvent onEvent
}) })

View File

@ -0,0 +1,437 @@
import { invoke } from "@tauri-apps/api/core"
import { array, literal, optional, parse, safeParse, union, type InferOutput } from "valibot"
import { KUNKUN_EXT_IDENTIFIER } from "../constants"
import { CmdType, Ext, ExtCmd, ExtData } from "../models/extension"
import { convertDateToSqliteString, SearchMode, SearchModeEnum, SQLSortOrder } from "../models/sql"
import { generateJarvisPluginCommand } from "./common"
/* -------------------------------------------------------------------------- */
/* Extension CRUD */
/* -------------------------------------------------------------------------- */
export function createExtension(ext: {
identifier: string
version: string
enabled?: boolean
path?: string
data?: any
}) {
return invoke<void>(generateJarvisPluginCommand("create_extension"), ext)
}
export function getAllExtensions() {
return invoke<Ext[]>(generateJarvisPluginCommand("get_all_extensions"))
}
export function getUniqueExtensionByIdentifier(identifier: string) {
return invoke<Ext | undefined>(
generateJarvisPluginCommand("get_unique_extension_by_identifier"),
{
identifier
}
)
}
export function getUniqueExtensionByPath(path: string) {
return invoke<Ext | undefined>(generateJarvisPluginCommand("get_unique_extension_by_path"), {
path
})
}
export function getAllExtensionsByIdentifier(identifier: string) {
return invoke<Ext[]>(generateJarvisPluginCommand("get_all_extensions_by_identifier"), {
identifier
})
}
/**
* Use this function when you expect the extension to exist. Such as builtin extensions.
* @param identifier
* @returns
*/
export function getExtensionByIdentifierExpectExists(identifier: string): Promise<Ext> {
return getUniqueExtensionByIdentifier(identifier).then((ext) => {
if (!ext) {
throw new Error(`Unexpexted Error: Extension ${identifier} not found`)
}
return ext
})
}
// TODO: clean this up
// export function deleteExtensionByIdentifier(identifier: string) {
// return invoke<void>(generateJarvisPluginCommand("delete_extension_by_identifier"), { identifier })
// }
export function deleteExtensionByPath(path: string) {
return invoke<void>(generateJarvisPluginCommand("delete_extension_by_path"), {
path
})
}
export function deleteExtensionByExtId(extId: string) {
return invoke<void>(generateJarvisPluginCommand("delete_extension_by_ext_id"), { extId })
}
/* -------------------------------------------------------------------------- */
/* Extension Command CRUD */
/* -------------------------------------------------------------------------- */
export function createCommand(data: {
extId: number
name: string
cmdType: CmdType
data: string
alias?: string
hotkey?: string
enabled?: boolean
}) {
return invoke<void>(generateJarvisPluginCommand("create_command"), {
...data,
enabled: data.enabled ?? false
})
}
export function getCommandById(cmdId: number) {
return invoke<ExtCmd | undefined>(generateJarvisPluginCommand("get_command_by_id"), { cmdId })
}
export function getCommandsByExtId(extId: number) {
return invoke<ExtCmd[]>(generateJarvisPluginCommand("get_commands_by_ext_id"), { extId })
}
export function deleteCommandById(cmdId: number) {
return invoke<void>(generateJarvisPluginCommand("delete_command_by_id"), {
cmdId
})
}
export function updateCommandById(data: {
cmdId: number
name: string
cmdType: CmdType
data: string
alias?: string
hotkey?: string
enabled: boolean
}) {
return invoke<void>(generateJarvisPluginCommand("update_command_by_id"), data)
}
/* -------------------------------------------------------------------------- */
/* Extension Data CRUD */
/* -------------------------------------------------------------------------- */
export const ExtDataField = union([literal("data"), literal("search_text")])
export type ExtDataField = InferOutput<typeof ExtDataField>
function convertRawExtDataToExtData(rawData?: {
createdAt: string
updatedAt: string
data: null | string
searchText: null | string
}): ExtData | undefined {
if (!rawData) {
return rawData
}
const parsedRes = safeParse(ExtData, {
...rawData,
createdAt: new Date(rawData.createdAt),
updatedAt: new Date(rawData.updatedAt),
data: rawData.data ?? undefined,
searchText: rawData.searchText ?? undefined
})
if (parsedRes.success) {
return parsedRes.output
} else {
console.error("Extension Data Parse Failure", parsedRes.issues)
throw new Error("Fail to parse extension data")
}
}
export function createExtensionData(data: {
extId: number
dataType: string
data: string
searchText?: string
}) {
return invoke<void>(generateJarvisPluginCommand("create_extension_data"), data)
}
export function getExtensionDataById(dataId: number, fields?: ExtDataField[]) {
return invoke<
| (ExtData & {
createdAt: string
updatedAt: string
data: null | string
searchText: null | string
})
| undefined
>(generateJarvisPluginCommand("get_extension_data_by_id"), {
dataId,
fields
}).then(convertRawExtDataToExtData)
}
/**
* Fields option can be used to select optional fields. By default, if left empty, data and searchText are not returned.
* This is because data and searchText can be large and we don't want to return them by default.
* If you just want to get data ids in order to delete them, retrieving all data is not necessary.
* @param searchParams
*/
export async function searchExtensionData(searchParams: {
extId: number
searchMode: SearchMode
dataId?: number
dataType?: string
searchText?: string
afterCreatedAt?: string
beforeCreatedAt?: string
limit?: number
offset?: number
orderByCreatedAt?: SQLSortOrder
orderByUpdatedAt?: SQLSortOrder
fields?: ExtDataField[]
}): Promise<ExtData[]> {
const fields = parse(optional(array(ExtDataField), []), searchParams.fields)
let items = await invoke<
(ExtData & {
createdAt: string
updatedAt: string
data: null | string
searchText: null | string
})[]
>(generateJarvisPluginCommand("search_extension_data"), {
searchQuery: {
...searchParams,
fields
}
})
return items.map(convertRawExtDataToExtData).filter((item) => item) as ExtData[]
}
export function deleteExtensionDataById(dataId: number) {
return invoke<void>(generateJarvisPluginCommand("delete_extension_data_by_id"), { dataId })
}
export function updateExtensionDataById(data: {
dataId: number
data: string
searchText?: string
}) {
return invoke<void>(generateJarvisPluginCommand("update_extension_data_by_id"), data)
}
/* -------------------------------------------------------------------------- */
/* Built-in Extensions */
/* -------------------------------------------------------------------------- */
export function getExtClipboard() {
return getExtensionByIdentifierExpectExists(KUNKUN_EXT_IDENTIFIER.KUNKUN_CLIPBOARD_EXT_IDENTIFIER)
}
export function getExtQuickLinks() {
return getExtensionByIdentifierExpectExists(
KUNKUN_EXT_IDENTIFIER.KUNKUN_QUICK_LINKS_EXT_IDENTIFIER
)
}
export function getExtRemote() {
return getExtensionByIdentifierExpectExists(KUNKUN_EXT_IDENTIFIER.KUNKUN_REMOTE_EXT_IDENTIFIER)
}
export function getExtScriptCmd() {
return getExtensionByIdentifierExpectExists(
KUNKUN_EXT_IDENTIFIER.KUNKUN_SCRIPT_CMD_EXT_IDENTIFIER
)
}
export function getExtDev() {
return getExtensionByIdentifierExpectExists(KUNKUN_EXT_IDENTIFIER.KUNKUN_DEV_EXT_IDENTIFIER)
}
/**
* Database API for extensions.
* Extensions shouldn't have full access to the database, they can only access their own data.
* When an extension is loaded, the main thread will create an instance of this class and
* expose it to the extension.
*/
export class JarvisExtDB {
extId: number
constructor(extId: number) {
this.extId = extId
}
async add(data: { data: string; dataType?: string; searchText?: string }) {
return createExtensionData({
data: data.data,
dataType: data.dataType ?? "default",
searchText: data.searchText,
extId: this.extId
})
}
async delete(dataId: number): Promise<void> {
// Verify if this data belongs to this extension
const d = await getExtensionDataById(dataId)
if (!d || d.extId !== this.extId) {
throw new Error("Extension Data not found")
}
return await deleteExtensionDataById(dataId)
}
async search(searchParams: {
dataId?: number
searchMode?: SearchMode
dataType?: string
searchText?: string
afterCreatedAt?: Date
beforeCreatedAt?: Date
limit?: number
orderByCreatedAt?: SQLSortOrder
orderByUpdatedAt?: SQLSortOrder
fields?: ExtDataField[]
}): Promise<ExtData[]> {
const beforeCreatedAt = searchParams.beforeCreatedAt
? convertDateToSqliteString(searchParams.beforeCreatedAt)
: undefined
const afterCreatedAt = searchParams.afterCreatedAt
? convertDateToSqliteString(searchParams.afterCreatedAt)
: undefined
return searchExtensionData({
...searchParams,
searchMode: searchParams.searchMode ?? SearchModeEnum.FTS,
extId: this.extId,
beforeCreatedAt,
afterCreatedAt
})
}
/**
* Retrieve all data of this extension.
* Use `search()` method for more advanced search.
* @param options optional fields to retrieve. By default, data and searchText are not returned.
* @returns
*/
retrieveAll(options: { fields?: ExtDataField[] }): Promise<ExtData[]> {
return this.search({ fields: options.fields })
}
/**
* Retrieve all data of this extension by type.
* Use `search()` method for more advanced search.
* @param dataType
* @returns
*/
retrieveAllByType(dataType: string): Promise<ExtData[]> {
return this.search({ dataType })
}
/**
* Delete all data of this extension.
*/
deleteAll(): Promise<void> {
return this.search({})
.then((items) => {
return Promise.all(items.map((item) => this.delete(item.dataId)))
})
.then(() => {})
}
/**
* Update data and searchText of this extension.
* @param dataId unique id of the data
* @param data
* @param searchText
* @returns
*/
async update(data: { dataId: number; data: string; searchText?: string }): Promise<void> {
const d = await getExtensionDataById(data.dataId)
if (!d || d.extId !== this.extId) {
throw new Error("Extension Data not found")
}
return updateExtensionDataById(data)
}
}
export class KV {
extId: number
db: JarvisExtDB
private DataType: string = "kunkun_kv"
constructor(extId: number) {
this.extId = extId
this.db = new JarvisExtDB(extId)
}
get<T = string>(key: string): Promise<T | null | undefined> {
return this.db
.search({
dataType: this.DataType,
searchText: key,
searchMode: SearchModeEnum.ExactMatch,
fields: ["search_text", "data"]
})
.then((items) => {
if (items.length === 0) {
return null
} else if (items.length > 1) {
throw new Error("Multiple KVs with the same key")
}
return items[0].data ? (JSON.parse(items[0].data).value as T) : null
})
.catch((err) => {
console.warn(err)
return null
})
}
set(key: string, value: string): Promise<void> {
return this.db
.search({
dataType: this.DataType,
searchText: key,
searchMode: SearchModeEnum.ExactMatch
})
.then((items) => {
if (items.length === 0) {
return this.db.add({
data: JSON.stringify({ value: value }),
dataType: this.DataType,
searchText: key
})
} else if (items.length === 1) {
return this.db.update({
dataId: items[0].dataId,
data: JSON.stringify({ value: value }),
searchText: key
})
} else {
return Promise.all(items.map((item) => this.db.delete(item.dataId))).then(() =>
Promise.resolve()
)
}
})
}
delete(key: string): Promise<void> {
return this.db
.search({
dataType: this.DataType,
searchText: key,
searchMode: SearchModeEnum.ExactMatch
})
.then((items) => {
return Promise.all(items.map((item) => this.db.delete(item.dataId))).then(() =>
Promise.resolve()
)
})
}
exists(key: string): Promise<boolean> {
return this.db
.search({
dataType: this.DataType,
searchText: key,
searchMode: SearchModeEnum.ExactMatch,
fields: []
})
.then((items) => {
return items.length > 0
})
}
}

View File

@ -5,7 +5,8 @@ export * from "./tools"
export * from "./extension" export * from "./extension"
export * from "./system" export * from "./system"
export * from "./store" export * from "./store"
export * as sql from "./sql" export * as db from "./db"
export { JarvisExtDB } from "./db"
export * from "./clipboard" export * from "./clipboard"
export * from "./fileSearch" export * from "./fileSearch"
export * from "./utils" export * from "./utils"

View File

@ -1,30 +0,0 @@
import { invoke } from "@tauri-apps/api/core"
import { generateJarvisPluginCommand } from "./common"
export interface QueryResult {
/** The number of rows affected by the query. */
rowsAffected: number
/**
* The last inserted `id`.
*
* This value is not set for Postgres databases. If the
* last inserted id is required on Postgres, the `select` function
* must be used, with a `RETURNING` clause
* (`INSERT INTO todos (title) VALUES ($1) RETURNING id`).
*/
lastInsertId?: number
}
export function select(query: string, values: any[]) {
return invoke<any[]>(generateJarvisPluginCommand("select"), {
query,
values
})
}
export function execute(query: string, values: any[]) {
return invoke<QueryResult>(generateJarvisPluginCommand("execute"), {
query,
values
})
}

View File

@ -1,79 +1,91 @@
import * as v from "valibot" import {
any,
array,
boolean,
date,
enum_,
function_,
nullable,
number,
object,
optional,
record,
string,
type InferOutput
} from "valibot"
import { Icon } from "./icon" import { Icon } from "./icon"
/** /**
* Map window label to extension * Map window label to extension
*/ */
export const ExtensionLabelMap = v.record( export const ExtensionLabelMap = record(
v.string("Window label"), string("Window label"),
v.object({ object({
path: v.string("Path to the extension"), path: string("Path to the extension"),
processes: v.array(v.number()), processes: array(number()),
dist: v.optional(v.nullable(v.string())) dist: optional(nullable(string()))
}) })
) )
export type ExtensionLabelMap = v.InferOutput<typeof ExtensionLabelMap> export type ExtensionLabelMap = InferOutput<typeof ExtensionLabelMap>
export const Ext = v.object({ export const Ext = object({
extId: v.number(), extId: number(),
identifier: v.string(), identifier: string(),
version: v.string(), version: string(),
enabled: v.boolean(), enabled: boolean(),
installedAt: v.string(), installed_at: string(),
path: v.optional(v.nullable(v.string())), path: nullable(string()),
data: v.optional(v.any()) data: nullable(any())
}) })
export type Ext = v.InferOutput<typeof Ext> export type Ext = InferOutput<typeof Ext>
export const CmdTypeEnum = { export enum CmdTypeEnum {
HeadlessWorker: "headless_worker", HeadlessWorker = "headless_worker",
Builtin: "builtin", Builtin = "builtin",
System: "system", System = "system",
UiWorker: "ui_worker", UiWorker = "ui_worker",
UiIframe: "ui_iframe", UiIframe = "ui_iframe",
QuickLink: "quick_link", QuickLink = "quick_link",
Remote: "remote" Remote = "remote"
} }
export const CmdType = v.picklist(Object.values(CmdTypeEnum)) export const CmdType = enum_(CmdTypeEnum)
export type CmdType = InferOutput<typeof CmdType>
export type CmdType = v.InferOutput<typeof CmdType> export const ExtCmd = object({
export const ExtCmd = v.object({ cmdId: number(),
cmdId: v.number(), extId: number(),
extId: v.number(), name: string(),
name: v.string(),
type: CmdType, type: CmdType,
data: v.string(), data: string(),
alias: v.optional(v.nullable(v.string())), alias: nullable(optional(string())),
hotkey: v.optional(v.nullable(v.string())), hotkey: nullable(optional(string())),
enabled: v.boolean() enabled: boolean()
}) })
export type ExtCmd = InferOutput<typeof ExtCmd>
export type ExtCmd = v.InferOutput<typeof ExtCmd> export const QuickLinkCmd = object({
export const QuickLinkCmd = v.object({
...ExtCmd.entries, ...ExtCmd.entries,
data: v.object({ link: v.string(), icon: Icon }) data: object({ link: string(), icon: Icon })
}) })
export type QuickLinkCmd = v.InferOutput<typeof QuickLinkCmd> export type QuickLinkCmd = InferOutput<typeof QuickLinkCmd>
export const ExtData = v.object({ export const ExtData = object({
dataId: v.number(), dataId: number(),
extId: v.number(), extId: number(),
dataType: v.string(), dataType: string(),
data: v.optional(v.string()), data: optional(string()),
searchText: v.optional(v.string()), searchText: optional(string()),
createdAt: v.date(), createdAt: date(),
updatedAt: v.date() updatedAt: date()
}) })
export type ExtData = v.InferOutput<typeof ExtData> export type ExtData = InferOutput<typeof ExtData>
export const SysCommand = v.object({ export const SysCommand = object({
name: v.string(), name: string(),
value: v.string(), value: string(),
icon: v.optional(v.nullable(Icon)), icon: nullable(Icon),
keywords: v.optional(v.nullable(v.array(v.string()))), keywords: nullable(array(string())),
function: v.function_(), function: function_(),
confirmRequired: v.boolean() confirmRequired: boolean()
}) })
export type SysCommand = v.InferOutput<typeof SysCommand> export type SysCommand = InferOutput<typeof SysCommand>

View File

@ -4,21 +4,14 @@ import { NodeName, NodeNameEnum } from "./constants"
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Icon */ /* Icon */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
// export enum IconEnum { export enum IconEnum {
// Iconify = "iconify", Iconify = "iconify",
// RemoteUrl = "remote-url", RemoteUrl = "remote-url",
// Svg = "svg", Svg = "svg",
// Base64PNG = "base64-png", Base64PNG = "base64-png",
// Text = "text" Text = "text"
// }
export const IconEnum = {
Iconify: "iconify",
RemoteUrl: "remote-url",
Svg: "svg",
Base64PNG: "base64-png",
Text: "text"
} }
export const IconType = v.picklist(Object.values(IconEnum)) export const IconType = v.enum_(IconEnum)
export type IconType = v.InferOutput<typeof IconType> export type IconType = v.InferOutput<typeof IconType>
export type Icon = { export type Icon = {

View File

@ -13,4 +13,3 @@ export { Markdown as MarkdownSchema } from "../ui/template/schema/markdown"
export * as ListSchema from "../ui/template/schema/list" export * as ListSchema from "../ui/template/schema/list"
export * as FormSchema from "../ui/template/schema/form" export * as FormSchema from "../ui/template/schema/form"
export * from "./file-transfer" export * from "./file-transfer"
export * from "./server"

View File

@ -1,3 +1,4 @@
import { FsPermissionSchema } from "tauri-api-adapter/permissions"
import * as v from "valibot" import * as v from "valibot"
import { import {
AllKunkunPermission, AllKunkunPermission,
@ -7,29 +8,29 @@ import {
OpenPermissionScopedSchema, OpenPermissionScopedSchema,
ShellPermissionScopedSchema ShellPermissionScopedSchema
} from "../permissions" } from "../permissions"
import { CmdType, CmdTypeEnum } from "./extension" import { CmdType } from "./extension"
import { BaseIcon as Icon } from "./icon" import { Icon } from "./icon"
export const OSPlatformEnum = { export enum OSPlatformEnum {
linux: "linux", linux = "linux",
macos: "macos", macos = "macos",
windows: "windows" windows = "windows"
} }
export const OSPlatform = v.picklist(Object.values(OSPlatformEnum)) export const OSPlatform = v.enum_(OSPlatformEnum)
export type OSPlatform = v.InferOutput<typeof OSPlatform> export type OSPlatform = v.InferOutput<typeof OSPlatform>
const allPlatforms = Object.values(OSPlatformEnum)
export const TriggerCmd = v.object({ export const TriggerCmd = v.object({
type: v.union([v.literal("text"), v.literal("regex")]), type: v.union([v.literal("text"), v.literal("regex")]),
value: v.string() value: v.string()
}) })
export type TriggerCmd = v.InferOutput<typeof TriggerCmd> export type TriggerCmd = v.InferOutput<typeof TriggerCmd>
export const TitleBarStyleEnum = { export enum TitleBarStyleEnum {
visible: "visible", "visible" = "visible",
transparent: "transparent", "transparent" = "transparent",
overlay: "overlay" "overlay" = "overlay"
} }
export type TitleBarStyle = v.InferOutput<typeof TitleBarStyle> export const TitleBarStyle = v.enum_(TitleBarStyleEnum)
export const TitleBarStyle = v.picklist(Object.values(TitleBarStyleEnum))
// JS new WebViewWindow only accepts lowercase, while manifest loaded from Rust is capitalized. I run toLowerCase() on the value before passing it to the WebViewWindow. // JS new WebViewWindow only accepts lowercase, while manifest loaded from Rust is capitalized. I run toLowerCase() on the value before passing it to the WebViewWindow.
// This lowercase title bar style schema is used to validate and set the type so TypeScript won't complaint // This lowercase title bar style schema is used to validate and set the type so TypeScript won't complaint
// export const TitleBarStyleAllLower = z.enum(["visible", "transparent", "overlay"]); // export const TitleBarStyleAllLower = z.enum(["visible", "transparent", "overlay"]);
@ -70,33 +71,37 @@ export const WindowConfig = v.object({
export type WindowConfig = v.InferOutput<typeof WindowConfig> export type WindowConfig = v.InferOutput<typeof WindowConfig>
export const BaseCmd = v.object({ export const BaseCmd = v.object({
main: v.string("HTML file to load, e.g. dist/index.html"), main: v.string("HTML file to load, e.g. dist/index.html"),
description: v.optional(v.nullable(v.string("Description of the Command"), "")), description: v.optional(v.nullable(v.string("Description of the Command"), ""), ""),
name: v.string("Name of the command"), name: v.string("Name of the command"),
cmds: v.array(TriggerCmd, "Commands to trigger the UI"), cmds: v.array(TriggerCmd, "Commands to trigger the UI"),
icon: v.optional(Icon), icon: v.optional(Icon),
platforms: v.optional( platforms: v.optional(
v.array(OSPlatform, "Platforms available on. Leave empty for all platforms.") v.nullable(
v.array(OSPlatform, "Platforms available on. Leave empty for all platforms."),
allPlatforms
),
allPlatforms
) )
}) })
export const CustomUiCmd = v.object({ export const CustomUiCmd = v.object({
...BaseCmd.entries, ...BaseCmd.entries,
type: v.optional(CmdType, CmdType.enum.UiIframe),
dist: v.string("Dist folder to load, e.g. dist, build, out"), dist: v.string("Dist folder to load, e.g. dist, build, out"),
devMain: v.string( devMain: v.string(
"URL to load in development to support live reload, e.g. http://localhost:5173/" "URL to load in development to support live reload, e.g. http://localhost:5173/"
), ),
window: v.optional(v.nullable(WindowConfig)), window: v.optional(v.nullable(WindowConfig))
type: v.optional(CmdType, CmdTypeEnum.UiIframe)
}) })
export type CustomUiCmd = v.InferOutput<typeof CustomUiCmd> export type CustomUiCmd = v.InferOutput<typeof CustomUiCmd>
export const TemplateUiCmd = v.object({ export const TemplateUiCmd = v.object({
...BaseCmd.entries, ...BaseCmd.entries,
type: v.optional(CmdType, CmdTypeEnum.UiWorker), type: v.optional(CmdType, CmdType.enum.UiWorker),
window: v.optional(v.nullable(WindowConfig)) window: v.optional(v.nullable(WindowConfig))
}) })
export const HeadlessCmd = v.object({ export const HeadlessCmd = v.object({
...BaseCmd.entries, ...BaseCmd.entries,
type: v.optional(CmdType, CmdTypeEnum.HeadlessWorker) type: v.optional(CmdType, CmdType.enum.HeadlessWorker)
}) })
export type HeadlessCmd = v.InferOutput<typeof HeadlessCmd> export type HeadlessCmd = v.InferOutput<typeof HeadlessCmd>
export type TemplateUiCmd = v.InferOutput<typeof TemplateUiCmd> export type TemplateUiCmd = v.InferOutput<typeof TemplateUiCmd>
@ -190,8 +195,7 @@ export const ExtPackageJsonExtra = v.object({
...ExtPackageJson.entries, ...ExtPackageJson.entries,
...{ ...{
extPath: v.string(), extPath: v.string(),
extFolderName: v.string(), extFolderName: v.string()
apiVersion: v.optional(v.string("API version of the extension"))
} }
}) })

View File

@ -1,91 +0,0 @@
import * as v from "valibot"
import { BaseIcon } from "./icon"
import { ExtPackageJson, KunkunExtManifest } from "./manifest"
export const ExtPublishSourceTypeEnum = {
jsr: "jsr",
npm: "npm"
} as const
export const ExtPublishMetadata = v.object({
source: v.optional(v.string("Source of the extension (e.g. url to package)")),
sourceType: v.optional(v.picklist(Object.values(ExtPublishSourceTypeEnum))),
rekorLogIndex: v.optional(v.string("Rekor log index of the extension")),
git: v.optional(
v.object({
githubActionInvocationId: v.string("GitHub action invocation ID"),
repo: v.string("GitHub repo of the extension"),
owner: v.string("GitHub owner of the extension"),
commit: v.string("Commit hash of the extension"),
workflowPath: v.string("Workflow path of the extension"),
repoNodeId: v.optional(
v.string("GitHub repo node ID of the extension (a string, not the number id)")
)
})
)
})
export type ExtPublishMetadata = v.InferOutput<typeof ExtPublishMetadata>
/***
* Correspond to `extensions` table in supabase, missing a few fields
*/
export const ExtensionStoreListItem = v.object({
identifier: v.string(),
name: v.string(),
created_at: v.string(),
downloads: v.number(),
author_id: v.string(),
short_description: v.string(),
long_description: v.string(),
version: v.string(),
api_version: v.optional(v.string()),
icon: BaseIcon
})
export type ExtensionStoreListItem = v.InferOutput<typeof ExtensionStoreListItem>
export const PublishStateEnum = {
public: "public",
pending: "pending",
under_review: "under_review",
private: "private"
}
export const ExtensionPublishState = v.picklist(Object.values(PublishStateEnum))
export const ExtPublish = v.object({
id: v.number(),
name: v.string(),
tarball_path: v.string(),
created_at: v.string(),
version: v.string(),
manifest: KunkunExtManifest,
shasum: v.string(),
tarball_size: v.number(),
unpacked_size: v.nullable(v.number()),
cmd_count: v.number(),
identifier: v.string(),
downloads: v.number(),
demo_images: v.array(v.string()),
api_version: v.nullable(v.string()),
extension_state: ExtensionPublishState,
package_json: ExtPackageJson,
metadata: ExtPublishMetadata,
readme: v.nullable(v.string())
})
export type ExtPublish = v.InferOutput<typeof ExtPublish>
export const DBExtension = v.object({
api_version: v.string(),
author_id: v.string(),
created_at: v.string(),
downloads: v.number(),
icon: BaseIcon,
identifier: v.string(),
long_description: v.nullable(v.string()),
name: v.string(),
readme: v.nullable(v.string()),
short_description: v.string(),
tarball_size: v.nullable(v.number()),
version: v.string()
})
export type DBExtension = v.InferOutput<typeof DBExtension>

View File

@ -1,12 +1,12 @@
import * as v from "valibot" import { enum_, type InferOutput } from "valibot"
export enum SQLSortOrderEnum { export enum SQLSortOrderEnum {
Asc = "ASC", Asc = "ASC",
Desc = "DESC" Desc = "DESC"
} }
export const SQLSortOrder = v.enum_(SQLSortOrderEnum) export const SQLSortOrder = enum_(SQLSortOrderEnum)
export type SQLSortOrder = v.InferOutput<typeof SQLSortOrder> export type SQLSortOrder = InferOutput<typeof SQLSortOrder>
export enum SearchModeEnum { export enum SearchModeEnum {
ExactMatch = "exact_match", ExactMatch = "exact_match",
@ -14,8 +14,8 @@ export enum SearchModeEnum {
FTS = "fts" FTS = "fts"
} }
export const SearchMode = v.enum_(SearchModeEnum) export const SearchMode = enum_(SearchModeEnum)
export type SearchMode = v.InferOutput<typeof SearchMode> export type SearchMode = InferOutput<typeof SearchMode>
export function convertDateToSqliteString(date: Date) { export function convertDateToSqliteString(date: Date) {
const pad = (num: number) => num.toString().padStart(2, "0") const pad = (num: number) => num.toString().padStart(2, "0")
@ -29,6 +29,3 @@ export function convertDateToSqliteString(date: Date) {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
} }
export const ExtDataField = v.union([v.literal("data"), v.literal("search_text")])
export type ExtDataField = v.InferOutput<typeof ExtDataField>

View File

@ -2,7 +2,7 @@ import { DenoIo, RPCChannel } from "kkrpc"
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
export function expose(api: Record<string, any>) { export function expose(api: Record<string, any>) {
const stdio = new DenoIo(Deno.stdin.readable) const stdio = new DenoIo(Deno.stdin.readable, Deno.stdout.writable)
const channel = new RPCChannel(stdio, { expose: api }) const channel = new RPCChannel(stdio, { expose: api })
return channel return channel
} }

View File

@ -1,7 +1,6 @@
import * as pathAPI from "@tauri-apps/api/path" import * as pathAPI from "@tauri-apps/api/path"
import { BaseDirectory } from "@tauri-apps/api/path" import { BaseDirectory } from "@tauri-apps/api/path"
import { exists, mkdir } from "@tauri-apps/plugin-fs" import { exists, mkdir } from "@tauri-apps/plugin-fs"
import { platform } from "@tauri-apps/plugin-os"
import { minimatch } from "minimatch" import { minimatch } from "minimatch"
import type { import type {
FsPermissionScoped, FsPermissionScoped,
@ -93,12 +92,8 @@ export async function matchPathAndScope(
scope: string, scope: string,
extensionDir: string extensionDir: string
): Promise<boolean> { ): Promise<boolean> {
let translatedTarget = await translateScopeToPath(target, extensionDir) const translatedTarget = await translateScopeToPath(target, extensionDir)
let translatedScope = await translateScopeToPath(scope, extensionDir) const translatedScope = await translateScopeToPath(scope, extensionDir)
if (platform() === "windows") {
translatedTarget = translatedTarget.replaceAll("\\", "/")
translatedScope = translatedScope.replaceAll("\\", "/")
}
return minimatch(translatedTarget, translatedScope) return minimatch(translatedTarget, translatedScope)
} }

View File

@ -21,7 +21,7 @@ export const breakingChangesVersionCheckpoints = [
const checkpointVersions = breakingChangesVersionCheckpoints.map((c) => c.version) const checkpointVersions = breakingChangesVersionCheckpoints.map((c) => c.version)
const sortedCheckpointVersions = sort(checkpointVersions) const sortedCheckpointVersions = sort(checkpointVersions)
export const version = "0.1.7" export const version = "0.1.5"
export function isVersionBetween(v: string, start: string, end: string) { export function isVersionBetween(v: string, start: string, end: string) {
const vCleaned = clean(v) const vCleaned = clean(v)

View File

@ -4,9 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
rusqlite = { version = "0.31.0", features = [ rusqlite = { version = "0.31.0", features = ["bundled-sqlcipher-vendored-openssl"] }
"bundled-sqlcipher-vendored-openssl",
] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
tempfile = "3.10.1" tempfile = "3.10.1"

View File

@ -2,7 +2,6 @@ pub mod models;
pub mod schema; pub mod schema;
use models::{CmdType, ExtDataField, ExtDataSearchQuery, SearchMode}; use models::{CmdType, ExtDataField, ExtDataSearchQuery, SearchMode};
use rusqlite::{params, params_from_iter, Connection, Result, ToSql}; use rusqlite::{params, params_from_iter, Connection, Result, ToSql};
use serde_json::Value as JsonValue;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::path::Path; use std::path::Path;
@ -123,97 +122,6 @@ impl JarvisDB {
Ok(()) Ok(())
} }
pub fn select(&self, query: String, values: Vec<JsonValue>) -> Result<Vec<JsonValue>> {
println!("DB selecting: {}", query);
println!("DB selecting values: {:?}", values);
let mut stmt = self.conn.prepare(&query)?;
// Convert JsonValue parameters to appropriate types for rusqlite
let mut params: Vec<Box<dyn ToSql>> = Vec::new();
for value in values {
if value.is_null() {
params.push(Box::new(Option::<String>::None));
} else if value.is_string() {
params.push(Box::new(value.as_str().unwrap().to_owned()));
} else if let Some(number) = value.as_number() {
if number.is_i64() {
params.push(Box::new(number.as_i64().unwrap()));
} else if number.is_u64() {
params.push(Box::new(number.as_u64().unwrap() as i64));
} else {
params.push(Box::new(number.as_f64().unwrap()));
}
} else {
params.push(Box::new(value.to_string()));
}
}
// Get column names from statement
let column_names: Vec<String> = (0..stmt.column_count())
.map(|i| stmt.column_name(i).unwrap().to_string())
.collect();
// Execute the query with the converted parameters and map results
let rows = stmt.query_map(params_from_iter(params.iter().map(|p| p.as_ref())), |row| {
let mut result = Vec::new();
for i in 0..column_names.len() {
let value: Value = match row.get_ref(i)? {
rusqlite::types::ValueRef::Null => Value::Null,
rusqlite::types::ValueRef::Integer(i) => Value::Number(i.into()),
rusqlite::types::ValueRef::Real(r) => Value::Number(
serde_json::Number::from_f64(r).unwrap_or(serde_json::Number::from(0)),
),
rusqlite::types::ValueRef::Text(t) => {
Value::String(String::from_utf8_lossy(t).into_owned())
}
rusqlite::types::ValueRef::Blob(b) => {
Value::String(String::from_utf8_lossy(b).into_owned())
}
};
result.push(value);
}
Ok(Value::Array(result))
})?;
let mut results = Vec::new();
for row in rows {
results.push(row?);
}
Ok(results)
}
pub fn execute(&self, query: &str, values: Vec<JsonValue>) -> Result<(u64, i64)> {
let mut stmt = self.conn.prepare(query)?;
// Convert JsonValue parameters to appropriate types for rusqlite
let mut params: Vec<Box<dyn ToSql>> = Vec::new();
for value in values {
if value.is_null() {
params.push(Box::new(Option::<String>::None));
} else if value.is_string() {
params.push(Box::new(value.as_str().unwrap().to_owned()));
} else if let Some(number) = value.as_number() {
if number.is_i64() {
params.push(Box::new(number.as_i64().unwrap()));
} else if number.is_u64() {
params.push(Box::new(number.as_u64().unwrap() as i64));
} else {
params.push(Box::new(number.as_f64().unwrap()));
}
} else {
params.push(Box::new(value.to_string()));
}
}
// Execute the query with the converted parameters
let rows_affected = stmt.execute(params_from_iter(params.iter().map(|p| p.as_ref())))?;
// Get the last insert rowid
let last_insert_id = self.conn.last_insert_rowid();
Ok((rows_affected as u64, last_insert_id))
}
pub fn get_all_extensions(&self) -> Result<Vec<models::Ext>> { pub fn get_all_extensions(&self) -> Result<Vec<models::Ext>> {
let mut stmt = self.conn.prepare( let mut stmt = self.conn.prepare(
"SELECT ext_id, identifier, path, data, version, enabled, installed_at FROM extensions", "SELECT ext_id, identifier, path, data, version, enabled, installed_at FROM extensions",
@ -1075,72 +983,4 @@ mod tests {
fs::remove_file(&db_path).unwrap(); fs::remove_file(&db_path).unwrap();
} }
#[test]
fn test_select_and_execute() {
let dir = tempdir().unwrap();
let db_path = dir.path().join("test.db");
let db = JarvisDB::new(&db_path, None).unwrap();
db.init().unwrap();
// Create a simple todo table
db.execute(
"CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT, completed BOOLEAN)",
vec![],
)
.unwrap();
// Test execute with INSERT
let (rows_affected, last_id) = db
.execute(
"INSERT INTO todos (title, completed) VALUES (?1, ?2)",
vec![json!("Buy groceries"), json!(false)],
)
.unwrap();
assert_eq!(rows_affected, 1);
assert_eq!(last_id, 1);
// Test select with basic query
let results = db
.select(
"SELECT title, completed FROM todos WHERE id = ?1".to_string(),
vec![json!(1)],
)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0][0], "Buy groceries");
assert_eq!(results[0][1], "false");
// Test execute with UPDATE
let (rows_affected, _) = db
.execute(
"UPDATE todos SET completed = ?1 WHERE id = ?2",
vec![json!(true), json!(1)],
)
.unwrap();
assert_eq!(rows_affected, 1);
// Verify the update with select
let results = db
.select(
"SELECT completed FROM todos WHERE id = ?1".to_string(),
vec![json!(1)],
)
.unwrap();
assert_eq!(results[0][0], "true");
// Test execute with DELETE
let (rows_affected, _) = db
.execute("DELETE FROM todos WHERE id = ?1", vec![json!(1)])
.unwrap();
assert_eq!(rows_affected, 1);
// Verify the deletion
let results = db
.select("SELECT COUNT(*) as count FROM todos".to_string(), vec![])
.unwrap();
assert_eq!(results[0][0], 0);
fs::remove_file(&db_path).unwrap();
}
} }

View File

@ -1,34 +0,0 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@ -1,35 +0,0 @@
# drizzle
- Only use `pull` to generate the schema from existing database.
- Don't `migrate` or `push`.
```bash
export DB_FILE_NAME="~/Library/Application Support/sh.kunkun.desktop/kk.dev.sqlite"
bunx drizzle-kit pull
```
We are using sqlite with fts5, which drizzle doesn't support yet, so pushing the schema will destroy the existing schema.
We only use pulled schema to generate sql queries.
## Update Schema
After `drizzle-kit pull` the schema may have problem with JSON type and boolean type.
Will need to manually update the following
### JSON
```diff lang="ts"
+ data: text({ mode: "json" }).notNull(),
+ metadata: text({ mode: "json" }),
- data: numeric().notNull(),
- metadata: numeric(),
```
### Boolean
```diff lang="ts"
+ enabled: integer({ mode: "boolean" }),
- enabled: numeric().default(sql`(TRUE)`),
```

View File

@ -1,15 +0,0 @@
import "dotenv/config"
import { defineConfig } from "drizzle-kit"
if (!process.env.DB_FILE_NAME) {
throw new Error("DB_FILE_NAME is not set")
}
export default defineConfig({
out: "./drizzle",
// schema: "./src/db/schema.ts",
dialect: "sqlite",
dbCredentials: {
url: process.env.DB_FILE_NAME
}
})

View File

@ -1,71 +0,0 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE TABLE `schema_version` (
`version` integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE `extensions` (
`ext_id` integer PRIMARY KEY AUTOINCREMENT,
`identifier` text NOT NULL,
`version` text NOT NULL,
`enabled` numeric DEFAULT (TRUE),
`path` text,
`data` numeric,
`installed_at` numeric DEFAULT (CURRENT_TIMESTAMP)
);
--> statement-breakpoint
CREATE TABLE `commands` (
`cmd_id` integer PRIMARY KEY AUTOINCREMENT,
`ext_id` integer NOT NULL,
`name` text NOT NULL,
`enabled` numeric DEFAULT (TRUE),
`alias` text,
`hotkey` text,
`type` text NOT NULL,
`data` numeric,
FOREIGN KEY (`ext_id`) REFERENCES `extensions`(`ext_id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `extension_data` (
`data_id` integer PRIMARY KEY AUTOINCREMENT,
`ext_id` integer NOT NULL,
`data_type` text NOT NULL,
`data` numeric NOT NULL,
`metadata` numeric,
`search_text` text,
`created_at` numeric DEFAULT (CURRENT_TIMESTAMP),
`updated_at` numeric DEFAULT (CURRENT_TIMESTAMP),
FOREIGN KEY (`ext_id`) REFERENCES `extensions`(`ext_id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `extension_data_fts` (
`data_id` numeric,
`search_text` numeric,
`extension_data_fts` numeric,
`rank` numeric
);
--> statement-breakpoint
CREATE TABLE `extension_data_fts_data` (
`id` integer PRIMARY KEY,
`block` blob
);
--> statement-breakpoint
CREATE TABLE `extension_data_fts_idx` (
`segid` numeric NOT NULL,
`term` numeric NOT NULL,
`pgno` numeric,
PRIMARY KEY(`segid`, `term`)
);
--> statement-breakpoint
CREATE TABLE `extension_data_fts_docsize` (
`id` integer PRIMARY KEY,
`sz` blob
);
--> statement-breakpoint
CREATE TABLE `extension_data_fts_config` (
`k` numeric PRIMARY KEY NOT NULL,
`v` numeric
);
*/

View File

@ -1,405 +0,0 @@
{
"id": "00000000-0000-0000-0000-000000000000",
"prevId": "",
"version": "6",
"dialect": "sqlite",
"tables": {
"schema_version": {
"name": "schema_version",
"columns": {
"version": {
"autoincrement": false,
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extensions": {
"name": "extensions",
"columns": {
"ext_id": {
"autoincrement": true,
"name": "ext_id",
"type": "integer",
"primaryKey": true,
"notNull": false
},
"identifier": {
"autoincrement": false,
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"version": {
"autoincrement": false,
"name": "version",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"default": "(TRUE)",
"autoincrement": false,
"name": "enabled",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"path": {
"autoincrement": false,
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": false
},
"data": {
"autoincrement": false,
"name": "data",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"installed_at": {
"default": "(CURRENT_TIMESTAMP)",
"autoincrement": false,
"name": "installed_at",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"commands": {
"name": "commands",
"columns": {
"cmd_id": {
"autoincrement": true,
"name": "cmd_id",
"type": "integer",
"primaryKey": true,
"notNull": false
},
"ext_id": {
"autoincrement": false,
"name": "ext_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"name": {
"autoincrement": false,
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"default": "(TRUE)",
"autoincrement": false,
"name": "enabled",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"alias": {
"autoincrement": false,
"name": "alias",
"type": "text",
"primaryKey": false,
"notNull": false
},
"hotkey": {
"autoincrement": false,
"name": "hotkey",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"autoincrement": false,
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"data": {
"autoincrement": false,
"name": "data",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"commands_ext_id_extensions_ext_id_fk": {
"name": "commands_ext_id_extensions_ext_id_fk",
"tableFrom": "commands",
"tableTo": "extensions",
"columnsFrom": [
"ext_id"
],
"columnsTo": [
"ext_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data": {
"name": "extension_data",
"columns": {
"data_id": {
"autoincrement": true,
"name": "data_id",
"type": "integer",
"primaryKey": true,
"notNull": false
},
"ext_id": {
"autoincrement": false,
"name": "ext_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"data_type": {
"autoincrement": false,
"name": "data_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"data": {
"autoincrement": false,
"name": "data",
"type": "numeric",
"primaryKey": false,
"notNull": true
},
"metadata": {
"autoincrement": false,
"name": "metadata",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"search_text": {
"autoincrement": false,
"name": "search_text",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "(CURRENT_TIMESTAMP)",
"autoincrement": false,
"name": "created_at",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"default": "(CURRENT_TIMESTAMP)",
"autoincrement": false,
"name": "updated_at",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"extension_data_ext_id_extensions_ext_id_fk": {
"name": "extension_data_ext_id_extensions_ext_id_fk",
"tableFrom": "extension_data",
"tableTo": "extensions",
"columnsFrom": [
"ext_id"
],
"columnsTo": [
"ext_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data_fts": {
"name": "extension_data_fts",
"columns": {
"data_id": {
"autoincrement": false,
"name": "data_id",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"search_text": {
"autoincrement": false,
"name": "search_text",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"extension_data_fts": {
"autoincrement": false,
"name": "extension_data_fts",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"rank": {
"autoincrement": false,
"name": "rank",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data_fts_data": {
"name": "extension_data_fts_data",
"columns": {
"id": {
"autoincrement": false,
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": false
},
"block": {
"autoincrement": false,
"name": "block",
"type": "blob",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data_fts_idx": {
"name": "extension_data_fts_idx",
"columns": {
"segid": {
"autoincrement": false,
"name": "segid",
"type": "numeric",
"primaryKey": false,
"notNull": true
},
"term": {
"autoincrement": false,
"name": "term",
"type": "numeric",
"primaryKey": false,
"notNull": true
},
"pgno": {
"autoincrement": false,
"name": "pgno",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {
"extension_data_fts_idx_segid_term_pk": {
"columns": [
"segid",
"term"
],
"name": "extension_data_fts_idx_segid_term_pk"
}
},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data_fts_docsize": {
"name": "extension_data_fts_docsize",
"columns": {
"id": {
"autoincrement": false,
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": false
},
"sz": {
"autoincrement": false,
"name": "sz",
"type": "blob",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"extension_data_fts_config": {
"name": "extension_data_fts_config",
"columns": {
"k": {
"autoincrement": false,
"name": "k",
"type": "numeric",
"primaryKey": true,
"notNull": true
},
"v": {
"autoincrement": false,
"name": "v",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1742544246324,
"tag": "0000_colossal_jocasta",
"breakpoints": true
}
]
}

View File

@ -1,21 +0,0 @@
import { relations } from "drizzle-orm/relations"
import { commands, extensionData, extensions } from "./schema"
export const commandsRelations = relations(commands, ({ one }) => ({
extension: one(extensions, {
fields: [commands.extId],
references: [extensions.extId]
})
}))
export const extensionsRelations = relations(extensions, ({ many }) => ({
commands: many(commands),
extensionData: many(extensionData)
}))
export const extensionDataRelations = relations(extensionData, ({ one }) => ({
extension: one(extensions, {
fields: [extensionData.extId],
references: [extensions.extId]
})
}))

Some files were not shown because too many files have changed in this diff Show More