feat(stage-tamagotchi): built-in local loopback http server

This commit is contained in:
Neko Ayaka
2026-04-20 20:39:12 +08:00
parent 60456c7567
commit 033e76c2f1
24 changed files with 1620 additions and 384 deletions
+3
View File
@@ -87,6 +87,7 @@
"alien-signals": "catalog:",
"animejs": "^4.3.6",
"async-mutex": "catalog:",
"chess.js": "catalog:",
"colorjs.io": "^0.6.1",
"culori": "^4.0.2",
"d3": "catalog:",
@@ -127,6 +128,7 @@
"vue-router": "^5.0.4",
"vue-sonner": "catalog:",
"web-haptics": "catalog:",
"whatwg-mimetype": "catalog:",
"xsschema": "catalog:",
"zod": "^4.3.6"
},
@@ -165,6 +167,7 @@
"@types/semver": "^7.7.1",
"@types/splitpanes": "catalog:",
"@types/three": "^0.184.0",
"@types/whatwg-mimetype": "catalog:",
"@types/yauzl": "^2.10.3",
"@vitejs/plugin-vue": "^6.0.6",
"@vue-macros/volar": "^3.1.2",
+6 -1
View File
@@ -25,6 +25,7 @@ import { setElectronMainDirname } from './libs/electron/location'
import { createI18n } from './libs/i18n'
import { createWindowAuthManagerService } from './services/airi/auth'
import { setupServerChannel } from './services/airi/channel-server'
import { setupBuiltInServer } from './services/airi/http-server'
import { setupMcpStdioManager } from './services/airi/mcp-servers'
import { setupPluginHost } from './services/airi/plugins'
import { setupAutoUpdater } from './services/electron/auto-updater'
@@ -125,6 +126,10 @@ app.whenReady().then(async () => {
build: async ({ dependsOn }) => setupServerChannel(dependsOn),
})
const airiHttpServer = injeca.provide('modules:airi-http-server', {
build: async () => setupBuiltInServer({ servers: [] }),
})
const mcpStdioManager = injeca.provide('modules:mcp-stdio-manager', {
build: async () => setupMcpStdioManager(),
})
@@ -187,7 +192,7 @@ app.whenReady().then(async () => {
})
injeca.invoke({
dependsOn: { mainWindow, tray, serverChannel, pluginHost, mcpStdioManager, onboardingWindow: onboardingWindowManager },
dependsOn: { mainWindow, tray, serverChannel, airiHttpServer, pluginHost, mcpStdioManager, onboardingWindow: onboardingWindowManager },
callback: noop,
})
@@ -17,7 +17,7 @@ import {
electronAuthLogout,
electronAuthStartLogin,
} from '../../../shared/eventa'
import { startLoopbackServer } from '../electron/loopback-server'
import { startLoopbackServer } from './http-server/http/auth'
const log = useLogg('auth-service').useGlobalConfig()
@@ -0,0 +1,72 @@
import { HTTPError } from 'h3'
export interface HttpErrorInput {
status: number
code: string
message: string
reason?: string
details?: unknown
expose?: boolean
}
/**
* Unified HTTP error shape for AIRI local HTTP server modules.
*
* Use when:
* - Returning typed errors from internal server modules
* - Keeping stable error code + reason metadata for logging/debugging
*
* Expects:
* - `code` is a stable machine-readable value
* - `message` is safe for clients when `expose` is true
*
* Returns:
* - Error instance with status and structured metadata
*/
export class HttpError extends Error {
readonly status: number
readonly code: string
readonly reason?: string
readonly details?: unknown
readonly expose: boolean
constructor(input: HttpErrorInput) {
super(input.message)
this.name = 'HttpError'
this.status = input.status
this.code = input.code
this.reason = input.reason
this.details = input.details
this.expose = input.expose ?? false
}
}
/**
* Converts a local `HttpError` into an h3-compatible throwable error.
*
* Use when:
* - A route catches internal `HttpError` values and must throw an h3 HTTP error
*
* Expects:
* - `error.message` is only exposed when `error.expose` is true
*
* Returns:
* - `HTTPError` preserving status while controlling client-visible message
*/
export function toH3HttpError(error: HttpError) {
return HTTPError.status(error.status, error.expose ? error.message : defaultHttpMessage(error.status))
}
function defaultHttpMessage(status: number) {
if (status === 400)
return 'Bad Request'
if (status === 401)
return 'Unauthorized'
if (status === 403)
return 'Forbidden'
if (status === 404)
return 'Not Found'
if (status >= 500)
return 'Internal Server Error'
return 'Request Failed'
}
@@ -0,0 +1,24 @@
import { afterEach, describe, expect, it } from 'vitest'
import { startLoopbackServer } from './index'
describe('startLoopbackServer', () => {
const servers: Array<Awaited<ReturnType<typeof startLoopbackServer>>> = []
afterEach(async () => {
for (const server of servers.splice(0)) {
server.close()
await server.result.catch(() => {})
}
})
it('returns code and state from callback', async () => {
const server = await startLoopbackServer()
servers.push(server)
const response = await fetch(`http://127.0.0.1:${server.port}/callback?code=ok&state=state-1`)
expect(response.status).toBe(200)
await expect(server.result).resolves.toEqual({ code: 'ok', state: 'state-1' })
})
})
@@ -0,0 +1,131 @@
import { eventHandler, getQuery, H3, handleCors } from 'h3'
import { createH3Server } from '../../server'
export interface LoopbackCallbackResult {
code: string
state: string
}
/**
* Starts a temporary loopback callback server for the Electron OIDC flow.
*
* Use when:
* - Exchanging authorization code from system browser callback
*
* Expects:
* - Callback request on `GET /callback?code=...&state=...`
* - One-shot lifecycle; first successful callback closes the server
*
* Returns:
* - Random bound port, callback result promise, and manual cancellation method
*/
export async function startLoopbackServer(): Promise<{
port: number
result: Promise<LoopbackCallbackResult>
close: () => void
}> {
const host = '127.0.0.1'
let settled = false
let timeout: ReturnType<typeof setTimeout> | undefined
let resolveResult!: (value: LoopbackCallbackResult) => void
let rejectResult!: (reason: Error) => void
const result = new Promise<LoopbackCallbackResult>((resolve, reject) => {
resolveResult = resolve
rejectResult = reject
})
const app = new H3()
const loopbackServer = createH3Server({ app, host })
const corsOptions = {
origin: '*',
methods: '*',
preflight: {
statusCode: 204,
},
} as const
const finish = (callback: () => void) => {
if (settled) {
return
}
settled = true
if (timeout) {
clearTimeout(timeout)
timeout = undefined
}
callback()
void loopbackServer.stop()
}
app.options('/callback', eventHandler(async (event) => {
const corsResponse = handleCors(event, corsOptions)
if (corsResponse !== false) {
return corsResponse
}
return new Response(null, { status: 204 })
}))
app.get('/callback', eventHandler(async (event) => {
const corsResponse = handleCors(event, corsOptions)
if (corsResponse !== false) {
return corsResponse
}
const query = getQuery(event)
const error = typeof query.error === 'string' ? query.error : undefined
if (error) {
const description = typeof query.error_description === 'string' && query.error_description.length > 0
? query.error_description
: error
finish(() => {
rejectResult(new Error(description))
})
return new Response('<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>', {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})
}
const code = typeof query.code === 'string' ? query.code : ''
const state = typeof query.state === 'string' ? query.state : ''
if (!code || !state) {
return new Response('<html><body><h2>Missing parameters</h2></body></html>', {
status: 400,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})
}
finish(() => {
resolveResult({ code, state })
})
return new Response('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the app.</p></body></html>', {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})
}))
const address = await loopbackServer.start()
timeout = setTimeout(() => {
finish(() => {
rejectResult(new Error('Sign-in timed out — no callback received'))
})
}, 5 * 60 * 1000)
return {
port: address.port,
result,
close: () => {
finish(() => {
rejectResult(new Error('OIDC sign-in attempt cancelled'))
})
},
}
}
@@ -0,0 +1,70 @@
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { createExtensionStaticAssetServer } from './index'
import { createExtensionAssetTokenStore } from './token-store'
describe('createExtensionStaticAssetServer', () => {
const servers: Array<ReturnType<typeof createExtensionStaticAssetServer>> = []
afterEach(async () => {
while (servers.length > 0) {
const server = servers.pop()
if (!server) {
continue
}
await server.stop()
}
})
it('accepts pathPrefix relative to /ui route segment', async () => {
const rootDir = await mkdtemp(join(tmpdir(), 'airi-extension-static-assets-'))
await mkdir(join(rootDir, 'ui', 'assets'), { recursive: true })
await writeFile(join(rootDir, 'ui', 'assets', 'app.js'), 'console.log("ok")\n')
const extensionId = 'airi-plugin-game-chess'
const version = '1.0.0'
const tokenStore = createExtensionAssetTokenStore()
const validateInputs: string[] = []
const server = createExtensionStaticAssetServer({
getManifestEntryByName: () => new Map([
[extensionId, { rootDir, version }],
]),
tokenStore: {
...tokenStore,
validate(token, input) {
validateInputs.push(input.assetPath)
return tokenStore.validate(token, input)
},
},
})
servers.push(server)
await server.start()
const token = server.issueToken({
extensionId,
version,
sessionId: 'session-1',
pathPrefix: 'assets/',
ttlMs: 60_000,
})
const baseUrl = server.getBaseUrl()
expect(baseUrl).toBeTruthy()
const response = await fetch(`${baseUrl}/_airi/extensions/${extensionId}/ui/assets/app.js?t=${token}`)
const responseBody = await response.text()
expect({
status: response.status,
validateInputs,
responseBody,
}).toEqual({
status: 200,
validateInputs: ['assets/app.js'],
responseBody: 'console.log("ok")\n',
})
})
})
@@ -0,0 +1,179 @@
import type { ServerManager } from '../../server-manager/types'
import type { ExtensionAssetTokenStore } from './types'
import { stat } from 'node:fs/promises'
import { H3 } from 'h3'
import {
normalizePluginAssetPath,
resolvePluginAssetFilePath,
} from '../../../plugins/asset-mount'
import { HttpError } from '../../errors'
import { createH3Server } from '../../server'
import { createExtensionStaticAssetRoute } from './route'
import { createExtensionAssetTokenStore } from './token-store'
export interface ExtensionStaticAssetManifestEntry {
rootDir: string
version: string
}
export interface ExtensionStaticAssetServer extends ServerManager {
getBaseUrl: () => string | undefined
issueToken: ExtensionAssetTokenStore['issue']
revokeByExtensionId: ExtensionAssetTokenStore['revokeByExtensionId']
revokeAll: ExtensionAssetTokenStore['revokeAll']
}
/**
* Creates the standalone extension static asset server.
*
* Use when:
* - Main process must serve plugin iframe assets via local loopback HTTP
* - Tokenized auth is required for all plugin asset requests
*
* Expects:
* - `getManifestEntryByName` returns up-to-date plugin root/version map
*
* Returns:
* - Lifecycle service with token issue/revoke APIs and local base URL getter
*/
export function createExtensionStaticAssetServer(options: {
getManifestEntryByName: () => Map<string, ExtensionStaticAssetManifestEntry>
host?: string
tokenStore?: ExtensionAssetTokenStore
getType?: (ext: string) => string | undefined
}): ExtensionStaticAssetServer {
const host = options.host ?? '127.0.0.1'
const tokenStore = options.tokenStore ?? createExtensionAssetTokenStore()
const getType = options.getType ?? defaultExtensionAssetMimeTypeResolver
const app = new H3()
const serverLifecycle = createH3Server({ app, host })
app.get('/_airi/extensions/**', createExtensionStaticAssetRoute({
getType,
authorize: async ({ token, extensionId, assetPath }) => {
const entry = options.getManifestEntryByName().get(extensionId)
if (!entry) {
return {
ok: false,
error: new HttpError({
status: 401,
code: 'EXTENSION_ASSET_EXTENSION_NOT_REGISTERED',
message: 'Unauthorized',
reason: 'extension manifest entry does not exist for requested extensionId',
}),
}
}
return tokenStore.validate(token, {
extensionId,
version: entry.version,
assetPath,
})
},
resolveAsset: async ({ extensionId, assetPath }) => {
const entry = options.getManifestEntryByName().get(extensionId)
if (!entry) {
return {
ok: false,
error: new HttpError({
status: 404,
code: 'EXTENSION_ASSET_EXTENSION_NOT_FOUND',
message: 'Not Found',
reason: 'extension manifest entry does not exist for requested extensionId',
}),
}
}
const normalizedAssetPath = normalizePluginAssetPath(assetPath)
if (!normalizedAssetPath) {
return {
ok: false,
error: new HttpError({
status: 400,
code: 'EXTENSION_ASSET_PATH_INVALID',
message: 'Bad Request',
reason: 'asset path could not be normalized',
}),
}
}
const fullAssetPath = `ui/${normalizedAssetPath}`
const filePath = await resolvePluginAssetFilePath(entry.rootDir, fullAssetPath)
if (!filePath) {
return {
ok: false,
error: new HttpError({
status: 400,
code: 'EXTENSION_ASSET_PATH_RESOLVE_FAILED',
message: 'Bad Request',
reason: 'resolved asset path is outside extension root',
}),
}
}
try {
const fileStats = await stat(filePath)
if (!fileStats.isFile()) {
return {
ok: false,
error: new HttpError({
status: 404,
code: 'EXTENSION_ASSET_NOT_FILE',
message: 'Not Found',
reason: 'resolved path exists but is not a file',
}),
}
}
return {
ok: true,
filePath,
size: fileStats.size,
mtime: fileStats.mtimeMs,
}
}
catch {
return {
ok: false,
error: new HttpError({
status: 404,
code: 'EXTENSION_ASSET_NOT_FOUND',
message: 'Not Found',
reason: 'resolved file does not exist',
}),
}
}
},
}))
return {
key: 'extension-static-assets',
async start() {
await serverLifecycle.start()
},
async stop() {
await serverLifecycle.stop()
},
getBaseUrl() {
return serverLifecycle.getAddress()?.baseUrl
},
issueToken: tokenStore.issue,
revokeByExtensionId: tokenStore.revokeByExtensionId,
revokeAll: tokenStore.revokeAll,
}
}
const extensionAssetMimeTypeOverrides: Record<string, string> = {
'.wasm': 'application/wasm',
'.avif': 'image/avif',
'.heic': 'image/heic',
'.heif': 'image/heif',
}
function defaultExtensionAssetMimeTypeResolver(ext: string) {
return extensionAssetMimeTypeOverrides[ext.toLowerCase()]
}
@@ -0,0 +1,100 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { H3 } from 'h3'
import { toNodeHandler } from 'h3/node'
import { afterEach, describe, expect, it } from 'vitest'
import { HttpError } from '../../errors'
import { createExtensionStaticAssetRoute } from './route'
describe('createExtensionStaticAssetRoute', () => {
let server: ReturnType<typeof createServer> | undefined
const tempRoots: string[] = []
afterEach(async () => {
for (const root of tempRoots) {
await rm(root, { recursive: true, force: true })
}
tempRoots.length = 0
await new Promise<void>((resolve) => {
if (!server) {
resolve()
return
}
server.close(() => resolve())
server = undefined
})
})
it('returns 401 when token is missing', async () => {
const app = new H3()
app.get('/_airi/extensions/:extensionId/ui/**assetPath', createExtensionStaticAssetRoute({
authorize: async () => ({
ok: false,
error: new HttpError({
status: 401,
code: 'TOKEN_MISSING',
message: 'Unauthorized',
}),
}),
resolveAsset: async () => ({
ok: false,
error: new HttpError({
status: 404,
code: 'NOT_FOUND',
message: 'Not Found',
}),
}),
}))
server = createServer(toNodeHandler(app))
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', () => resolve()))
const address = server.address()
const port = typeof address === 'object' && address ? address.port : 0
const response = await fetch(`http://127.0.0.1:${port}/_airi/extensions/a/ui/index.html`)
expect(response.status).toBe(401)
})
it('uses custom getType resolver and sets nosniff', async () => {
const root = await mkdtemp(join(tmpdir(), 'airi-extension-static-assets-'))
tempRoots.push(root)
const wasmFilePath = join(root, 'module.wasm')
await writeFile(wasmFilePath, new Uint8Array([0, 97, 115, 109]))
let authorizeCalled = false
const app = new H3()
app.get('/_airi/extensions/:extensionId/ui/:assetPath', createExtensionStaticAssetRoute({
authorize: async () => {
authorizeCalled = true
return { ok: true }
},
resolveAsset: async () => ({
ok: true,
filePath: wasmFilePath,
size: 4,
mtime: Date.now(),
}),
getType: ext => ext === '.wasm' ? 'application/wasm' : undefined,
}))
server = createServer(toNodeHandler(app))
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', () => resolve()))
const address = server.address()
const port = typeof address === 'object' && address ? address.port : 0
const response = await fetch(`http://127.0.0.1:${port}/_airi/extensions/a/ui/module.wasm?t=test-token`)
if (!authorizeCalled) {
throw new Error(`Expected authorize to be called. response=${response.status} body=${await response.text()}`)
}
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('application/wasm')
expect(response.headers.get('x-content-type-options')).toBe('nosniff')
})
})
@@ -0,0 +1,87 @@
import type { ExtensionStaticAssetRouteOptions } from './types'
import { readFile } from 'node:fs/promises'
import { eventHandler, getQuery, getRequestURL, serveStatic } from 'h3'
import {
normalizePluginAssetPath,
parsePluginAssetRequestPath,
} from '../../../plugins/asset-mount'
import { HttpError, toH3HttpError } from '../../errors'
/**
* Creates the secured extension static asset route handler.
*
* Use when:
* - Serving plugin iframe assets under `/_airi/extensions/:extensionId/ui/**assetPath`
*
* Expects:
* - Query token `t` to be present and valid
* - `resolveAsset` to map request params into a validated local file
*
* Returns:
* - H3 event handler that enforces token auth before static file response
*/
export function createExtensionStaticAssetRoute(options: ExtensionStaticAssetRouteOptions) {
return eventHandler(async (event) => {
try {
const requestPath = parsePluginAssetRequestPath(getRequestURL(event).pathname)
const extensionId = requestPath?.extensionId ?? ''
const assetPath = normalizePluginAssetPath(requestPath?.assetPath ?? '')
const queryToken = getQuery(event).t
const token = typeof queryToken === 'string' ? queryToken : ''
if (!token || !extensionId || !assetPath) {
throw new HttpError({
status: 401,
code: 'EXTENSION_ASSET_REQUEST_INVALID',
message: 'Unauthorized',
reason: 'required token, extensionId, or assetPath is missing',
})
}
const auth = await options.authorize({ token, extensionId, assetPath })
if (!auth.ok) {
throw auth.error
}
let resolved: Awaited<ReturnType<ExtensionStaticAssetRouteOptions['resolveAsset']>> | undefined
const resolveOnce = async () => {
if (!resolved) {
resolved = await options.resolveAsset({ extensionId, assetPath })
}
return resolved
}
return await serveStatic(event, {
getType: options.getType,
getContents: async () => {
const item = await resolveOnce()
if (!item.ok) {
throw item.error
}
return await readFile(item.filePath)
},
getMeta: async () => {
const item = await resolveOnce()
if (!item.ok) {
return undefined
}
event.res.headers.set('X-Content-Type-Options', 'nosniff')
return {
size: item.size,
mtime: item.mtime,
}
},
})
}
catch (error) {
if (error instanceof HttpError) {
throw toH3HttpError(error)
}
throw error
}
})
}
@@ -0,0 +1,64 @@
import { describe, expect, it, vi } from 'vitest'
import { createExtensionAssetTokenStore } from './token-store'
describe('createExtensionAssetTokenStore', () => {
it('issues and validates short token', () => {
const now = vi.fn(() => 1000)
const store = createExtensionAssetTokenStore({ now })
const token = store.issue({
extensionId: 'airi-plugin-game-chess',
version: '0.1.0',
sessionId: 'session-1',
pathPrefix: 'ui/',
ttlMs: 30_000,
})
expect(token.length).toBeLessThanOrEqual(24)
expect(store.validate(token, {
extensionId: 'airi-plugin-game-chess',
version: '0.1.0',
assetPath: 'ui/index.html',
}).ok).toBe(true)
})
it('rejects mismatched plugin and revoked tokens', () => {
const now = vi.fn(() => 1000)
const store = createExtensionAssetTokenStore({ now })
const token = store.issue({
extensionId: 'airi-plugin-game-chess',
version: '0.1.0',
sessionId: 'session-1',
pathPrefix: 'ui/',
ttlMs: 30_000,
})
expect(store.validate(token, {
extensionId: 'other-plugin',
version: '0.1.0',
assetPath: 'ui/index.html',
})).toMatchObject({
ok: false,
error: {
status: 401,
code: 'EXTENSION_ASSET_EXTENSION_MISMATCH',
},
})
store.revokeByExtensionId('airi-plugin-game-chess')
expect(store.validate(token, {
extensionId: 'airi-plugin-game-chess',
version: '0.1.0',
assetPath: 'ui/index.html',
})).toMatchObject({
ok: false,
error: {
status: 401,
code: 'EXTENSION_ASSET_TOKEN_NOT_FOUND',
},
})
})
})
@@ -0,0 +1,127 @@
import type {
ExtensionAssetTokenIssueInput,
ExtensionAssetTokenStore,
ExtensionAssetTokenValidateInput,
ExtensionAssetTokenValidationResult,
} from './types'
import { randomBytes } from 'node:crypto'
import { HttpError } from '../../errors'
interface ExtensionAssetTokenRecord {
extensionId: string
version: string
sessionId: string
pathPrefix: string
exp: number
}
function normalizePathPrefix(pathPrefix: string) {
const normalized = pathPrefix.trim().replaceAll('\\', '/')
if (!normalized) {
return ''
}
return normalized.endsWith('/') ? normalized : `${normalized}/`
}
function createOpaqueToken() {
return randomBytes(18).toString('base64url')
}
/**
* Creates an in-memory opaque token store for extension static asset access.
*
* Use when:
* - Main process needs short-lived auth tokens for plugin iframe asset loading
*
* Expects:
* - Tokens are opaque and stored server-side only
* - Callers revoke by plugin id or revoke all on app shutdown
*
* Returns:
* - Issue/validate/revoke operations for extension asset tokens
*/
export function createExtensionAssetTokenStore(options: { now?: () => number } = {}): ExtensionAssetTokenStore {
const now = options.now ?? (() => Date.now())
const records = new Map<string, ExtensionAssetTokenRecord>()
const dropIfExpired = (token: string, record: ExtensionAssetTokenRecord) => {
if (record.exp > now()) {
return false
}
records.delete(token)
return true
}
const issue = (input: ExtensionAssetTokenIssueInput) => {
const token = createOpaqueToken()
records.set(token, {
extensionId: input.extensionId,
version: input.version,
sessionId: input.sessionId,
pathPrefix: normalizePathPrefix(input.pathPrefix),
exp: now() + input.ttlMs,
})
return token
}
const unauthorized = (code: string, reason: string) => {
return {
ok: false as const,
error: new HttpError({
status: 401,
code,
message: 'Unauthorized',
reason,
}),
}
}
const validate = (token: string, input: ExtensionAssetTokenValidateInput): ExtensionAssetTokenValidationResult => {
const record = records.get(token)
if (!record) {
return unauthorized('EXTENSION_ASSET_TOKEN_NOT_FOUND', 'token was not found in token store')
}
if (dropIfExpired(token, record)) {
return unauthorized('EXTENSION_ASSET_TOKEN_EXPIRED', 'token has expired')
}
if (record.extensionId !== input.extensionId) {
return unauthorized('EXTENSION_ASSET_EXTENSION_MISMATCH', 'token extensionId does not match request extensionId')
}
if (typeof input.version === 'string' && record.version !== input.version) {
return unauthorized('EXTENSION_ASSET_VERSION_MISMATCH', 'token version does not match request version')
}
const normalizedAssetPath = input.assetPath.trim().replaceAll('\\', '/')
if (!normalizedAssetPath) {
return unauthorized('EXTENSION_ASSET_PATH_EMPTY', 'asset path is empty')
}
if (record.pathPrefix && !normalizedAssetPath.startsWith(record.pathPrefix)) {
return unauthorized('EXTENSION_ASSET_PATH_PREFIX_MISMATCH', 'asset path is outside allowed prefix')
}
return { ok: true }
}
return {
issue,
validate,
revokeByExtensionId(extensionId) {
for (const [token, record] of records.entries()) {
if (record.extensionId === extensionId) {
records.delete(token)
}
}
},
revokeAll() {
records.clear()
},
}
}
@@ -0,0 +1,36 @@
import type { HttpError } from '../../errors'
export interface ExtensionAssetTokenIssueInput {
extensionId: string
version: string
sessionId: string
pathPrefix: string
ttlMs: number
}
export interface ExtensionAssetTokenValidateInput {
extensionId: string
version?: string
assetPath: string
}
export type ExtensionAssetTokenValidationResult
= | { ok: true }
| { ok: false, error: HttpError }
export interface ExtensionAssetTokenStore {
issue: (input: ExtensionAssetTokenIssueInput) => string
validate: (token: string, input: ExtensionAssetTokenValidateInput) => ExtensionAssetTokenValidationResult
revokeByExtensionId: (extensionId: string) => void
revokeAll: () => void
}
export type ExtensionStaticAssetResolveResult
= | { ok: true, filePath: string, size: number, mtime: number }
| { ok: false, error: HttpError }
export interface ExtensionStaticAssetRouteOptions {
authorize: (params: { token: string, extensionId: string, assetPath: string }) => Promise<ExtensionAssetTokenValidationResult>
resolveAsset: (params: { extensionId: string, assetPath: string }) => Promise<ExtensionStaticAssetResolveResult>
getType?: (ext: string) => string | undefined
}
@@ -0,0 +1,20 @@
import { describe, expect, it, vi } from 'vitest'
import { setupBuiltInServer } from './index'
describe('setupBuiltInServer', () => {
it('starts registered adapters', async () => {
const auth = { key: 'auth', start: vi.fn(async () => {}), stop: vi.fn(async () => {}) }
const assets = { key: 'assets', start: vi.fn(async () => {}), stop: vi.fn(async () => {}) }
const service = setupBuiltInServer({
authServer: auth,
extensionStaticAssetServer: assets,
})
await service.start()
expect(auth.start).toHaveBeenCalledOnce()
expect(assets.start).toHaveBeenCalledOnce()
})
})
@@ -0,0 +1,38 @@
import type { ServerManager } from './server-manager/types'
import { createHttpServerManager } from './server-manager'
export interface BuiltInServer {
start: () => Promise<void>
stop: () => Promise<void>
}
/**
* Composes AIRI local HTTP servers behind one lifecycle service.
*
* Use when:
* - Main process needs one start/stop entrypoint for local HTTP services
*
* Expects:
* - Each server follows the `ServerManager` lifecycle contract
*
* Returns:
* - A lifecycle service with ordered startup/shutdown behavior
*/
export function setupBuiltInServer(params: {
authServer?: ServerManager
extensionStaticAssetServer?: ServerManager
servers?: ServerManager[]
}): BuiltInServer {
const servers = [
...(params.authServer ? [params.authServer] : []),
...(params.extensionStaticAssetServer ? [params.extensionStaticAssetServer] : []),
...(params.servers ?? []),
]
const manager = createHttpServerManager(servers)
return {
start: manager.start,
stop: manager.stop,
}
}
@@ -0,0 +1,61 @@
import { describe, expect, it, vi } from 'vitest'
import { createHttpServerManager } from './index'
describe('createHttpServerManager', () => {
it('starts and stops registered servers in order', async () => {
const startA = vi.fn(async () => {})
const stopA = vi.fn(async () => {})
const startB = vi.fn(async () => {})
const stopB = vi.fn(async () => {})
const manager = createHttpServerManager([
{ key: 'a', start: startA, stop: stopA },
{ key: 'b', start: startB, stop: stopB },
])
await manager.start()
await manager.stop()
expect(startA).toHaveBeenCalledOnce()
expect(startB).toHaveBeenCalledOnce()
expect(stopB).toHaveBeenCalledOnce()
expect(stopA).toHaveBeenCalledOnce()
})
it('serializes concurrent start and stop calls', async () => {
let releaseStartA: (() => void) | undefined
const startA = vi.fn(async () => {
await new Promise<void>((resolve) => {
releaseStartA = resolve
})
})
const stopA = vi.fn(async () => {})
const startB = vi.fn(async () => {})
const stopB = vi.fn(async () => {})
const manager = createHttpServerManager([
{ key: 'a', start: startA, stop: stopA },
{ key: 'b', start: startB, stop: stopB },
])
const firstStart = manager.start()
const secondStart = manager.start()
await Promise.resolve()
expect(startA).toHaveBeenCalledOnce()
expect(startB).toHaveBeenCalledTimes(0)
releaseStartA?.()
await Promise.all([firstStart, secondStart])
expect(startA).toHaveBeenCalledOnce()
expect(startB).toHaveBeenCalledOnce()
await Promise.all([manager.stop(), manager.stop()])
expect(stopB).toHaveBeenCalledOnce()
expect(stopA).toHaveBeenCalledOnce()
})
})
@@ -0,0 +1,51 @@
import type { ServerManager } from './types'
import { Mutex } from 'async-mutex'
/**
* Creates an ordered lifecycle manager for AIRI local HTTP servers.
*
* Use when:
* - Multiple standalone HTTP servers must boot together
* - Shutdown order must run in reverse startup order
*
* Expects:
* - The `servers` list order defines startup order
* - `start`/`stop` may be called multiple times safely
*
* Returns:
* - An idempotent manager with `start` and `stop`
*/
export function createHttpServerManager(servers: ServerManager[]) {
let started = false
const lifecycleMutex = new Mutex()
return {
async start() {
await lifecycleMutex.runExclusive(async () => {
if (started) {
return
}
for (const server of servers) {
await server.start()
}
started = true
})
},
async stop() {
await lifecycleMutex.runExclusive(async () => {
if (!started) {
return
}
for (const server of [...servers].reverse()) {
await server.stop()
}
started = false
})
},
}
}
@@ -0,0 +1,19 @@
/**
* Shared lifecycle contract for AIRI local HTTP sub-servers.
*
* Use when:
* - Registering standalone local HTTP services under `services/airi/http-server`
* - Composing startup/shutdown order in the server manager
*
* Expects:
* - `start` to be idempotent
* - `stop` to be safe to call after partial startup
*
* Returns:
* - Promise lifecycle completion for each server action
*/
export interface ServerManager {
key: string
start: () => Promise<void>
stop: () => Promise<void>
}
@@ -0,0 +1,73 @@
import { Mutex } from 'async-mutex'
import { getRandomPort } from 'get-port-please'
import { serve } from 'h3'
export interface BuiltInServerAddress {
host: string
port: number
baseUrl: string
}
/**
* Creates a reusable local HTTP server lifecycle around an h3 app.
*
* Use when:
* - A local HTTP module needs host/port assignment and start/stop lifecycle
* - Callers may trigger concurrent start/stop operations
*
* Expects:
* - `app` is a valid h3 app/handler accepted by `serve`
* - Caller manages route registration before first `start`
*
* Returns:
* - Idempotent lifecycle with serialized start/stop and runtime address getter
*/
export function createH3Server(options: {
app: Parameters<typeof serve>[0]
host?: string
port?: number
silent?: boolean
}) {
const host = options.host ?? '127.0.0.1'
const silent = options.silent ?? true
const lifecycleMutex = new Mutex()
let server: ReturnType<typeof serve> | undefined
let address: BuiltInServerAddress | undefined
return {
async start(): Promise<BuiltInServerAddress> {
return await lifecycleMutex.runExclusive(async () => {
if (address) {
return address
}
const port = options.port ?? await getRandomPort(host)
server = serve(options.app, { hostname: host, port, silent })
address = {
host,
port,
baseUrl: `http://${host}:${port}`,
}
return address
})
},
async stop(): Promise<void> {
await lifecycleMutex.runExclusive(async () => {
address = undefined
if (!server) {
return
}
const activeServer = server
server = undefined
await activeServer.close().catch(() => {})
})
},
getAddress() {
return address
},
}
}
@@ -0,0 +1,57 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
buildMountedPluginAssetPath,
normalizePluginAssetPath,
parsePluginAssetRequestPath,
resolvePluginAssetFilePath,
} from './asset-mount'
describe('asset-mount', () => {
const tempRoots: string[] = []
afterEach(async () => {
for (const root of tempRoots) {
await rm(root, { recursive: true, force: true })
}
tempRoots.length = 0
})
it('normalizes valid asset paths and rejects traversal-like segments', () => {
expect(normalizePluginAssetPath('dist/ui/index.html')).toBe('dist/ui/index.html')
expect(normalizePluginAssetPath('./dist/ui/index.html')).toBeUndefined()
expect(normalizePluginAssetPath('../secret.txt')).toBeUndefined()
expect(normalizePluginAssetPath('dist/../ui/index.html')).toBeUndefined()
})
it('parses mounted plugin request path and rejects malformed routes', () => {
expect(parsePluginAssetRequestPath('/_airi/extensions/airi-plugin-game-chess/ui/dist/ui/index.html')).toEqual({
extensionId: 'airi-plugin-game-chess',
assetPath: 'dist/ui/index.html',
})
expect(parsePluginAssetRequestPath('/_airi/extensions/airi-plugin-game-chess/ui/../../etc/passwd')).toBeUndefined()
expect(parsePluginAssetRequestPath('/_airi/extensions//ui/index.html')).toBeUndefined()
})
it('builds mounted asset path with encoded segments', () => {
expect(buildMountedPluginAssetPath({
extensionId: 'airi-plugin-game-chess',
assetPath: 'dist/ui/index.html',
})).toBe('/_airi/extensions/airi-plugin-game-chess/ui/dist/ui/index.html')
})
it('resolves only files inside plugin root', async () => {
const root = await mkdtemp(join(tmpdir(), 'airi-plugin-assets-'))
tempRoots.push(root)
await mkdir(join(root, 'dist', 'ui'), { recursive: true })
await writeFile(join(root, 'dist', 'ui', 'index.html'), '<html></html>')
await expect(resolvePluginAssetFilePath(root, 'dist/ui/index.html')).resolves.toContain('dist/ui/index.html')
await expect(resolvePluginAssetFilePath(root, '../outside.txt')).resolves.toBeUndefined()
})
})
@@ -0,0 +1,100 @@
import { realpath } from 'node:fs/promises'
import { resolve, sep } from 'node:path'
export interface ParsedPluginAssetRequest {
extensionId: string
assetPath: string
}
const pathPrefix = '/_airi/extensions/'
const segmentPattern = /^[\w.+-]+$/
export function normalizePluginAssetPath(value: string): string | undefined {
const normalized = value.trim().replaceAll('\\', '/')
if (!normalized) {
return undefined
}
const segments = normalized
.split('/')
.map(segment => decodeURIComponent(segment).trim())
.filter(Boolean)
if (segments.length === 0) {
return undefined
}
if (segments.some(segment => segment === '.' || segment === '..')) {
return undefined
}
return segments.join('/')
}
export function parsePluginAssetRequestPath(pathname: string): ParsedPluginAssetRequest | undefined {
if (!pathname.startsWith(pathPrefix)) {
return undefined
}
const rawRemainder = pathname.slice(pathPrefix.length)
if (!rawRemainder) {
return undefined
}
const segments = rawRemainder.split('/').filter(Boolean)
if (segments.length < 3) {
return undefined
}
const extensionId = decodeURIComponent(segments[0] ?? '')
const mountSegment = decodeURIComponent(segments[1] ?? '')
const rawAssetPath = segments.slice(2).join('/')
if (!segmentPattern.test(extensionId) || mountSegment !== 'ui') {
return undefined
}
const assetPath = normalizePluginAssetPath(rawAssetPath)
if (!assetPath) {
return undefined
}
return {
extensionId,
assetPath,
}
}
export async function resolvePluginAssetFilePath(rootDir: string, assetPath: string) {
const normalizedAssetPath = normalizePluginAssetPath(assetPath)
if (!normalizedAssetPath) {
return undefined
}
const resolvedRoot = await realpath(rootDir)
const resolvedCandidate = resolve(resolvedRoot, normalizedAssetPath)
const normalizedRootPrefix = `${resolvedRoot}${sep}`
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRootPrefix)) {
return undefined
}
return resolvedCandidate
}
export function buildMountedPluginAssetPath(input: {
extensionId: string
assetPath: string
}) {
const normalizedAssetPath = normalizePluginAssetPath(input.assetPath)
if (!normalizedAssetPath) {
return undefined
}
const encodedExtensionId = encodeURIComponent(input.extensionId)
const encodedAssetPath = normalizedAssetPath
.split('/')
.map(segment => encodeURIComponent(segment))
.join('/')
return `${pathPrefix}${encodedExtensionId}/ui/${encodedAssetPath}`
}
@@ -1,146 +0,0 @@
import http from 'node:http'
import { useLogg } from '@guiiai/logg'
const log = useLogg('loopback-server').useGlobalConfig()
export interface LoopbackCallbackResult {
code: string
state: string
}
/**
* Fixed ports for the loopback OIDC callback server.
* The server relay page (`/api/auth/oidc/electron-callback`) forwards the
* authorization code to `http://127.0.0.1:{port}/callback` via JS fetch().
* The port is encoded in the `state` parameter, not in the redirect_uri.
*
* See RFC 8252 S7.3 for the loopback redirect pattern.
*/
const LOOPBACK_PORTS = [19721, 19722, 19723, 19724, 19725]
/**
* Start a temporary HTTP server on 127.0.0.1 to receive an OIDC authorization
* callback. Tries ports from LOOPBACK_PORTS in order. The server handles
* exactly one request and then shuts down.
*
* This follows RFC 8252 S7.3 (Loopback Interface Redirection) and is the
* approach used by VS Code, GitHub Desktop, and Slack Desktop.
*/
export function startLoopbackServer(): Promise<{
port: number
result: Promise<LoopbackCallbackResult>
close: () => void
}> {
return new Promise((resolveStart, rejectStart) => {
let settled = false
let resultResolve: (value: LoopbackCallbackResult) => void
let resultReject: (reason: Error) => void
const resultPromise = new Promise<LoopbackCallbackResult>((resolve, reject) => {
resultResolve = resolve
resultReject = reject
})
const server = http.createServer((req, res) => {
if (settled)
return
const url = new URL(req.url ?? '/', `http://127.0.0.1`)
// CORS: the relay page on the server origin sends a cross-origin fetch()
// to the loopback. Allow all origins since this is a one-shot local server.
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
if (req.method === 'OPTIONS') {
res.writeHead(204)
res.end()
return
}
if (url.pathname !== '/callback') {
res.writeHead(404)
res.end('Not found')
return
}
const error = url.searchParams.get('error')
if (error) {
const description = url.searchParams.get('error_description') ?? error
settled = true
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end('<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>')
resultReject!(new Error(description))
server.close()
return
}
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
if (!code || !state) {
res.writeHead(400, { 'Content-Type': 'text/html' })
res.end('<html><body><h2>Missing parameters</h2></body></html>')
return
}
settled = true
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the app.</p></body></html>')
resultResolve!({ code, state })
server.close()
})
// Safety timeout: close after 5 minutes
const timeout = setTimeout(() => {
if (!settled) {
settled = true
resultReject!(new Error('Sign-in timed out — no callback received'))
server.close()
}
}, 5 * 60 * 1000)
server.on('close', () => clearTimeout(timeout))
// Try each port in order
let portIndex = 0
function tryListen(): void {
if (portIndex >= LOOPBACK_PORTS.length) {
rejectStart(new Error(`All loopback ports (${LOOPBACK_PORTS.join(', ')}) are in use`))
return
}
const port = LOOPBACK_PORTS[portIndex]
server.once('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
log.withFields({ port }).log('Port in use, trying next')
portIndex++
tryListen()
}
else {
rejectStart(err)
}
})
server.listen(port, '127.0.0.1', () => {
log.withFields({ port }).log('Loopback callback server started')
resolveStart({
port,
result: resultPromise,
close: () => {
if (settled)
return
settled = true
resultReject!(new Error('OIDC sign-in attempt cancelled'))
server.close()
},
})
})
}
tryListen()
})
}
+293 -236
View File
@@ -6,6 +6,9 @@ settings:
catalogs:
default:
'@ax-llm/ax':
specifier: ^19.0.43
version: 19.0.45
'@better-auth/oauth-provider':
specifier: 1.5.6
version: 1.5.6
@@ -123,6 +126,9 @@ catalogs:
'@types/unist':
specifier: ^3.0.3
version: 3.0.3
'@types/whatwg-mimetype':
specifier: ^5.0.0
version: 5.0.0
'@types/ws':
specifier: ^8.18.1
version: 8.18.1
@@ -183,6 +189,9 @@ catalogs:
capacitor-native-settings:
specifier: ^8.1.0
version: 8.1.0
chess.js:
specifier: ^1.4.0
version: 1.4.0
crossws:
specifier: ^0.4.5
version: 0.4.5
@@ -267,12 +276,18 @@ catalogs:
posthog-js:
specifier: 1.306.1
version: 1.306.1
reka-ui:
specifier: ^2.9.2
version: 2.9.6
splitpanes:
specifier: ^4.0.4
version: 4.0.4
std-env:
specifier: ^4.1.0
version: 4.1.0
stockfish:
specifier: ^18.0.7
version: 18.0.7
superjson:
specifier: ^2.2.6
version: 2.2.6
@@ -297,6 +312,9 @@ catalogs:
valibot:
specifier: 1.2.0
version: 1.2.0
vieval:
specifier: ^0.0.1
version: 0.0.1
vite:
specifier: ^8.0.8
version: 8.0.8
@@ -318,6 +336,9 @@ catalogs:
web-haptics:
specifier: ^0.0.6
version: 0.0.6
whatwg-mimetype:
specifier: ^5.0.0
version: 5.0.0
ws:
specifier: ^8.20.0
version: 8.20.0
@@ -1243,6 +1264,9 @@ importers:
async-mutex:
specifier: 'catalog:'
version: 0.5.0
chess.js:
specifier: 'catalog:'
version: 1.4.0
colorjs.io:
specifier: ^0.6.1
version: 0.6.1
@@ -1363,6 +1387,9 @@ importers:
web-haptics:
specifier: 'catalog:'
version: 0.0.6(react@19.2.3)(vue@3.5.32(typescript@5.9.3))
whatwg-mimetype:
specifier: 'catalog:'
version: 5.0.0
xsschema:
specifier: 'catalog:'
version: 0.5.0-beta.2(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.3.1(typescript@5.9.3)))(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6)
@@ -1375,7 +1402,7 @@ importers:
version: 3.0.2(electron@41.2.1)
'@electron-toolkit/tsconfig':
specifier: ^2.0.0
version: 2.0.0(@types/node@24.12.2)
version: 2.0.0(@types/node@25.6.0)
'@electron-toolkit/utils':
specifier: ^4.0.0
version: 4.0.0(electron@41.2.1)
@@ -1414,7 +1441,7 @@ importers:
version: 3.1.0
'@intlify/unplugin-vue-i18n':
specifier: ^11.0.7
version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
'@modelcontextprotocol/sdk':
specifier: 'catalog:'
version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
@@ -1450,10 +1477,10 @@ importers:
version: link:../../packages/ui-transitions
'@proj-airi/unplugin-fetch':
specifier: 'catalog:'
version: 0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
version: 0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@proj-airi/unplugin-live2d-sdk':
specifier: ^0.1.7
version: 0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
version: 0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@types/audioworklet':
specifier: 'catalog:'
version: 0.0.97
@@ -1472,12 +1499,15 @@ importers:
'@types/three':
specifier: ^0.184.0
version: 0.184.0
'@types/whatwg-mimetype':
specifier: 'catalog:'
version: 5.0.0
'@types/yauzl':
specifier: ^2.10.3
version: 2.10.3
'@vitejs/plugin-vue':
specifier: ^6.0.6
version: 6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
'@vue-macros/volar':
specifier: ^3.1.2
version: 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
@@ -1507,7 +1537,7 @@ importers:
version: 6.8.3
electron-vite:
specifier: ^5.0.0
version: 5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
version: 5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
get-port-please:
specifier: 'catalog:'
version: 3.2.0
@@ -1528,31 +1558,31 @@ importers:
version: 2.2.6
unocss-preset-scrollbar:
specifier: ^4.0.0
version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))
version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))
unplugin-info:
specifier: ^1.3.2
version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
unplugin-yaml:
specifier: ^4.1.0
version: 4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
version: 4.1.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vite:
specifier: 'catalog:'
version: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-bundle-visualizer:
specifier: ^1.2.1
version: 1.2.1(rolldown@1.0.0-rc.16)(rollup@4.60.1)
vite-plugin-mkcert:
specifier: 'catalog:'
version: 2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
version: 2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vite-plugin-vue-devtools:
specifier: ^8.1.1
version: 8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
version: 8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
vite-plugin-vue-layouts:
specifier: ^0.11.0
version: 0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
version: 0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
vue-macros:
specifier: ^3.1.2
version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
vue-tsc:
specifier: ^3.2.6
version: 3.2.6(typescript@5.9.3)
@@ -3768,6 +3798,70 @@ importers:
specifier: ^3.2.6
version: 3.2.6(typescript@5.9.3)
plugins/airi-plugin-game-chess:
dependencies:
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.17
'@proj-airi/ui':
specifier: workspace:^
version: link:../../packages/ui
animejs:
specifier: ^4.3.6
version: 4.3.6
chess.js:
specifier: 'catalog:'
version: 1.4.0
reka-ui:
specifier: 'catalog:'
version: 2.9.6(vue@3.5.32(typescript@5.9.3))
stockfish:
specifier: 'catalog:'
version: 18.0.7
vue:
specifier: 'catalog:'
version: 3.5.32(typescript@5.9.3)
devDependencies:
'@ax-llm/ax':
specifier: 'catalog:'
version: 19.0.45(zod@4.3.6)
'@proj-airi/stage-ui':
specifier: workspace:^
version: link:../../packages/stage-ui
'@proj-airi/unocss-preset-chromatic':
specifier: ^1.0.2
version: 1.1.1
'@unocss/reset':
specifier: ^66.6.7
version: 66.6.8
'@vitejs/plugin-vue':
specifier: ^6.0.5
version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))
'@xsai-ext/providers':
specifier: 'catalog:'
version: 0.5.0-beta.2
'@xsai/generate-text':
specifier: 'catalog:'
version: 0.5.0-beta.2(patch_hash=306bfb723913596b334140f0d6fa48063f336e3b44024efc1d72bf60d54b15e6)
'@xsai/shared-chat':
specifier: 'catalog:'
version: 0.5.0-beta.2(patch_hash=26f2819b987245ec85f216b821ddf73aeb28fd7e611238a2d37250e42f838a01)
'@xsai/stream-text':
specifier: 'catalog:'
version: 0.5.0-beta.2(patch_hash=90dfe10d02f5946658508ec019937eab600745c93446ce7b2fdb1a0ed70e3e49)
pinia:
specifier: 'catalog:'
version: 3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3))
unocss:
specifier: ^66.6.7
version: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vieval:
specifier: 'catalog:'
version: 0.0.1(@types/node@25.6.0)(chokidar@5.0.0)(dotenv@17.4.2)(esbuild@0.27.2)(giget@2.0.0)(jiti@2.6.1)(less@4.6.4)(magicast@0.5.2)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vue-tsc:
specifier: ^3.1.1
version: 3.2.6(typescript@5.9.3)
plugins/airi-plugin-homeassistant:
dependencies:
'@guiiai/logg':
@@ -4351,6 +4445,15 @@ packages:
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
'@ax-llm/ax@19.0.45':
resolution: {integrity: sha512-zC/OqUutTV0omOuAxBgeCihmxP3e1eUcbA78FCaMF3pPXZ3/FgQDZf0R04LH/lVIxm5BH4/qDdNNYcIVGwii8Q==}
hasBin: true
peerDependencies:
zod: ^3.24.0 || ^4.0.0
peerDependenciesMeta:
zod:
optional: true
'@azure/msal-common@14.16.1':
resolution: {integrity: sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w==}
engines: {node: '>=0.8.0'}
@@ -9526,6 +9629,9 @@ packages:
'@types/webxr@0.5.24':
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
'@types/whatwg-mimetype@5.0.0':
resolution: {integrity: sha512-YYiBDCoqBgDIF2ByYn4qDb4RaXZ46cOQ6j2We1Ni3bikFNI7YFeL8jxSiYowWsriZrb1mw09CLZ+b+ZkIhsLVw==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -11029,6 +11135,26 @@ packages:
magicast:
optional: true
c12@4.0.0-beta.4:
resolution: {integrity: sha512-gcWQAloC/SwGx4U7l3iQdalUQQLLXwYS1d3SqIwFj4UUrTXh8L9yGkBcA00B0gxELMwbxtsrt6VrAxtSgqZZoA==}
peerDependencies:
chokidar: ^5
dotenv: '*'
giget: '*'
jiti: '*'
magicast: '*'
peerDependenciesMeta:
chokidar:
optional: true
dotenv:
optional: true
giget:
optional: true
jiti:
optional: true
magicast:
optional: true
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -11112,6 +11238,9 @@ packages:
character-entities@2.0.2:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
chess.js@1.4.0:
resolution: {integrity: sha512-BBJgrrtKQOzFLonR0l+k64A98NLemPwNsCskwb+29bRwobUa4iTm51E1kwGPbWXAcfdDa18nad6vpPPKPWarqw==}
chevrotain@10.5.0:
resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==}
@@ -11331,6 +11460,9 @@ packages:
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
confbox@0.2.4:
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
@@ -11609,6 +11741,9 @@ packages:
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
dayjs@1.11.20:
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -12657,9 +12792,15 @@ packages:
fast-string-truncated-width@1.2.1:
resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==}
fast-string-truncated-width@3.0.3:
resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
fast-string-width@1.1.0:
resolution: {integrity: sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==}
fast-string-width@3.0.2:
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
@@ -15555,6 +15696,9 @@ packages:
rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
rc9@3.0.1:
resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@@ -16216,6 +16360,10 @@ packages:
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
stockfish@18.0.7:
resolution: {integrity: sha512-tJ+bfMAHs4fV7QYiLcHUicx1RzKOQwj8twx/z0iUTauMG+SE3l1rEtRSg+WER81ke3bc5tqKBWEWntUbydkgZg==}
hasBin: true
store2@2.14.4:
resolution: {integrity: sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==}
@@ -17204,6 +17352,10 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vieval@0.0.1:
resolution: {integrity: sha512-JCvKwQP0rE/SvvZR/vPJFBqVE286+ZxGCQpohQBtmdMuwoiE38JbkDDPlT09qx2FCckCJYdEqP05DmCxJ8X0TA==}
hasBin: true
vite-bundle-visualizer@1.2.1:
resolution: {integrity: sha512-cwz/Pg6+95YbgIDp+RPwEToc4TKxfsFWSG/tsl2DSZd9YZicUag1tQXjJ5xcL7ydvEoaC2FOZeaXOU60t9BRXw==}
engines: {node: ^18.19.0 || >=20.6.0}
@@ -17633,6 +17785,10 @@ packages:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-mimetype@5.0.0:
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
engines: {node: '>=20'}
whatwg-url@15.1.0:
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
engines: {node: '>=20'}
@@ -18125,6 +18281,13 @@ snapshots:
'@asamuzakjp/nwsapi@2.3.9': {}
'@ax-llm/ax@19.0.45(zod@4.3.6)':
dependencies:
'@opentelemetry/api': 1.9.1
dayjs: 1.11.20
optionalDependencies:
zod: 4.3.6
'@azure/msal-common@14.16.1': {}
'@azure/msal-node@2.16.3':
@@ -19381,9 +19544,9 @@ snapshots:
dependencies:
electron: 41.2.1
'@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)':
'@electron-toolkit/tsconfig@2.0.0(@types/node@25.6.0)':
dependencies:
'@types/node': 24.12.2
'@types/node': 25.6.0
'@electron-toolkit/utils@4.0.0(electron@41.2.1)':
dependencies:
@@ -20269,31 +20432,6 @@ snapshots:
- supports-color
- typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1))
'@intlify/bundle-utils': 11.0.7(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))
'@intlify/shared': 11.3.2
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.3.2)(@vue/compiler-dom@3.5.32)(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
'@typescript-eslint/scope-manager': 8.58.1
'@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.3)
debug: 4.4.3(supports-color@10.2.2)
fast-glob: 3.3.3
pathe: 2.0.3
picocolors: 1.1.1
unplugin: 2.3.11
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vue: 3.5.32(typescript@5.9.3)
optionalDependencies:
vue-i18n: 11.3.2(vue@3.5.32(typescript@5.9.3))
transitivePeerDependencies:
- '@vue/compiler-dom'
- eslint
- rollup
- supports-color
- typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1))
@@ -22264,35 +22402,11 @@ snapshots:
transitivePeerDependencies:
- magicast
'@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
ofetch: 1.5.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
ofetch: 1.5.1
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)':
dependencies:
ofetch: 1.5.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
yauzl: 3.3.0
transitivePeerDependencies:
- '@types/node'
- '@vitejs/devtools'
- esbuild
- jiti
- less
- sass
- sass-embedded
- stylus
- sugarss
- terser
- tsx
- yaml
'@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)':
dependencies:
ofetch: 1.5.1
@@ -23240,7 +23354,6 @@ snapshots:
'@types/node@25.6.0':
dependencies:
undici-types: 7.19.2
optional: true
'@types/nprogress@0.2.3': {}
@@ -23324,6 +23437,8 @@ snapshots:
'@types/webxr@0.5.24': {}
'@types/whatwg-mimetype@5.0.0': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.12.2
@@ -23767,6 +23882,19 @@ snapshots:
unplugin-utils: 0.3.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@unocss/vite@66.6.8(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
'@jridgewell/remapping': 2.3.5
'@unocss/config': 66.6.8
'@unocss/core': 66.6.8
'@unocss/inspector': 66.6.8
chokidar: 5.0.0
magic-string: 0.30.21
pathe: 2.0.3
tinyglobby: 0.2.16
unplugin-utils: 0.3.1
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
'@unrteljs/eval@0.2.1':
dependencies:
builtin-modules: 5.0.0
@@ -23891,12 +24019,6 @@ snapshots:
vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vue: 3.5.32(typescript@5.9.3)
'@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.13
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vue: 3.5.32(typescript@5.9.3)
'@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.13
@@ -24199,15 +24321,6 @@ snapshots:
transitivePeerDependencies:
- vue
'@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
sirv: 3.0.2
vue: 3.5.32(typescript@5.9.3)
optionalDependencies:
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- typescript
'@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
sirv: 3.0.2
@@ -25379,6 +25492,21 @@ snapshots:
optionalDependencies:
magicast: 0.5.2
c12@4.0.0-beta.4(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.6.1)(magicast@0.5.2):
dependencies:
confbox: 0.2.4
defu: 6.1.7
exsolve: 1.0.8
pathe: 2.0.3
pkg-types: 2.3.0
rc9: 3.0.1
optionalDependencies:
chokidar: 5.0.0
dotenv: 17.4.2
giget: 2.0.0
jiti: 2.6.1
magicast: 0.5.2
cac@6.7.14: {}
cac@7.0.0: {}
@@ -25459,6 +25587,8 @@ snapshots:
character-entities@2.0.2: {}
chess.js@1.4.0: {}
chevrotain@10.5.0:
dependencies:
'@chevrotain/cst-dts-gen': 10.5.0
@@ -25672,6 +25802,8 @@ snapshots:
confbox@0.2.2: {}
confbox@0.2.4: {}
config-chain@1.1.13:
dependencies:
ini: 1.3.8
@@ -25968,6 +26100,8 @@ snapshots:
date-fns@4.1.0: {}
dayjs@1.11.20: {}
de-indent@1.0.2: {}
debounce@1.2.1: {}
@@ -26305,7 +26439,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron-vite@5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
electron-vite@5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0)
@@ -26313,7 +26447,7 @@ snapshots:
esbuild: 0.25.12
magic-string: 0.30.21
picocolors: 1.1.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- supports-color
@@ -27110,10 +27244,16 @@ snapshots:
fast-string-truncated-width@1.2.1: {}
fast-string-truncated-width@3.0.3: {}
fast-string-width@1.1.0:
dependencies:
fast-string-truncated-width: 1.2.1
fast-string-width@3.0.2:
dependencies:
fast-string-truncated-width: 3.0.3
fast-uri@3.1.0: {}
fast-wrap-ansi@0.1.6:
@@ -30682,6 +30822,11 @@ snapshots:
defu: 6.1.7
destr: 2.0.5
rc9@3.0.1:
dependencies:
defu: 6.1.7
destr: 2.0.5
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@@ -31591,6 +31736,8 @@ snapshots:
std-env@4.1.0: {}
stockfish@18.0.7: {}
store2@2.14.4: {}
string-width@4.2.3:
@@ -32162,8 +32309,7 @@ snapshots:
undici-types@7.16.0: {}
undici-types@7.19.2:
optional: true
undici-types@7.19.2: {}
undici@6.24.1: {}
@@ -32273,6 +32419,11 @@ snapshots:
'@unocss/preset-mini': 66.6.8
unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
unocss-preset-scrollbar@4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))):
dependencies:
'@unocss/preset-mini': 66.6.8
unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@unocss/cli': 66.6.8
@@ -32321,6 +32472,30 @@ snapshots:
- '@emnapi/runtime'
- vite
unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@unocss/cli': 66.6.8
'@unocss/core': 66.6.8
'@unocss/preset-attributify': 66.6.8
'@unocss/preset-icons': 66.6.8
'@unocss/preset-mini': 66.6.8
'@unocss/preset-tagify': 66.6.8
'@unocss/preset-typography': 66.6.8
'@unocss/preset-uno': 66.6.8
'@unocss/preset-web-fonts': 66.6.8
'@unocss/preset-wind': 66.6.8
'@unocss/preset-wind3': 66.6.8
'@unocss/preset-wind4': 66.6.8
'@unocss/transformer-attributify-jsx': 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
'@unocss/transformer-compile-class': 66.6.8
'@unocss/transformer-directives': 66.6.8
'@unocss/transformer-variant-group': 66.6.8
'@unocss/vite': 66.6.8(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
- vite
unpack-string@0.0.2: {}
unpipe@1.0.0: {}
@@ -32333,14 +32508,6 @@ snapshots:
unplugin: 2.3.11
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
optionalDependencies:
esbuild: 0.27.2
rolldown: 1.0.0-rc.16
rollup: 4.60.1
unplugin: 2.3.11
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
optionalDependencies:
esbuild: 0.27.2
@@ -32375,19 +32542,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
ci-info: 4.4.0
git-url-parse: 16.1.0
simple-git: 3.36.0
unplugin: 2.3.11
optionalDependencies:
esbuild: 0.27.2
rollup: 4.60.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- supports-color
unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
ci-info: 4.4.0
@@ -32497,17 +32651,6 @@ snapshots:
rollup: 4.60.1
vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
unplugin-yaml@4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
unplugin: 3.0.0
yaml: 2.8.3
optionalDependencies:
esbuild: 0.27.2
rolldown: 1.0.0-rc.16
rollup: 4.60.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -32670,6 +32813,37 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vieval@0.0.1(@types/node@25.6.0)(chokidar@5.0.0)(dotenv@17.4.2)(esbuild@0.27.2)(giget@2.0.0)(jiti@2.6.1)(less@4.6.4)(magicast@0.5.2)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
'@moeru/std': 0.1.0-beta.17
'@pnpm/find-workspace-dir': 1000.1.5
'@xsai-ext/providers': 0.5.0-beta.2
'@xsai/generate-text': 0.5.0-beta.2(patch_hash=306bfb723913596b334140f0d6fa48063f336e3b44024efc1d72bf60d54b15e6)
c12: 4.0.0-beta.4(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.6.1)(magicast@0.5.2)
es-toolkit: 1.43.0
fast-string-width: 3.0.2
meow: 14.1.0
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- '@types/node'
- '@vitejs/devtools'
- chokidar
- dotenv
- esbuild
- giget
- jiti
- less
- magicast
- sass
- sass-embedded
- stylus
- sugarss
- terser
- tsx
- yaml
vite-bundle-visualizer@1.2.1(rolldown@1.0.0-rc.16)(rollup@2.80.0):
dependencies:
cac: 6.7.14
@@ -32692,22 +32866,12 @@ snapshots:
- rollup
- supports-color
vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
birpc: 2.9.0
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-hot-client: 2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
birpc: 2.9.0
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-hot-client: 2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vite-hot-client@2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-hot-client@2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
@@ -32753,21 +32917,6 @@ snapshots:
- tsx
- yaml
vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
ansis: 4.2.0
debug: 4.4.3(supports-color@10.2.2)
error-stack-parser-es: 1.0.5
ohash: 2.0.11
open: 10.2.0
perfect-debounce: 2.1.0
sirv: 3.0.2
unplugin-utils: 0.3.1
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-dev-rpc: 1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
transitivePeerDependencies:
- supports-color
vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
ansis: 4.2.0
@@ -32799,13 +32948,6 @@ snapshots:
- typescript
- ws
vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
debug: 4.4.3(supports-color@10.2.2)
supports-color: 10.2.2
undici: 8.1.0
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
debug: 4.4.3(supports-color@10.2.2)
@@ -32824,20 +32966,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)):
dependencies:
'@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3))
'@vue/devtools-kit': 8.1.1
'@vue/devtools-shared': 8.1.1
sirv: 3.0.2
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vite-plugin-inspect: 11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
vite-plugin-vue-inspector: 5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
transitivePeerDependencies:
- '@nuxt/kit'
- supports-color
- vue
vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)):
dependencies:
'@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3))
@@ -32852,21 +32980,6 @@ snapshots:
- supports-color
- vue
vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0)
'@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
'@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0)
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0)
'@vue/compiler-dom': 3.5.32
kolorist: 1.8.0
magic-string: 0.30.21
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
transitivePeerDependencies:
- supports-color
vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
dependencies:
'@babel/core': 7.29.0
@@ -32882,16 +32995,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)):
dependencies:
debug: 4.4.3(supports-color@10.2.2)
fast-glob: 3.3.3
vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
vue: 3.5.32(typescript@5.9.3)
vue-router: 5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
transitivePeerDependencies:
- supports-color
vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)):
dependencies:
debug: 4.4.3(supports-color@10.2.2)
@@ -33150,54 +33253,6 @@ snapshots:
- vue-tsc
- webpack
vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)):
dependencies:
'@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
'@vue-macros/boolean-prop': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/chain-call': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/common': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/config': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-emit': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-models': 3.1.2(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-prop': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-props': 3.1.2(@vue-macros/reactivity-transform@3.1.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-props-refs': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-render': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-slots': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/define-stylex': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/devtools': 3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@vue-macros/export-expose': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/export-props': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/export-render': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/hoist-static': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/jsx-directive': 3.1.2(typescript@5.9.3)
'@vue-macros/named-template': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/reactivity-transform': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/script-lang': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/setup-block': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/setup-component': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/setup-sfc': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/short-bind': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/short-emits': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/short-vmodel': 3.1.2(vue@3.5.32(typescript@5.9.3))
'@vue-macros/volar': 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3))
unplugin: 2.3.11
unplugin-combine: 2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
unplugin-vue-define-options: 3.1.2(vue@3.5.32(typescript@5.9.3))
vue: 3.5.32(typescript@5.9.3)
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
- '@rspack/core'
- '@vueuse/core'
- esbuild
- rolldown
- rollup
- typescript
- vite
- vue-tsc
- webpack
vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)):
dependencies:
'@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3))
@@ -33377,6 +33432,8 @@ snapshots:
whatwg-mimetype@4.0.0: {}
whatwg-mimetype@5.0.0: {}
whatwg-url@15.1.0:
dependencies:
tr46: 6.0.0
+8
View File
@@ -28,6 +28,7 @@ patchedDependencies:
mineflayer@4.33.0: patches/mineflayer@4.33.0.patch
pixi-live2d-display: patches/pixi-live2d-display.patch
catalog:
'@ax-llm/ax': ^19.0.43
'@better-auth/oauth-provider': 1.5.6
'@capacitor/android': ^8.3.1
'@capacitor/barcode-scanner': ^3.0.2
@@ -67,6 +68,7 @@ catalog:
'@types/hast': ^3.0.4
'@types/splitpanes': ^2.2.6
'@types/unist': ^3.0.3
'@types/whatwg-mimetype': ^5.0.0
'@types/ws': ^8.18.1
'@vueuse/core': 14.1.0
'@xsai-ext/providers': 0.5.0-beta.2
@@ -87,6 +89,7 @@ catalog:
builder-util-runtime: ^9.5.1
cac: ^7.0.0
capacitor-native-settings: ^8.1.0
chess.js: ^1.4.0
crossws: ^0.4.5
d3: 7.9.0
date-fns: ^4.1.0
@@ -115,8 +118,10 @@ catalog:
oxc-minify: ^0.126.0
pinia: ^3.0.4
posthog-js: 1.306.1
reka-ui: ^2.9.2
splitpanes: ^4.0.4
std-env: ^4.1.0
stockfish: ^18.0.7
superjson: ^2.2.6
tsdown: ^0.21.9
tsx: ^4.21.0
@@ -125,6 +130,7 @@ catalog:
unstorage: ^1.17.5
uqr: ^0.1.3
valibot: 1.2.0
vieval: ^0.0.1
vite: ^8.0.8
vite-plugin-inspect: 12.0.0-beta.1
vite-plugin-mkcert: ^2.0.0
@@ -132,6 +138,7 @@ catalog:
vue-router: ^5.0.4
vue-sonner: 2.0.9
web-haptics: ^0.0.6
whatwg-mimetype: ^5.0.0
ws: ^8.20.0
xsschema: 0.5.0-beta.2
yaml: ^2.8.3
@@ -173,6 +180,7 @@ onlyBuiltDependencies:
- protobufjs
- sharp
- spawn-sync
- stockfish
- utf-8-validate
- vue-demi
packageExtensions: