From 13b0f45d3efd9e342025526bf7daade88fb7d420 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Sat, 21 Feb 2026 06:28:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=A1=B9=E7=89=88=E6=9C=AC=E5=B9=B6=E4=BC=98=E5=8C=96=E8=BF=9C?= =?UTF-8?q?=E7=A8=8B=E5=BA=94=E7=94=A8=E8=BF=9E=E6=8E=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant/package.json | 4 +- .../src/module/assistant/config/index.ts | 1 - .../local-app-manager/assistant-app.ts | 92 +++++++++++++++++-- assistant/src/module/assistant/query/index.ts | 2 +- assistant/src/module/http-token.ts | 17 ++-- assistant/src/module/light-code/index.ts | 15 +-- assistant/src/routes/remote/index.ts | 10 +- assistant/src/routes/user/index.ts | 18 +--- assistant/src/routes/user/utils/cookie.ts | 14 +++ assistant/src/server.ts | 14 +-- .../src/services/proxy/proxy-page-index.ts | 4 +- assistant/src/test/remote-app.ts | 3 +- pnpm-lock.yaml | 66 ++++++++----- 13 files changed, 184 insertions(+), 76 deletions(-) create mode 100644 assistant/src/routes/user/utils/cookie.ts diff --git a/assistant/package.json b/assistant/package.json index 4d71323..abee70e 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@inquirer/prompts": "^8.2.1", "@kevisual/ai": "^0.0.24", - "@kevisual/api": "^0.0.55", + "@kevisual/api": "^0.0.57", "@kevisual/load": "^0.0.6", "@kevisual/local-app-manager": "^0.1.32", "@kevisual/logger": "^0.0.4", @@ -76,7 +76,7 @@ "access": "public" }, "dependencies": { - "@aws-sdk/client-s3": "^3.994.0", + "@aws-sdk/client-s3": "^3.995.0", "@kevisual/js-filter": "^0.0.5", "@kevisual/oss": "^0.0.19", "@kevisual/video-tools": "^0.0.13", diff --git a/assistant/src/module/assistant/config/index.ts b/assistant/src/module/assistant/config/index.ts index 7949787..d64f0ae 100644 --- a/assistant/src/module/assistant/config/index.ts +++ b/assistant/src/module/assistant/config/index.ts @@ -91,7 +91,6 @@ export type AssistantConfigData = { */ url?: string; } - token?: string; registry?: string; // https://kevisual.cn /** * 前端代理,比如/root/home 转到https://kevisual.cn/root/home diff --git a/assistant/src/module/assistant/local-app-manager/assistant-app.ts b/assistant/src/module/assistant/local-app-manager/assistant-app.ts index 1ed1f37..93b67fe 100644 --- a/assistant/src/module/assistant/local-app-manager/assistant-app.ts +++ b/assistant/src/module/assistant/local-app-manager/assistant-app.ts @@ -7,12 +7,13 @@ import glob from 'fast-glob'; import type { App } from '@kevisual/router'; import { RemoteApp } from '@/module/remote-app/remote-app.ts'; import { logger } from '@/module/logger.ts'; -import { getEnvToken } from '@/module/http-token.ts'; import { initApi } from '@kevisual/api/proxy' import { Query } from '@kevisual/query'; import { initLightCode } from '@/module/light-code/index.ts'; import { ModuleResolver } from './assistant-app-resolve.ts'; import z from 'zod'; +import { assistantQuery } from '@/app.ts'; +import { useKey } from '@kevisual/context'; export class AssistantApp extends Manager { config: AssistantConfig; pagesPath: string; @@ -23,8 +24,8 @@ export class AssistantApp extends Manager { constructor(config: AssistantConfig, mainApp?: App) { config.checkMounted(); - const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps'); - const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages'); + const appsPath = config?.configPath?.appsDir + const pagesPath = config?.configPath?.pagesDir; const appsConfigPath = config.configPath?.appsConfigPath; const configFimename = path.basename(appsConfigPath || ''); super({ @@ -80,6 +81,12 @@ export class AssistantApp extends Manager { return pagesParse; } + /** + * 初始化远程应用连接,如果配置了远程应用且启用,则尝试连接远程应用服务器,并设置自动重连机制. + * 应用暴露 + * @info 需要登录权限 + * @param opts + */ async initRemoteApp(opts?: { token?: string, enabled?: boolean }) { const config = this.config.getConfig(); const share = config?.share; @@ -90,8 +97,17 @@ export class AssistantApp extends Manager { this.remoteApp = null; this.remoteIsConnected = false; } - const token = config?.token || opts?.token || getEnvToken() as string; - const url = new URL(share.url || 'https://kevisual.cn/ws/proxy'); + let token = opts?.token; + if (!token) { + token = await assistantQuery.queryLogin.getToken(); + } + let shareUrl = share?.url; + if (!shareUrl) { + const _url = new URL(config?.registry || 'https://kevisual.cn/'); + _url.pathname = '/ws/proxy'; + shareUrl = _url.toString(); + } + const url = new URL(shareUrl); const id = config?.app?.id; if (token && url && id) { const remoteApp = new RemoteApp({ @@ -129,11 +145,22 @@ export class AssistantApp extends Manager { this.remoteApp = remoteApp; } else { if (!token) { - logger.error('Token是远程应用连接必须的参数'); + logger.error('[remote-app] cli当前未登录,无法连接远程app'); + } else if (!id) { + logger.error('[remote-app] app id不存在,无法连接远程app'); } } } } + /** + * 本地路由初始化,根据配置加载应用的模块。 + * 加载局域网或者某一个链接的远程路由, 或者代码仓库的动态加载的light-code模块(实时同步远程文件执行) + * @info 不需要登录 + * 配置项说明: + * - router.proxy: 数组,包含需要代理的路由信息,每项可以是以下两种类型: + * - lightcode: 轻代码模块配置 + * @returns + */ async initRouterApp() { const config = this.config.getConfig(); const routerProxy = config?.router?.proxy || []; @@ -209,6 +236,10 @@ export class AssistantApp extends Manager { } } } + /** + * 动态加载文件插件模块的应用模块 + * @info 不需要登录 + */ async initRoutes() { const routes = this.config.getConfig().routes || []; for (const route of routes) { @@ -222,4 +253,53 @@ export class AssistantApp extends Manager { } } } + /** + * 检查本地用户登录状态,如果未登录且存在 CNB_TOKEN,则尝试使用 CNB_TOKEN 登录并更新用户信息 + */ + async checkLocalUser() { + const config = this.config.getConfig(); + const auth = config?.auth; + let checkCNB = false; + if (!auth?.username) { + checkCNB = true; + } else { + let temp = await assistantQuery.queryLogin.getToken() + if (temp) { + const isExpired = await assistantQuery.queryLogin.checkTokenValid() + console.log('Token 是否过期', isExpired); + } + logger.info('[assistant] 当前登录用户', auth.username, 'token有效性检查结果', !!temp); + } + const cnbToken = useKey('CNB_TOKEN'); + if (!checkCNB && cnbToken) { + const res = await assistantQuery.query.post({ + path: 'user', + key: 'cnb-login', + payload: { + data: { + cnbToken: cnbToken, + } + } + }); + if (res.code === 200) { + logger.info('CNB登录成功,用户信息已更新'); + const resUser = await assistantQuery.queryLogin.beforeSetLoginUser(res.data) + if (resUser.code === 200) { + const userInfo = resUser.data; + auth.username = userInfo.username; + auth.share = 'protected' + const app = config?.app || {}; + if (!app?.id) { + app.id = 'dev-cnb' + } + this.config.setConfig({ + auth, + app + }); + } else { + console.error('CNB登录失败,无法获取用户信息', resUser); + } + } + } + } } \ No newline at end of file diff --git a/assistant/src/module/assistant/query/index.ts b/assistant/src/module/assistant/query/index.ts index b50a3d8..ba5e0ae 100644 --- a/assistant/src/module/assistant/query/index.ts +++ b/assistant/src/module/assistant/query/index.ts @@ -38,4 +38,4 @@ export class AssistantQuery { getToken() { return this.queryLogin.getToken(); } -} \ No newline at end of file +} diff --git a/assistant/src/module/http-token.ts b/assistant/src/module/http-token.ts index 7a9232c..c779547 100644 --- a/assistant/src/module/http-token.ts +++ b/assistant/src/module/http-token.ts @@ -1,5 +1,4 @@ -import { useKey } from '@kevisual/use-config'; -import http from 'node:http'; +import { IncomingMessage } from 'node:http'; export const error = (msg: string, code = 500) => { return JSON.stringify({ code, message: msg }); }; @@ -16,7 +15,12 @@ const cookie = { return cookies; } } -export const getToken = async (req: http.IncomingMessage) => { +/** + * 从请求中获取token,优先级:Authorization header > query parameter > cookie + * @param req + * @returns + */ +export const getToken = async (req: IncomingMessage) => { let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || ''; const url = new URL(req.url || '', 'http://localhost'); if (!token) { @@ -31,9 +35,4 @@ export const getToken = async (req: http.IncomingMessage) => { } return { token }; -}; - -export const getEnvToken = () => { - const envTokne = useKey('KEVISUAL_TOKEN') || ''; - return envTokne; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/assistant/src/module/light-code/index.ts b/assistant/src/module/light-code/index.ts index 920f8a0..1f21f1c 100644 --- a/assistant/src/module/light-code/index.ts +++ b/assistant/src/module/light-code/index.ts @@ -8,6 +8,7 @@ const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2' import { filter } from "@kevisual/js-filter"; import { getHash, getStringHash } from '../file-hash.ts'; import { AssistantConfig } from '@/lib.ts'; +import { assistantQuery } from '@/app.ts'; const codeDemo = `// 这是一个示例代码文件 import {App} from '@kevisual/router'; @@ -48,11 +49,11 @@ export const initLightCode = async (opts: Opts) => { console.log('初始化 light-code 路由'); const config = opts.config as AssistantInit; const app = opts.router; - const token = config.getConfig()?.token || ''; + const token = await assistantQuery.getToken(); const query = config.query; const sync = opts.sync ?? 'remote'; if (!config || !app) { - console.error('initLightCode 缺少必要参数, config 或 app'); + console.error('[light-code] initLightCode 缺少必要参数, config 或 app'); return; } const lightcodeDir = opts.rootPath; @@ -126,7 +127,7 @@ export const initLightCode = async (opts: Opts) => { } diffList = findGlob({ cwd: lightcodeDir }); } else { - console.error('light-code 同步失败', queryRes.message); + console.error('[light-code] 同步失败', queryRes.message); diffList = codeFiles; } } else if (sync === 'local') { @@ -191,22 +192,22 @@ export const initLightCode = async (opts: Opts) => { } } } else { - console.error('light-code 路由执行失败', runRes.error); + console.error('[light-code] 路由执行失败', runRes.error); } } - console.log(`light-code 路由注册成功`, `注册${diffList.length}个路由`); + console.log(`[light-code] 路由注册成功`, `注册${diffList.length}个路由`); } export const clearLightCodeRoutes = (opts: Pick) => { const app = opts.router; if (!app) { - console.error('clearLightCodeRoutes 缺少必要参数, app'); + console.error('[light-code] clearLightCodeRoutes 缺少必要参数, app'); return; } const routes = app.getList(); for (const route of routes) { if (route.metadata?.source === 'light-code') { - // console.log(`删除 light-code 路由: ${route.path} ${route.id}`); + // console.log(`[light-code] 删除 light-code 路由: ${route.path} ${route.id}`); app.removeById(route.id); } } diff --git a/assistant/src/routes/remote/index.ts b/assistant/src/routes/remote/index.ts index 3514075..f50f5df 100644 --- a/assistant/src/routes/remote/index.ts +++ b/assistant/src/routes/remote/index.ts @@ -41,14 +41,14 @@ app.route({ } return; } - await manager.initRemoteApp({ enabled: true, token: ctx.query?.token }).then(() => { + try { + const res = await manager.initRemoteApp({ enabled: true, token: ctx.query?.token }) ctx.body = { content: '远程app连接成功', } - }).catch((err) => { + } catch (error) { ctx.body = { - content: `远程app连接失败: ${err.message}`, + content: '远程app连接失败', } - }); - + } }).addTo(app); \ No newline at end of file diff --git a/assistant/src/routes/user/index.ts b/assistant/src/routes/user/index.ts index f2c2606..910d68b 100644 --- a/assistant/src/routes/user/index.ts +++ b/assistant/src/routes/user/index.ts @@ -1,4 +1,5 @@ import { app, assistantConfig } from '../../app.ts'; +import { forwardCookie } from './utils/cookie.ts'; app.route({ path: 'admin', @@ -23,19 +24,7 @@ app.route({ password, }), }); - - // 转发上游服务器返回的所有 set-cookie(支持多个 cookie) - const setCookieHeaders = res.headers.getSetCookie?.() || []; - if (setCookieHeaders.length > 0) { - // 设置多个 cookie 到原生 http.ServerResponse - ctx.res.setHeader('Set-Cookie', setCookieHeaders); - } else { - // 兼容旧版本,使用 get 方法 - const setCookieHeader = res.headers.get('set-cookie'); - if (setCookieHeader) { - ctx.res.setHeader('Set-Cookie', setCookieHeader); - } - } + forwardCookie(ctx, res); const responseData = await res.json(); console.debug('admin login response', { res: responseData }); @@ -73,6 +62,9 @@ app.route({ key: 'me' }).define(async (ctx) => { const token = ctx.query.token; + if (!token) { + ctx.throw(401, 'token is required'); + } const res = await assistantConfig.query.post({ path: 'user', key: 'me', diff --git a/assistant/src/routes/user/utils/cookie.ts b/assistant/src/routes/user/utils/cookie.ts new file mode 100644 index 0000000..e0b9fb3 --- /dev/null +++ b/assistant/src/routes/user/utils/cookie.ts @@ -0,0 +1,14 @@ +export const forwardCookie = (ctx: any, res: any) => { + // 转发上游服务器返回的所有 set-cookie(支持多个 cookie) + const setCookieHeaders = res.headers.getSetCookie?.() || []; + if (setCookieHeaders.length > 0) { + // 设置多个 cookie 到原生 http.ServerResponse + ctx.res.setHeader('Set-Cookie', setCookieHeaders); + } else { + // 兼容旧版本,使用 get 方法 + const setCookieHeader = res.headers.get('set-cookie'); + if (setCookieHeader) { + ctx.res.setHeader('Set-Cookie', setCookieHeader); + } + } +} \ No newline at end of file diff --git a/assistant/src/server.ts b/assistant/src/server.ts index 1e5b18a..b235c72 100644 --- a/assistant/src/server.ts +++ b/assistant/src/server.ts @@ -47,14 +47,14 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1') qwenAsr, ]); const manager = useContextKey('manager', new AssistantApp(assistantConfig, app)); - setTimeout(() => { - manager.load({ runtime: 'client' }).then(() => { - console.log('Assistant App Loaded'); - }); - manager.initRemoteApp() - manager.initRouterApp() + setTimeout(async () => { + await manager.load({ runtime: 'client' }); + console.log('Assistant App Loaded'); + await manager.checkLocalUser() + await manager.initRemoteApp(); + await manager.initRouterApp(); if (runtime.isServer) { - manager.initRoutes(); + await manager.initRoutes(); } }, 1000); diff --git a/assistant/src/services/proxy/proxy-page-index.ts b/assistant/src/services/proxy/proxy-page-index.ts index e4b6027..ae866c6 100644 --- a/assistant/src/services/proxy/proxy-page-index.ts +++ b/assistant/src/services/proxy/proxy-page-index.ts @@ -45,7 +45,7 @@ const authFilter = async (req: http.IncomingMessage, res: http.ServerResponse) = return { code: 200, message: '允许访问根路径' }; } // 放开首页 - if (pathname.startsWith('/root/home') || pathname === '/root/cli/docs/') { + if (pathname.startsWith('/root/home') || pathname === '/root/cli-center/') { return { code: 200, message: '允许访问首页' }; } // 放开api, 以 /api, /v1, /client, /serve 开头的请求 @@ -86,7 +86,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp let noAdmin = !auth.username; const toSetting = () => { - res.writeHead(302, { Location: `/root/cli/setting/` }); + res.writeHead(302, { Location: `/root/cli-center/` }); res.end(); return true; } diff --git a/assistant/src/test/remote-app.ts b/assistant/src/test/remote-app.ts index 9a152da..ce4b76f 100644 --- a/assistant/src/test/remote-app.ts +++ b/assistant/src/test/remote-app.ts @@ -6,7 +6,7 @@ const main = async () => { const config = assistantConfig?.getConfig(); const share = config?.share; if (share && share.enabled !== false) { - const token = config?.token; + const token = '' const url = new URL(share.url || 'https://kevisual.cn/ws/proxy'); const id = config?.app?.id; if (!id) { @@ -14,7 +14,6 @@ const main = async () => { return; } if (!token) { - console.error('Token is required for remote app connection.'); return; } const remoteApp = new RemoteApp({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9ddbac..3776c3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,8 +127,8 @@ importers: assistant: dependencies: '@aws-sdk/client-s3': - specifier: ^3.994.0 - version: 3.994.0 + specifier: ^3.995.0 + version: 3.995.0 '@kevisual/js-filter': specifier: ^0.0.5 version: 0.0.5 @@ -170,8 +170,8 @@ importers: specifier: ^0.0.24 version: 0.0.24 '@kevisual/api': - specifier: ^0.0.55 - version: 0.0.55(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^0.0.57 + version: 0.0.57(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@kevisual/load': specifier: ^0.0.6 version: 0.0.6 @@ -459,8 +459,8 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.994.0': - resolution: {integrity: sha512-zIVQt/XfE2zTFrcPEf8R+KRaRD1++XHMPRhxXM2kVA6NA6Aq/cFCUyYOYYwSbWLF/XeToaX1auYGn3IoZKruPQ==} + '@aws-sdk/client-s3@3.995.0': + resolution: {integrity: sha512-r+t8qrQ0m9zoovYOH+wilp/glFRB/E+blsDyWzq2C+9qmyhCAQwaxjLaHM8T/uluAmhtZQIYqOH9ILRnvWtRNw==} engines: {node: '>=20.0.0'} '@aws-sdk/client-sso@3.993.0': @@ -555,8 +555,8 @@ packages: resolution: {integrity: sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.994.0': - resolution: {integrity: sha512-8y04Lv497KKd7f2TVlm2RaKQaNfnY17ZH8d3m+7sW/3R3BhZvHgWQZyqTb/vcN2ERz1YAnWx6woJyB3ZNFvakw==} + '@aws-sdk/signature-v4-multi-region@3.995.0': + resolution: {integrity: sha512-9Qx5JcAucnxnomREPb2D6L8K8GLG0rknt3+VK/BU3qTUynAcV4W21DQ04Z2RKDw+DYpW88lsZpXbVetWST2WUg==} engines: {node: '>=20.0.0'} '@aws-sdk/token-providers@3.993.0': @@ -575,8 +575,8 @@ packages: resolution: {integrity: sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-endpoints@3.994.0': - resolution: {integrity: sha512-L2obUBw4ACMMd1F/SG5LdfPyZ0xJNs9Maifwr3w0uWO+4YvHmk9FfRskfSfE/SLZ9S387oSZ+1xiP7BfVCP/Og==} + '@aws-sdk/util-endpoints@3.995.0': + resolution: {integrity: sha512-aym/pjB8SLbo9w2nmkrDdAAVKVlf7CM71B9mKhjDbJTzwpSFBPHqJIMdDyj0mLumKC0aIVDr1H6U+59m9GvMFw==} engines: {node: '>=20.0.0'} '@aws-sdk/util-locate-window@3.965.2': @@ -586,8 +586,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.972.3': resolution: {integrity: sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==} - '@aws-sdk/util-user-agent-node@3.972.9': - resolution: {integrity: sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA==} + '@aws-sdk/util-user-agent-node@3.972.10': + resolution: {integrity: sha512-LVXzICPlsheET+sE6tkcS47Q5HkSTrANIlqL1iFxGAY/wRQ236DX/PCAK56qMh9QJoXAfXfoRW0B0Og4R+X7Nw==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -1293,6 +1293,9 @@ packages: '@kevisual/api@0.0.55': resolution: {integrity: sha512-ylWpX12tzAULuvxJHhvt7N21SmVOiIV2eVbKSdJ51soo9XO2J7rGNrLcczQ1vPdaSHzCq+9RlAUyd0ogleGJCA==} + '@kevisual/api@0.0.57': + resolution: {integrity: sha512-U2nz+ckWZ4XGASC08xJT6WKQajhFQDd1iDb9tU1dHZECsvNvIzpHLG7RHFN1vahG1MdbQtppPmHgVTF2Zw7RWg==} + '@kevisual/app@0.0.1': resolution: {integrity: sha512-PEx8P3l0iNSqrz9Ib9kVCYfqNMX6/LfNu+cEafmY6ECP1cV5Vmv+TH2fuasMosKjtbH2fAdDi97sbd29tdEK+g==} @@ -5509,7 +5512,7 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.994.0': + '@aws-sdk/client-s3@3.995.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 @@ -5527,11 +5530,11 @@ snapshots: '@aws-sdk/middleware-ssec': 3.972.3 '@aws-sdk/middleware-user-agent': 3.972.11 '@aws-sdk/region-config-resolver': 3.972.3 - '@aws-sdk/signature-v4-multi-region': 3.994.0 + '@aws-sdk/signature-v4-multi-region': 3.995.0 '@aws-sdk/types': 3.973.1 - '@aws-sdk/util-endpoints': 3.994.0 + '@aws-sdk/util-endpoints': 3.995.0 '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.972.10 '@smithy/config-resolver': 4.4.6 '@smithy/core': 3.23.2 '@smithy/eventstream-serde-browser': 4.2.8 @@ -5582,7 +5585,7 @@ snapshots: '@aws-sdk/types': 3.973.1 '@aws-sdk/util-endpoints': 3.993.0 '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.972.10 '@smithy/config-resolver': 4.4.6 '@smithy/core': 3.23.2 '@smithy/fetch-http-handler': 5.3.9 @@ -5844,7 +5847,7 @@ snapshots: '@aws-sdk/types': 3.973.1 '@aws-sdk/util-endpoints': 3.993.0 '@aws-sdk/util-user-agent-browser': 3.972.3 - '@aws-sdk/util-user-agent-node': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.972.10 '@smithy/config-resolver': 4.4.6 '@smithy/core': 3.23.2 '@smithy/fetch-http-handler': 5.3.9 @@ -5882,7 +5885,7 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.994.0': + '@aws-sdk/signature-v4-multi-region@3.995.0': dependencies: '@aws-sdk/middleware-sdk-s3': 3.972.11 '@aws-sdk/types': 3.973.1 @@ -5920,7 +5923,7 @@ snapshots: '@smithy/util-endpoints': 3.2.8 tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.994.0': + '@aws-sdk/util-endpoints@3.995.0': dependencies: '@aws-sdk/types': 3.973.1 '@smithy/types': 4.12.0 @@ -5939,7 +5942,7 @@ snapshots: bowser: 2.13.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.972.9': + '@aws-sdk/util-user-agent-node@3.972.10': dependencies: '@aws-sdk/middleware-user-agent': 3.972.11 '@aws-sdk/types': 3.973.1 @@ -6630,6 +6633,27 @@ snapshots: - react-dom - use-sync-external-store + '@kevisual/api@0.0.57(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@kevisual/context': 0.0.8 + '@kevisual/js-filter': 0.0.5 + '@kevisual/load': 0.0.6 + '@paralleldrive/cuid2': 3.3.0 + es-toolkit: 1.44.0 + eventemitter3: 5.0.4 + fuse.js: 7.1.0 + nanoid: 5.1.6 + path-browserify-esm: 1.0.6 + sonner: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + spark-md5: 3.0.2 + zustand: 5.0.11(@types/react@19.2.10)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + - react + - react-dom + - use-sync-external-store + '@kevisual/app@0.0.1(dotenv@17.2.3)': dependencies: '@kevisual/ai': 0.0.19