From 9f20e149a0f89a9d343ae56ceb5e7280d485228a Mon Sep 17 00:00:00 2001 From: abearxiong Date: Tue, 20 Jan 2026 02:46:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=A1=B9=EF=BC=8C=E6=B7=BB=E5=8A=A0=20OpenCode=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=EF=BC=8C=E9=87=8D=E6=9E=84=E4=BB=A3=E7=90=86=E5=92=8C?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=80=BB=E8=BE=91=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20AGENTS=20=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant/.opencode/plugin/agent.ts | 1 + assistant/AGENTS.md | 204 ++++++++++++++++++ assistant/agent/call.ts | 33 +++ assistant/agent/index.ts | 3 + assistant/agent/plugin.ts | 64 ++++++ assistant/agent/step.ts | 27 +++ assistant/package.json | 11 +- assistant/src/main.ts | 6 + assistant/src/routes/index.ts | 17 ++ assistant/src/routes/opencode/index.ts | 1 + assistant/src/routes/opencode/ls.ts | 40 ++++ assistant/src/routes/opencode/module/open.ts | 56 +++++ .../src/services/proxy/proxy-page-index.ts | 4 + assistant/tsconfig.json | 1 + package.json | 5 +- pnpm-lock.yaml | 90 +++++--- 16 files changed, 531 insertions(+), 32 deletions(-) create mode 100644 assistant/.opencode/plugin/agent.ts create mode 100644 assistant/AGENTS.md create mode 100644 assistant/agent/call.ts create mode 100644 assistant/agent/index.ts create mode 100644 assistant/agent/plugin.ts create mode 100644 assistant/agent/step.ts create mode 100644 assistant/src/main.ts create mode 100644 assistant/src/routes/opencode/index.ts create mode 100644 assistant/src/routes/opencode/ls.ts create mode 100644 assistant/src/routes/opencode/module/open.ts diff --git a/assistant/.opencode/plugin/agent.ts b/assistant/.opencode/plugin/agent.ts new file mode 100644 index 0000000..d0de8bb --- /dev/null +++ b/assistant/.opencode/plugin/agent.ts @@ -0,0 +1 @@ +export * from "../../agent/plugin"; \ No newline at end of file diff --git a/assistant/AGENTS.md b/assistant/AGENTS.md new file mode 100644 index 0000000..05abf7f --- /dev/null +++ b/assistant/AGENTS.md @@ -0,0 +1,204 @@ +# AGENTS.md + +## 构建、代码检查和测试命令 + +### 核心命令 + +```bash +# 开发 +bun run dev # 运行主入口 (src/run.ts) +bun run dev:server # 启动服务器(热重载) +bun run dev:cnb # 使用自定义配置目录启动服务器 +bun run dev:share # 测试远程应用 + +# 构建 +bun run build # 完整生产构建(先清理 dist) +bun run build:lib # 构建库文件 +bun run postbuild:lib # 生成 TypeScript 类型定义 + +# 包管理 +pnpm install # 安装依赖(使用 pnpm v10.28.0) +``` + +### 环境要求 + +- **Node.js**: >=22.0.0 +- **运行时**: Bun(开发环境)/ Node.js(生产环境) +- **包管理器**: pnpm@10.28.0(强制要求) + +### 环境变量 + +- `ASSISTANT_CONFIG_DIR`: 覆盖助手配置文件目录路径 +- `BUN_PATH`: 覆盖 Bun 可执行文件路径 + +--- + +## 代码风格规范 + +### TypeScript + +- **严格模式**: 通过扩展配置启用(`@kevisual/types/json/backend.json`) +- **模块格式**: 仅 ESM(`package.json` 中的 `"type": "module"`) +- **目标**: NodeNext 模块解析 +- **路径别名**: 使用 `@/*` 作为本地导入别名(如 `import { Foo } from '@/module/foo'`) + +### 导入规则 + +```typescript +// Node.js 内置模块必须使用 node: 协议 +import fs from 'node:fs'; +import path from 'node:path'; +import { execSync } from 'node:child_process'; + +// 外部包(不使用路径别名) +import chalk from 'chalk'; +import { program } from 'commander'; + +// 本地模块(使用 @ 别名) +import { AssistantInit } from '@/services/init/index.ts'; + +// 类型导入 +import type { AssistantConfig } from '@/module/assistant/index.ts'; +``` + +### 命名规范 + +| 类型 | 规范 | 示例 | +|------|------|------| +| 类 | PascalCase | `AssistantInit`, `AssistantApp`, `AssistantQuery` | +| 接口 | PascalCase | `ProxyInfo`, `AssistantInitOptions` | +| 类型 | PascalCase | `AssistantConfigData` | +| 函数 | camelCase | `getBunPath`, `checkFileExists`, `parseHomeArg` | +| 变量 | camelCase | `configDir`, `assistantConfig`, `isPortAvailable` | +| 私有字段 | `#` 前缀 + camelCase | `#query`, `#config` | +| 常量 | UPPER_SNAKE_CASE 或 camelCase | `commonPaths`, `randomId` | +| 文件名 | kebab-case | `proxy-page-index.ts`, `query-login.ts` | +| 目录名 | kebab-case | `query-login`, `hot-api` | + +### 代码模式 + +#### 错误处理 + +```typescript +// 同步操作包装 +try { + const result = fs.readFileSync(path); + return result; +} catch (error) { + console.error('读取文件错误:', error.message); + continue; // 或 return/throw +} + +// 异步操作包装 +try { + const result = await someAsyncOperation(); + return result; +} catch (error) { + console.error('操作失败:', error.message); + process.exit(1); // 或 throw/return undefined +} +``` + +#### 上下文/依赖注入 + +使用 `@kevisual/use-config` 进行上下文管理: + +```typescript +export const assistantConfig = useContextKey('assistantConfig', () => { + return new AssistantInit({ + path: configDir, + init: isInit, + }); +}); +``` + +#### 路由模式 + +使用 `@kevisual/router` 的 App 和 SimpleRouter: + +```typescript +app.route({ + path: 'router', + key: 'list', + description: '获取路由列表', +}).define(async (ctx) => { + ctx.body = { list }; +}).addTo(app); +``` + +### JSDoc 文档 + +为公共 API、复杂函数和类型添加文档: + +```typescript +/** + * 助手初始化类 + * @class AssistantInit + */ +export class AssistantInit extends AssistantConfig {} + +/** + * @param {string} p - 要解析的路径 + * @returns {string} 解析后的绝对路径 + */ +export const w = (p: string) => path.join(__dirname, p); +``` + +### 文件组织 + +- **路由**: `src/routes/` 和 `src/routes-simple/` +- **模块**: `src/module/`(功能模块) +- **服务**: `src/services/`(公共服务) +- **命令**: `src/command/`(CLI 命令) +- **查询**: `src/query/`(API/查询逻辑) +- **入口文件**: + - `src/index.ts` - 主 CLI 入口 + - `src/server.ts` - 服务器入口 + - `src/run.ts` - 开发运行器 + - `src/run-server.ts` - 服务器运行器 + +### 控制台输出 + +使用 `chalk` 实现彩色输出: + +```typescript +console.log(chalk.blue('助手路径不存在,正在创建...')); +console.log(chalk.yellow('助手路径已存在'), chalk.green(assistantConfig.configDir)); +console.error(chalk.red('启动服务器错误:'), error.message); +``` + +### 系统检测 + +```typescript +const isWindows = process.platform === 'win32'; +const bunExecutableName = isWindows ? 'bun.exe' : 'bun'; +``` + +### 端口处理 + +```typescript +import getPort, { portNumbers } from 'get-port'; + +const port = await getPort({ port: 51515 }); +const availablePort = await getPort({ port: portNumbers(51515, 52000) }); +``` + +### 关键依赖 + +- `@kevisual/router` - 应用路由 +- `@kevisual/query` - API 查询 +- `@kevisual/use-config` - 配置/上下文 +- `commander` - CLI 参数解析 +- `chalk` - 终端颜色 +- `ws` - WebSocket(通过 `@kevisual/ws`) +- `pm2` - 进程管理(生产环境) + +### 重要说明 + +1. 开发时始终使用 `bun`,但构建输出以 Node.js 为目标 +2. 打包时外部包必须在 `bun.config.mjs` 的 `external` 数组中声明 +3. 库构建排除 `pm2` +4. 以 `ENVISION_*` 为前缀的环境变量在构建时可用 +5. 项目在业务逻辑中广泛使用中文注释 +6. 私有类成员应使用 `#` 前缀语法 +7. 操作前始终使用 `checkFileExists` 检查文件是否存在 diff --git a/assistant/agent/call.ts b/assistant/agent/call.ts new file mode 100644 index 0000000..f265207 --- /dev/null +++ b/assistant/agent/call.ts @@ -0,0 +1,33 @@ +import { createSkill } from '@kevisual/router' +import { app } from './index.ts' +import { tool } from '@opencode-ai/plugin/tool' + +// "调用 path: router key: list" +app.route({ + path: 'call', + key: '', + description: '调用', + middleware: ['auth'], + metadata: { + tags: ['opencode'], + ...createSkill({ + skill: 'call-app', + title: '调用app应用', + summary: '调用router的应用, 参数path, key, payload', + args: { + path: tool.schema.string().describe('应用路径,例如 cnb'), + key: tool.schema.string().optional().describe('应用key,例如 list-repos'), + payload: tool.schema.object({}).optional().describe('调用参数'), + } + }) + }, +}).define(async (ctx) => { + const { path, key = '' } = ctx.query; + if (!path) { + ctx.throw('路径path不能为空'); + } + const res = await ctx.run({ path, key, payload: ctx.query.payload || {} }, { + ...ctx + }); + ctx.forward(res); +}).addTo(app) \ No newline at end of file diff --git a/assistant/agent/index.ts b/assistant/agent/index.ts new file mode 100644 index 0000000..4e6681a --- /dev/null +++ b/assistant/agent/index.ts @@ -0,0 +1,3 @@ +import { app } from '../src/main.ts' + +export { app } \ No newline at end of file diff --git a/assistant/agent/plugin.ts b/assistant/agent/plugin.ts new file mode 100644 index 0000000..f931300 --- /dev/null +++ b/assistant/agent/plugin.ts @@ -0,0 +1,64 @@ +import { tool } from "@opencode-ai/plugin/tool" +import { type Plugin } from "@opencode-ai/plugin" +import { app } from './index.ts'; +import { Skill } from "@kevisual/router"; + +import './call.ts'; +import './step.ts'; + +const routes = app.router.routes.filter(r => { + const metadata = r.metadata as Skill + if (metadata && metadata.tags && metadata.tags.includes('opencode')) { + return !!metadata.skill + } + return false +}) + +// opencode run "查看系统信息" +export const AgentPlugin: Plugin = async ({ project, client, $, directory, worktree }) => { + return { + 'tool': { + ...routes.reduce((acc, route) => { + const metadata = route.metadata as Skill + acc[metadata.skill!] = { + name: metadata.title || metadata.skill, + description: metadata.summary || '', + args: metadata.args || {}, + async execute(args: Record) { + console.log(`Executing skill ${metadata.skill} with args:`, args); + await client.app.log({ + body: { + service: 'cnb', + level: 'info', + message: `Executing skill ${metadata.skill} with args: ${JSON.stringify(args)}` + } + }); + const res = await app.run({ + path: route.path, + key: route.key, + payload: args + }, + // @ts-ignore + { appId: app.appId! }); + if (res.code === 200) { + if (res.data?.content) { + return res.data.content; + } + const str = JSON.stringify(res.data || res, null, 2); + if (str.length > 5000) { + return str.slice(0, 5000) + '... (truncated)'; + } + return str; + } + return `Error: ${res?.message || '无法获取结果'}`; + } + } + return acc; + }, {} as Record) + }, + 'tool.execute.before': async (opts) => { + // console.log('CnbPlugin: tool.execute.before', opts.tool); + // delete toolSkills['cnb-login-verify'] + } + } +} diff --git a/assistant/agent/step.ts b/assistant/agent/step.ts new file mode 100644 index 0000000..e7e1a30 --- /dev/null +++ b/assistant/agent/step.ts @@ -0,0 +1,27 @@ +import { createSkill } from '@kevisual/router' +import { app } from './index.ts' +import { tool } from '@opencode-ai/plugin/tool' + +// "调用 path: test key: step" +app.route({ + path: 'test', + key: 'step', + description: '测试步骤调用', + metadata: { + tags: ['opencode'], + ...createSkill({ + skill: 'test-step', + title: '获取系统信息', + summary: '根据步骤获取系统信息', + args: { + } + }) + }, +}).define(async (ctx) => { + ctx.body = { + context: `执行步骤 +1. 调用 path: client key: system 获取系统信息 +2. 调用 path: client key: time 获取当前时间 +3. 返回结果`, + }; +}).addTo(app) \ No newline at end of file diff --git a/assistant/package.json b/assistant/package.json index a7479e4..595ed2d 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -10,7 +10,7 @@ ], "author": "abearxiong (https://www.xiongxiao.me)", "license": "MIT", - "packageManager": "pnpm@10.28.0", + "packageManager": "pnpm@10.28.1", "type": "module", "files": [ "dist", @@ -41,15 +41,16 @@ } }, "devDependencies": { - "@kevisual/ai": "^0.0.20", + "@kevisual/ai": "^0.0.21", "@kevisual/load": "^0.0.6", "@kevisual/local-app-manager": "^0.1.32", "@kevisual/logger": "^0.0.4", "@kevisual/query": "0.0.35", "@kevisual/query-login": "0.0.7", - "@kevisual/router": "^0.0.55", + "@kevisual/router": "^0.0.56", "@kevisual/types": "^0.0.11", "@kevisual/use-config": "^1.0.28", + "@opencode-ai/plugin": "^1.1.25", "@types/bun": "^1.3.6", "@types/lodash-es": "^4.17.12", "@types/node": "^25.0.9", @@ -80,7 +81,9 @@ "@kevisual/ha-api": "^0.0.6", "@kevisual/oss": "^0.0.16", "@kevisual/video-tools": "^0.0.13", - "eventemitter3": "^5.0.1", + "@opencode-ai/sdk": "^1.1.25", + "es-toolkit": "^1.44.0", + "eventemitter3": "^5.0.4", "lowdb": "^7.0.1", "lru-cache": "^11.2.4", "pm2": "^6.0.14", diff --git a/assistant/src/main.ts b/assistant/src/main.ts new file mode 100644 index 0000000..57e7adf --- /dev/null +++ b/assistant/src/main.ts @@ -0,0 +1,6 @@ +import { app, assistantConfig } from './app.ts'; + +import './routes/index.ts'; +import './routes-simple/index.ts'; + +export { app, assistantConfig }; \ No newline at end of file diff --git a/assistant/src/routes/index.ts b/assistant/src/routes/index.ts index 855a5c6..ac2b2fa 100644 --- a/assistant/src/routes/index.ts +++ b/assistant/src/routes/index.ts @@ -9,8 +9,11 @@ import './user/index.ts'; // TODO: 移除 // import './hot-api/key-sender/index.ts'; +import './opencode/index.ts'; + import os from 'node:os'; import { authCache } from '@/module/cache/auth.ts'; +import { createSkill } from '@kevisual/router'; const getTokenUser = async (token: string) => { const query = assistantConfig.query const res = await query.post({ @@ -101,6 +104,9 @@ app description: '获取当前登录用户信息, 第一个登录的用户为管理员用户', }) .define(async (ctx) => { + if (!ctx.query?.token && ctx.appId === app.appId) { + return; + } const authResult = await checkAuth(ctx); if (authResult.code !== 200) { ctx.throw(authResult.code, authResult.message); @@ -115,6 +121,9 @@ app }) .define(async (ctx) => { console.log('query', ctx.query); + if (!ctx.query?.token && ctx.appId === app.appId) { + return; + } const authResult = await checkAuth(ctx, true); if (authResult.code !== 200) { ctx.throw(authResult.code, authResult.message); @@ -152,6 +161,14 @@ app path: 'client', key: 'system', description: '获取系统信息', + metadata: { + tags: ['opencode'], + ...createSkill({ + skill: 'view-system-info', + title: '查看系统信息', + summary: '获取服务器操作系统平台、架构和版本信息', + }) + } }) .define(async (ctx) => { const { platform, arch, release } = os; diff --git a/assistant/src/routes/opencode/index.ts b/assistant/src/routes/opencode/index.ts new file mode 100644 index 0000000..91a5c4e --- /dev/null +++ b/assistant/src/routes/opencode/index.ts @@ -0,0 +1 @@ +import './ls.ts' \ No newline at end of file diff --git a/assistant/src/routes/opencode/ls.ts b/assistant/src/routes/opencode/ls.ts new file mode 100644 index 0000000..4e1f01c --- /dev/null +++ b/assistant/src/routes/opencode/ls.ts @@ -0,0 +1,40 @@ +import { useKey } from "@kevisual/use-config"; +import { app } from '@/app.ts' +import { createSkill } from "@kevisual/router"; +import { opencodeManager } from './module/open.js' + +app.route({ + path: 'opencode', + key: 'create', + middleware: ['auth'], + description: '创建 OpenCode 客户端', + metadata: { + tags: ['opencode'], + ...createSkill({ + skill: 'create-opencode-client', + title: '创建 OpenCode 客户端', + summary: '创建 OpenCode 客户端,如果存在则复用', + args: { + + } + }) + }, +}).define(async (ctx) => { + const client = await opencodeManager.getClient(); + ctx.body = { success: true, url: opencodeManager.url, message: 'OpenCode 客户端已就绪' }; +}).addTo(app); + +// 调用 path: opencode key: ls-projects +app.route({ + path: 'opencode', + key: 'ls-projects' +}).define(async (ctx) => { + const client = await opencodeManager.getClient(); + const projects = await client.project.list(); + const currentProject = await client.project.current(); + ctx.body = { + projects, + currentProject + }; +}).addTo(app); + diff --git a/assistant/src/routes/opencode/module/open.ts b/assistant/src/routes/opencode/module/open.ts new file mode 100644 index 0000000..f81b32c --- /dev/null +++ b/assistant/src/routes/opencode/module/open.ts @@ -0,0 +1,56 @@ +import { createOpencode, OpencodeClient } from "@opencode-ai/sdk"; + +export class OpencodeManager { + private static instance: OpencodeManager | null = null; + private client: OpencodeClient | null = null; + private server: { url: string; close(): void } | null = null; + private isInitializing: boolean = false; + + public url: string = ''; + private constructor() {} + + static getInstance(): OpencodeManager { + if (!OpencodeManager.instance) { + OpencodeManager.instance = new OpencodeManager(); + } + return OpencodeManager.instance; + } + + async getClient(): Promise { + // 如果已经有 client,直接返回 + if (this.client) { + return this.client; + } + + // 如果正在初始化,等待初始化完成 + if (this.isInitializing) { + await new Promise(resolve => setTimeout(resolve, 100)); + return this.getClient(); + } + + // 开始初始化 + this.isInitializing = true; + try { + const result = await createOpencode({ + hostname: '0.0.0.0', + }); + console.log('OpencodeManager: OpenCode 服务已启动', result.server.url); + this.url = result.server.url; + this.client = result.client; + this.server = result.server; + return this.client; + } finally { + this.isInitializing = false; + } + } + + close(): void { + if (this.server) { + this.server.close(); + this.server = null; + } + this.client = null; + } +} + +export const opencodeManager = OpencodeManager.getInstance(); diff --git a/assistant/src/services/proxy/proxy-page-index.ts b/assistant/src/services/proxy/proxy-page-index.ts index b1b73fc..4eeab0b 100644 --- a/assistant/src/services/proxy/proxy-page-index.ts +++ b/assistant/src/services/proxy/proxy-page-index.ts @@ -111,6 +111,10 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp logger.debug('handle by router', { url: req.url }); return; } + if (pathname.startsWith('/router') || pathname.startsWith('/opencode')) { + logger.debug('handle by router (opencode/router)', { url: req.url }); + return; + } // client, api, v1, serve 开头的拦截 const apiProxy = _assistantConfig?.api?.proxy || []; const defaultApiProxy = createApiProxy(_assistantConfig?.app?.url || 'https://kevisual.cn'); diff --git a/assistant/tsconfig.json b/assistant/tsconfig.json index b23d0f2..9601927 100644 --- a/assistant/tsconfig.json +++ b/assistant/tsconfig.json @@ -14,5 +14,6 @@ }, "include": [ "src/**/*", + "agent/**/*" ], } \ No newline at end of file diff --git a/package.json b/package.json index 44b8613..e0774ba 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,10 @@ "@kevisual/app": "^0.0.2", "@kevisual/context": "^0.0.4", "@kevisual/use-config": "^1.0.28", + "@opencode-ai/sdk": "^1.1.25", "@types/busboy": "^1.5.4", "busboy": "^1.6.0", - "eventemitter3": "^5.0.1", + "eventemitter3": "^5.0.4", "lowdb": "^7.0.1", "lru-cache": "^11.2.4", "micromatch": "^4.0.8", @@ -55,7 +56,6 @@ "unstorage": "^1.17.4" }, "devDependencies": { - "pm2": "^6.0.14", "@kevisual/dts": "^0.0.3", "@kevisual/load": "^0.0.6", "@kevisual/logger": "^0.0.4", @@ -75,6 +75,7 @@ "form-data": "^4.0.5", "ignore": "^7.0.5", "jsonwebtoken": "^9.0.3", + "pm2": "^6.0.14", "tar": "^7.5.3", "zustand": "^5.0.10" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78f1266..8b78d5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@kevisual/use-config': specifier: ^1.0.28 version: 1.0.28(dotenv@17.2.3) + '@opencode-ai/sdk': + specifier: ^1.1.25 + version: 1.1.25 '@types/busboy': specifier: ^1.5.4 version: 1.5.4 @@ -27,8 +30,8 @@ importers: specifier: ^1.6.0 version: 1.6.0 eventemitter3: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.4 + version: 5.0.4 lowdb: specifier: ^7.0.1 version: 7.0.1 @@ -126,9 +129,15 @@ importers: '@kevisual/video-tools': specifier: ^0.0.13 version: 0.0.13(dotenv@17.2.3)(supports-color@10.2.2) + '@opencode-ai/sdk': + specifier: ^1.1.25 + version: 1.1.25 + es-toolkit: + specifier: ^1.44.0 + version: 1.44.0 eventemitter3: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.4 + version: 5.0.4 lowdb: specifier: ^7.0.1 version: 7.0.1 @@ -143,8 +152,8 @@ importers: version: 1.17.4(idb-keyval@6.2.2) devDependencies: '@kevisual/ai': - specifier: ^0.0.20 - version: 0.0.20 + specifier: ^0.0.21 + version: 0.0.21 '@kevisual/load': specifier: ^0.0.6 version: 0.0.6 @@ -161,14 +170,17 @@ importers: specifier: 0.0.7 version: 0.0.7(@kevisual/query@0.0.35) '@kevisual/router': - specifier: ^0.0.55 - version: 0.0.55 + specifier: ^0.0.56 + version: 0.0.56 '@kevisual/types': specifier: ^0.0.11 version: 0.0.11 '@kevisual/use-config': specifier: ^1.0.28 version: 1.0.28(dotenv@17.2.3) + '@opencode-ai/plugin': + specifier: ^1.1.25 + version: 1.1.25 '@types/bun': specifier: ^1.3.6 version: 1.3.6 @@ -1257,8 +1269,8 @@ packages: '@kevisual/ai@0.0.19': resolution: {integrity: sha512-AFc8m6OcHZNxCb88bvzhvwWTZ4EVYyPupBzPUsLKLpdNBvsqm9TRboKCM2brJj2cqHnm+H+RbAk9AcGJkYhRCA==} - '@kevisual/ai@0.0.20': - resolution: {integrity: sha512-RW4a1T8XbIaonjp4ndt+5YIVeHR9O+pAzZZ1dWeYIlqQXvDuJdHSVukOh4ohgFS0tyEPzCr39seFeb6Y0YNzkw==} + '@kevisual/ai@0.0.21': + resolution: {integrity: sha512-4YvnEDpgCje3jDZug95pXzmIS4jHHulDjXONXSWLScsSLiqIRZnZQ6K+2IbCnIDAFmGIBYR/xFt0fyJIKJmrDQ==} '@kevisual/api@0.0.17': resolution: {integrity: sha512-hW3Q182Lm8wggWfHTEKVTKsmp8MWFINB9l82nEbnwTnd1Lh9DPeQo1hMft7aeL8aGe4vjFCTv4MHixXjmQTzGg==} @@ -1332,8 +1344,8 @@ packages: '@kevisual/router@0.0.51': resolution: {integrity: sha512-i9qYBeS/um78oC912oWJD3iElB+5NTKyTrz1Hzf4DckiUFnjLL81UPwjIh5I2l9+ul0IZ/Pxx+sFSF99fJkzKg==} - '@kevisual/router@0.0.55': - resolution: {integrity: sha512-DVhXbbUCfSWWXsp1id1HBrkGiMZ6nFUBD1/C5E7IpLE5B32w7sv2xjKUt98OriFl0uyuneMEIZuZsAQaKplQ5g==} + '@kevisual/router@0.0.56': + resolution: {integrity: sha512-3k+wRUNT+kHqoA3r+6+lJRVHvbDMqNW75iWcYrzRFbf9lkEADYXzdIXHrOj/0Dk1EiTuLpK1i1e5dpWNpqlegA==} '@kevisual/types@0.0.11': resolution: {integrity: sha512-idNLDTEKVdNXZHFQq8PTN62nflh94kvGtx+v8YDcMxt0Zo+HWVZTFElm+dMQxAs/vn4wo8F2r3VwzWNX/vcqwQ==} @@ -1399,6 +1411,12 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@opencode-ai/plugin@1.1.25': + resolution: {integrity: sha512-oTUWS446H/j7z3pdzo3cOrB5N87XZ/RKdgPD8yHv/rLX92B4YQHjOqggVQ56Q+1VEnN0jxzhoqRylv/0ZEts/Q==} + + '@opencode-ai/sdk@1.1.25': + resolution: {integrity: sha512-mWUX489ArEF2ICg3iZsx2VQaGS3Z2j/dwAJDacao9t7dGDzjOIaacPw2weZ10zld7XmT9V9C0PM/A5lDZ52J+w==} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -3096,8 +3114,8 @@ packages: eventemitter2@6.4.9: resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@9.6.1: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} @@ -3342,6 +3360,10 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} + hono@4.11.4: + resolution: {integrity: sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -5036,6 +5058,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.8: + resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} + zustand@5.0.10: resolution: {integrity: sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==} engines: {node: '>=12.20.0'} @@ -6359,7 +6384,7 @@ snapshots: '@kevisual/permission': 0.0.3 '@kevisual/query': 0.0.31 - '@kevisual/ai@0.0.20': + '@kevisual/ai@0.0.21': dependencies: '@kevisual/logger': 0.0.4 '@kevisual/permission': 0.0.3 @@ -6370,7 +6395,7 @@ snapshots: '@kevisual/js-filter': 0.0.3 '@kevisual/load': 0.0.6 es-toolkit: 1.44.0 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 nanoid: 5.1.6 '@kevisual/app@0.0.1(dotenv@17.2.3)': @@ -6444,7 +6469,7 @@ snapshots: '@kevisual/context': 0.0.4 codemirror: 6.0.2 dayjs: 1.11.19 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 lit-html: 3.3.1 nanoid: 5.1.6 prettier: 3.7.4 @@ -6460,7 +6485,7 @@ snapshots: '@kevisual/load@0.0.6': dependencies: - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 '@kevisual/local-app-manager@0.1.32(supports-color@10.2.2)': dependencies: @@ -6532,7 +6557,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@kevisual/router@0.0.55': {} + '@kevisual/router@0.0.56': + dependencies: + hono: 4.11.4 '@kevisual/types@0.0.11': {} @@ -6550,7 +6577,7 @@ snapshots: '@kevisual/video': 0.0.2 crypto-js: 4.2.0 dayjs: 1.11.19 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 nanoid: 5.1.6 transitivePeerDependencies: - dotenv @@ -6645,6 +6672,13 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@opencode-ai/plugin@1.1.25': + dependencies: + '@opencode-ai/sdk': 1.1.25 + zod: 4.1.8 + + '@opencode-ai/sdk@1.1.25': {} + '@oslojs/encoding@1.1.0': {} '@peculiar/asn1-cms@2.6.0': @@ -6776,7 +6810,7 @@ snapshots: async: 2.6.4 debug: 4.3.7(supports-color@10.2.2) eventemitter2: 6.4.9 - extrareqp2: 1.0.0(debug@4.3.7(supports-color@10.2.2)) + extrareqp2: 1.0.0(debug@4.3.7) ws: 7.5.10 transitivePeerDependencies: - bufferutil @@ -8732,7 +8766,7 @@ snapshots: eventemitter2@6.4.9: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} execa@9.6.1: dependencies: @@ -8751,9 +8785,9 @@ snapshots: extend@3.0.2: {} - extrareqp2@1.0.0(debug@4.3.7(supports-color@10.2.2)): + extrareqp2@1.0.0(debug@4.3.7): dependencies: - follow-redirects: 1.15.9(debug@4.3.7(supports-color@10.2.2)) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug @@ -8807,7 +8841,7 @@ snapshots: flattie@1.1.1: {} - follow-redirects@1.15.9(debug@4.3.7(supports-color@10.2.2)): + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@10.2.2) @@ -9093,6 +9127,8 @@ snapshots: highlight.js@11.11.1: {} + hono@4.11.4: {} + hookable@5.5.3: {} html-escaper@3.0.3: {} @@ -9984,7 +10020,7 @@ snapshots: p-queue@8.1.1: dependencies: - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 p-timeout: 6.1.4 p-timeout@6.1.4: {} @@ -11115,6 +11151,8 @@ snapshots: zod@3.25.76: {} + zod@4.1.8: {} + zustand@5.0.10(@types/react@19.2.8)(react@19.2.3): optionalDependencies: '@types/react': 19.2.8