From 0f28176fd46e14583b5586d0d7f9a37595902ab1 Mon Sep 17 00:00:00 2001 From: Acbox Date: Sun, 11 Jan 2026 02:08:55 +0800 Subject: [PATCH] feat: cli --- package.json | 1 + packages/api/memory.db | Bin 0 -> 12288 bytes packages/api/src/modules/auth/service.ts | 39 +- packages/api/src/modules/schedule/service.ts | 10 + packages/cli/.eslintrc.json | 7 + packages/cli/.gitignore | 34 ++ packages/cli/README.md | 339 +++++++++++- packages/cli/package.json | 31 +- packages/cli/src/client.ts | 29 + packages/cli/src/commands/agent.ts | 223 ++++++++ packages/cli/src/commands/auth.ts | 143 +++++ packages/cli/src/commands/debug.ts | 55 ++ packages/cli/src/commands/memory.ts | 191 +++++++ packages/cli/src/commands/model.ts | 400 ++++++++++++++ packages/cli/src/commands/schedule.ts | 305 +++++++++++ packages/cli/src/commands/settings.ts | 181 ++++++ packages/cli/src/commands/user.ts | 238 ++++++++ packages/cli/src/config.ts | 72 +++ packages/cli/src/index.ts | 54 ++ packages/cli/src/types.ts | 71 +++ packages/cli/src/utils.ts | 63 +++ packages/cli/tsconfig.json | 22 + pnpm-lock.yaml | 548 ++++++++++++++++++- 23 files changed, 3032 insertions(+), 24 deletions(-) create mode 100644 packages/api/memory.db create mode 100644 packages/cli/.eslintrc.json create mode 100644 packages/cli/.gitignore create mode 100644 packages/cli/src/client.ts create mode 100644 packages/cli/src/commands/agent.ts create mode 100644 packages/cli/src/commands/auth.ts create mode 100644 packages/cli/src/commands/debug.ts create mode 100644 packages/cli/src/commands/memory.ts create mode 100644 packages/cli/src/commands/model.ts create mode 100644 packages/cli/src/commands/schedule.ts create mode 100644 packages/cli/src/commands/settings.ts create mode 100644 packages/cli/src/commands/user.ts create mode 100644 packages/cli/src/config.ts create mode 100644 packages/cli/src/types.ts create mode 100644 packages/cli/src/utils.ts create mode 100644 packages/cli/tsconfig.json diff --git a/package.json b/package.json index a9d87473..ffa39dd1 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "db:migrate": "pnpm --filter @memohome/db migrate", "db:generate": "pnpm --filter @memohome/db generate", "db:studio": "pnpm --filter @memohome/db studio", + "cli": "pnpm --filter @memohome/cli start", "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest" diff --git a/packages/api/memory.db b/packages/api/memory.db new file mode 100644 index 0000000000000000000000000000000000000000..c6a8e0bdf70dd242a9d102ec0e060f8b643c2d46 GIT binary patch literal 12288 zcmeI&PjAyO6aa81Mf|D6ir}ytdJ>mKTc)JVl5`RhfrhD~>qeJIbeGCb92%)jx+LA$ zafkyq#8=?DJKqRSd-E^^s6IzCFANGY}8>9aR}YM=kB=!I2?5Moxu>^xI=J!@1WZkDfZpI zw{^98kw$Ft+;<-I55(p7ddmf~jGxBoJRhCX$(&cKmXirTyYYJ(6mgpTEoG2#TJUfr z%JlM~%XITuc=JS@k3v4-V$;>Tb==pE-}69QyoyrQ=8dPMaVZ)M6hHwKKmim$0Te(1 z6hHwKKmio^F9j~@a-I0(qVeQ8xo=pe6=^I|+cc!AVMk15p=GF+&7&wVBP%j@{G4Zy zmA`G|W!X(Z*EIDk%|hULPR}7uz)jvy;(QF0rSk$9r@fz z)9qc&*fm<)x?vq3Ne{$Xo|Q)lnr&)prlp#;sf(j%k!qWUu9_y>X-6Svtj&IY{`%v~ zr4z99!=Fi8Wm3y%9+&mM)c8fj2LlCA00mG01yBG5Pyhu`00mG01yJA?2t1Z*inRI< G2>Ca-*~?x4 literal 0 HcmV?d00001 diff --git a/packages/api/src/modules/auth/service.ts b/packages/api/src/modules/auth/service.ts index ee2c98dd..3ea64fc7 100644 --- a/packages/api/src/modules/auth/service.ts +++ b/packages/api/src/modules/auth/service.ts @@ -11,20 +11,51 @@ export const validateUser = async (username: string, password: string) => { const rootUser = process.env.ROOT_USER const rootPassword = process.env.ROOT_USER_PASSWORD + let userId: string | null = null + if (rootUser && rootPassword && username === rootUser) { if (password === rootPassword) { + // 检查 root 用户是否存在于数据库中 + const [existingUser] = await db + .select() + .from(users) + .where(eq(users.username, rootUser)) + + userId = existingUser?.id + if (!existingUser) { + // 为 root 用户创建数据库记录 + // 使用占位符密码哈希,因为实际密码在环境变量中 + const [newUser] = await db + .insert(users) + .values({ + username: rootUser, + passwordHash: 'ENV_BASED_AUTH', // 占位符,实际使用环境变量验证 + role: 'admin', + displayName: 'Root User', + email: null, + avatarUrl: null, + isActive: true, + }) + .onConflictDoNothing() // 避免并发创建导致的冲突 + .returning({ + id: users.id, + }) + + userId = newUser.id + } + // 检查 root 用户的 settings 是否存在,不存在则创建 const [existingSettings] = await db .select() .from(settings) - .where(eq(settings.userId, 'root')) + .where(eq(settings.userId, userId)) if (!existingSettings) { // 为 root 用户创建默认 settings await db .insert(settings) .values({ - userId: 'root', + userId: userId, defaultChatModel: null, defaultEmbeddingModel: null, defaultSummaryModel: null, @@ -36,7 +67,7 @@ export const validateUser = async (username: string, password: string) => { // 返回 ROOT 用户信息 return { - id: 'root', + id: userId, username: rootUser, role: 'admin' as const, displayName: 'Root User', @@ -49,7 +80,7 @@ export const validateUser = async (username: string, password: string) => { const [user] = await db .select() .from(users) - .where(eq(users.username, username)) + .where(eq(users.id, userId!)) if (!user) { return null diff --git a/packages/api/src/modules/schedule/service.ts b/packages/api/src/modules/schedule/service.ts index 8bec4a99..e705ab69 100644 --- a/packages/api/src/modules/schedule/service.ts +++ b/packages/api/src/modules/schedule/service.ts @@ -94,6 +94,7 @@ export const createSchedule = async ( userId: string, data: CreateScheduleInput ) => { + const { scheduleTask } = createScheduler() const [newSchedule] = await db .insert(schedule) .values({ @@ -106,6 +107,15 @@ export const createSchedule = async ( active: true, }) .returning() + + scheduleTask(userId, { + id: newSchedule.id!, + pattern: newSchedule.pattern, + name: newSchedule.name, + description: newSchedule.description, + command: newSchedule.command, + maxCalls: newSchedule.maxCalls || undefined, + }) return newSchedule } diff --git a/packages/cli/.eslintrc.json b/packages/cli/.eslintrc.json new file mode 100644 index 00000000..2f9df27a --- /dev/null +++ b/packages/cli/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["../../eslint.config.mjs"], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } +} + diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 00000000..a14702c4 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/cli/README.md b/packages/cli/README.md index 7d322a7c..23679529 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1 +1,338 @@ -# @memohome/shared +# @memohome/cli + +MemoHome 的命令行工具,使用 Elysia Eden 与 API 服务器通信。 + +## 功能特性 + +- 🔐 **用户认证** - 登录、登出、查看当前用户 +- 👥 **用户管理** - 完整的用户 CRUD 操作(管理员) +- 🤖 **模型管理** - AI 模型配置管理 +- 💬 **Agent 对话** - 与 AI Agent 进行对话,支持流式响应 +- 🧠 **记忆管理** - 搜索、添加、查看对话记忆 +- ⚙️ **设置管理** - 个性化配置 +- 📅 **日程管理** - 定时任务管理 + +## 安装 + +```bash +# 在项目根目录 +pnpm install + +# 进入 CLI 目录 +cd packages/cli +``` + +## 快速开始 + +### 1. 配置 API 地址(可选) + +默认连接到 `http://localhost:7002`,如需修改: + +```bash +bun run src/index.ts auth config --set http://your-api-url:port +``` + +### 2. 登录 + +```bash +bun run src/index.ts auth login +# 或直接提供用户名和密码 +bun run src/index.ts auth login -u admin -p password +``` + +### 3. 开始使用 + +```bash +# 查看帮助 +bun run src/index.ts --help + +# 与 Agent 对话 +bun run src/index.ts agent chat "你好" + +# 进入交互模式 +bun run src/index.ts agent interactive +``` + +## 命令参考 + +### 认证命令 (`auth`) + +```bash +# 登录 +memohome auth login [-u username] [-p password] + +# 登出 +memohome auth logout + +# 查看当前登录用户 +memohome auth whoami + +# 查看/设置 API 配置 +memohome auth config [--set ] +``` + +### 用户管理 (`user`) 🔒 需要管理员权限 + +```bash +# 列出所有用户 +memohome user list + +# 创建用户 +memohome user create [-u username] [-p password] [-r role] + +# 获取用户详情 +memohome user get + +# 删除用户 +memohome user delete + +# 更新用户密码 +memohome user update-password [-p password] +``` + +### 模型管理 (`model`) + +```bash +# 列出所有模型 +memohome model list + +# 创建聊天模型配置 +memohome model create \ + -n "GPT-4" \ + -m "gpt-4" \ + -u "https://api.openai.com/v1" \ + -k "sk-xxx" \ + -c "openai" \ + -t "chat" + +# 创建 Embedding 模型配置 +memohome model create \ + -n "Text Embedding 3 Small" \ + -m "text-embedding-3-small" \ + -u "https://api.openai.com/v1" \ + -k "sk-xxx" \ + -c "openai" \ + -t "embedding" \ + -d 1536 + +# 获取模型详情 +memohome model get + +# 删除模型 +memohome model delete + +# 查看默认模型配置 +memohome model defaults +``` + +### Agent 对话 (`agent`) + +```bash +# 发送单条消息 +memohome agent chat "你好,介绍一下你自己" \ + [-t 60] \ + [-l Chinese] + +# 进入交互模式 +memohome agent interactive +memohome agent i # 简写 + +# 交互模式命令: +# /exit, /quit - 退出 +# /help - 帮助 +``` + +### 记忆管理 (`memory`) + +```bash +# 搜索记忆 +memohome memory search "关键词" [-l 10] + +# 添加记忆 +memohome memory add "这是一条记忆" + +# 查看消息历史 +memohome memory messages [-p 1] [-l 20] +memohome memory msg # 简写 + +# 按日期过滤消息 +memohome memory filter \ + -s 2024-01-01T00:00:00Z \ + -e 2024-12-31T23:59:59Z +``` + +### 设置管理 (`settings`) + +```bash +# 查看当前设置 +memohome settings get + +# 更新设置 +memohome settings set \ + [--language Chinese] \ + [--max-context-time 60] \ + [--chat-model ] \ + [--summary-model ] \ + [--embedding-model ] + +# 交互式设置向导 +memohome settings setup +``` + +### 日程管理 (`schedule`) + +```bash +# 列出所有定时任务 +memohome schedule list + +# 创建定时任务 +memohome schedule create \ + -t "每日提醒" \ + -d "每天早上9点的提醒" \ + -c "0 9 * * *" \ + -e + +# 获取任务详情 +memohome schedule get + +# 更新任务 +memohome schedule update \ + [-t title] \ + [-d description] \ + [-c cron] \ + [-e true/false] + +# 删除任务 +memohome schedule delete + +# 切换任务启用状态 +memohome schedule toggle +``` + +## 使用示例 + +### 完整工作流程 + +```bash +# 1. 登录 +memohome auth login -u admin -p password + +# 2. 创建模型配置(聊天模型) +memohome model create \ + -n "GPT-4" \ + -m "gpt-4" \ + -u "https://api.openai.com/v1" \ + -k "your-api-key" \ + -c "openai" \ + -t "chat" + +# 如果需要 embedding 模型 +memohome model create \ + -n "Text Embedding" \ + -m "text-embedding-3-small" \ + -u "https://api.openai.com/v1" \ + -k "your-api-key" \ + -c "openai" \ + -t "embedding" \ + -d 1536 + +# 3. 配置设置(使用模型ID) +memohome settings set \ + --language Chinese \ + --max-context-time 60 \ + --chat-model + +# 4. 开始对话 +memohome agent chat "你好" + +# 5. 进入交互模式 +memohome agent i +``` + +### Agent 交互模式示例 + +```bash +$ memohome agent interactive + +🤖 MemoHome Agent 交互模式 +输入 /exit 或 /quit 退出,输入 /help 查看帮助 + +You: 你好 +Agent: 你好!我是 MemoHome AI 助手,很高兴为你服务... + +You: 帮我总结一下今天的对话 +Agent: [🔧 使用工具: search_memory] +根据我们的对话记录... + +You: /exit +再见!👋 +``` + +### 搜索记忆示例 + +```bash +$ memohome memory search "项目计划" + +✓ 找到 3 条记忆 + +[1] 相似度: 92.50% +时间: 2024-01-15 10:30:00 +讨论了项目的初步计划和时间线... + +[2] 相似度: 85.20% +时间: 2024-01-14 15:20:00 +确定了项目的主要里程碑... + +[3] 相似度: 78.90% +时间: 2024-01-13 09:00:00 +项目启动会议记录... +``` + +## 配置文件 + +CLI 配置保存在 `~/.memohome/config.json`: + +```json +{ + "apiUrl": "http://localhost:7002", + "token": "your_jwt_token" +} +``` + +## 开发 + +```bash +# 开发模式(带热重载) +pnpm run dev + +# 直接运行 +pnpm run start +``` + +## 技术栈 + +- **Bun** - JavaScript 运行时 +- **Elysia Eden** - 类型安全的 HTTP 客户端 +- **Commander** - 命令行参数解析 +- **Chalk** - 终端颜色输出 +- **Inquirer** - 交互式提示 +- **Ora** - 加载动画 +- **Table** - 表格输出 + +## 注意事项 + +1. **认证要求**: 大部分命令需要先登录 +2. **管理员权限**: 用户管理命令需要管理员角色 +3. **模型配置**: 使用 Agent 前需要配置模型 +4. **流式响应**: Agent 对话使用 SSE 流式传输 + +## 相关文档 + +- [API 文档](../api/README.md) +- [认证系统](../api/AUTH_README.md) +- [Agent API](../api/AGENT_API.md) +- [用户管理](../api/USER_MANAGEMENT.md) + +## 许可证 + +MIT diff --git a/packages/cli/package.json b/packages/cli/package.json index 32e52c90..a3fa3e4a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,16 +1,39 @@ { "name": "@memohome/cli", "version": "1.0.0", - "description": "", + "description": "Command line interface for MemoHome API", "exports": { ".": "./src/index.ts" }, + "bin": { + "memohome": "./src/index.ts" + }, "scripts": { - "start": "bun run src/index.ts" + "start": "bun run src/index.ts", + "dev": "bun run --watch src/index.ts" }, "dependencies": { + "@elysiajs/eden": "^1.4.6", "@memohome/api": "workspace:*", - "@memohome/shared": "workspace:*" + "@memohome/shared": "workspace:*", + "elysia": "latest", + "commander": "^12.1.0", + "chalk": "^5.4.1", + "ora": "^8.1.1", + "inquirer": "^12.3.0", + "table": "^6.8.2", + "node-fetch": "^3.3.2" }, - "packageManager": "pnpm@10.27.0" + "devDependencies": { + "@types/node": "^22.10.5", + "bun-types": "latest", + "@types/bun": "latest" + }, + "packageManager": "pnpm@10.27.0", + "module": "src/index.ts", + "type": "module", + "private": true, + "peerDependencies": { + "typescript": "^5" + } } diff --git a/packages/cli/src/client.ts b/packages/cli/src/client.ts new file mode 100644 index 00000000..337d3d7b --- /dev/null +++ b/packages/cli/src/client.ts @@ -0,0 +1,29 @@ +import { treaty } from '@elysiajs/eden' +import { getApiUrl, getToken } from './config' + +// 使用动态导入来避免类型错误 +export function createClient() { + const apiUrl = getApiUrl() + const token = getToken() + + // Eden Treaty 配置 + const client = treaty(apiUrl, { + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return client as any +} + +export function requireAuth(): string { + const token = getToken() + if (!token) { + throw new Error('未登录,请先使用 "memohome auth login" 命令登录') + } + return token +} + +export { getApiUrl, getToken } + diff --git a/packages/cli/src/commands/agent.ts b/packages/cli/src/commands/agent.ts new file mode 100644 index 00000000..7a85cc7e --- /dev/null +++ b/packages/cli/src/commands/agent.ts @@ -0,0 +1,223 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import { requireAuth, getApiUrl, getToken } from '../client' + +export function agentCommands(program: Command) { + program + .command('chat ') + .description('与 AI Agent 对话') + .option('-t, --max-context-time ', '上下文加载时间(分钟)', '60') + .option('-l, --language ', '回复语言', 'Chinese') + .action(async (message, options) => { + try { + requireAuth() + const token = getToken()! + const apiUrl = getApiUrl() + + console.log(chalk.blue('🤖 Agent: ')) + + const response = await fetch(`${apiUrl}/agent/stream`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message, + maxContextLoadTime: parseInt(options.maxContextTime), + language: options.language, + }), + }) + + if (!response.ok) { + const errorData = await response.json() as { error?: string } + console.error(chalk.red('对话失败:'), errorData.error || '未知错误') + process.exit(1) + } + + const reader = response.body?.getReader() + const decoder = new TextDecoder() + + if (!reader) { + throw new Error('无法读取响应流') + } + + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value, { stream: true }) + buffer += chunk + + // 按行处理 + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6).trim() + + if (data === '[DONE]') { + console.log('\n') + return + } + + try { + const event = JSON.parse(data) + + if (event.type === 'text-delta' && event.text) { + process.stdout.write(event.text) + } else if (event.type === 'tool-call') { + console.log(chalk.dim(`\n[🔧 使用工具: ${event.toolName}]`)) + } else if (event.type === 'error') { + console.error(chalk.red('\n❌ 错误:'), event.error) + } + } catch { + // 跳过无法解析的JSON + } + } + } + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error(chalk.red('错误:'), message) + process.exit(1) + } + }) + + program + .command('interactive') + .alias('i') + .description('进入交互式对话模式') + .option('-t, --max-context-time ', '上下文加载时间(分钟)', '60') + .option('-l, --language ', '回复语言', 'Chinese') + .action(async (options) => { + try { + requireAuth() + const token = getToken()! + const apiUrl = getApiUrl() + + console.log(chalk.green.bold('🤖 MemoHome Agent 交互模式')) + console.log(chalk.dim('输入 /exit 或 /quit 退出,输入 /help 查看帮助\n')) + + const { createInterface } = await import('readline') + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + prompt: chalk.blue('You: '), + }) + + rl.prompt() + + rl.on('line', async (line: string) => { + const input = line.trim() + + if (input === '/exit' || input === '/quit') { + console.log(chalk.yellow('再见!👋')) + rl.close() + process.exit(0) + return + } + + if (input === '/help') { + console.log(chalk.green('\n可用命令:')) + console.log(chalk.dim(' /exit, /quit - 退出交互模式')) + console.log(chalk.dim(' /help - 显示帮助信息\n')) + rl.prompt() + return + } + + if (!input) { + rl.prompt() + return + } + + try { + console.log(chalk.green('Agent: ')) + + const response = await fetch(`${apiUrl}/agent/stream`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: input, + maxContextLoadTime: parseInt(options.maxContextTime), + language: options.language, + }), + }) + + if (!response.ok) { + const errorData = await response.json() as { error?: string } + console.error(chalk.red('对话失败:'), errorData.error || '未知错误') + rl.prompt() + return + } + + const reader = response.body?.getReader() + const decoder = new TextDecoder() + + if (!reader) { + throw new Error('无法读取响应流') + } + + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value, { stream: true }) + buffer += chunk + + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6).trim() + + if (data === '[DONE]') { + console.log('\n') + rl.prompt() + return + } + + try { + const event = JSON.parse(data) + + if (event.type === 'text-delta' && event.text) { + process.stdout.write(event.text) + } else if (event.type === 'tool-call') { + console.log(chalk.dim(`\n[🔧 ${event.toolName}]`)) + } else if (event.type === 'error') { + console.error(chalk.red('\n❌'), event.error) + } + } catch { + // 跳过无法解析的JSON + } + } + } + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error(chalk.red('错误:'), message) + rl.prompt() + } + }) + + rl.on('close', () => { + console.log(chalk.yellow('\n再见!👋')) + process.exit(0) + }) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error(chalk.red('错误:'), message) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/commands/auth.ts b/packages/cli/src/commands/auth.ts new file mode 100644 index 00000000..8f27cedd --- /dev/null +++ b/packages/cli/src/commands/auth.ts @@ -0,0 +1,143 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { createClient } from '../client' +import { setToken, clearToken, getToken, getApiUrl, setApiUrl } from '../config' +import { formatError } from '../utils' + +export function authCommands(program: Command) { + program + .command('login') + .description('登录到 MemoHome') + .option('-u, --username ', '用户名') + .option('-p, --password ', '密码') + .action(async (options) => { + try { + let username = options.username + let password = options.password + + if (!username || !password) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'username', + message: '请输入用户名:', + when: !username, + }, + { + type: 'password', + name: 'password', + message: '请输入密码:', + when: !password, + mask: '*', + }, + ]) + username = username || answers.username + password = password || answers.password + } + + const spinner = ora('正在登录...').start() + const client = createClient() + + const response = await client.auth.login.post({ + username, + password, + }) + + if (response.error) { + spinner.fail(chalk.red('登录失败')) + console.error(chalk.red(formatError(response.error.value))) + process.exit(1) + } + + const data = response.data as { success?: boolean; data?: { token?: string; user?: { username: string; role: string } } } | null + if (data?.success && data?.data?.token && data?.data?.user) { + setToken(data.data.token) + spinner.succeed(chalk.green('登录成功!')) + console.log(chalk.blue(`用户: ${data.data.user.username}`)) + console.log(chalk.blue(`角色: ${data.data.user.role}`)) + } else { + spinner.fail(chalk.red('登录失败')) + console.error(chalk.red('无效的响应格式')) + process.exit(1) + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error(chalk.red('登录错误:'), message) + process.exit(1) + } + }) + + program + .command('logout') + .description('登出当前用户') + .action(() => { + const token = getToken() + if (!token) { + console.log(chalk.yellow('当前未登录')) + return + } + + clearToken() + console.log(chalk.green('✓ 已登出')) + }) + + program + .command('whoami') + .description('查看当前登录用户') + .action(async () => { + try { + const token = getToken() + if (!token) { + console.log(chalk.yellow('当前未登录')) + console.log(chalk.dim('使用 "memohome auth login" 登录')) + return + } + + const spinner = ora('获取用户信息...').start() + const client = createClient() + + const response = await client.auth.me.get() + + if (response.error) { + spinner.fail(chalk.red('获取用户信息失败')) + console.error(chalk.red(formatError(response.error.value))) + process.exit(1) + } + + const data = response.data as { success?: boolean; data?: { username: string; role: string; id: string } } | null + if (data?.success && data?.data) { + spinner.succeed(chalk.green('已登录')) + console.log(chalk.blue(`用户名: ${data.data.username}`)) + console.log(chalk.blue(`角色: ${data.data.role}`)) + console.log(chalk.blue(`用户ID: ${data.data.id}`)) + } else { + spinner.fail(chalk.red('获取用户信息失败')) + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error(chalk.red('错误:'), message) + process.exit(1) + } + }) + + program + .command('config') + .description('查看或设置 API 配置') + .option('-s, --set ', '设置 API URL') + .action((options) => { + if (options.set) { + const url = options.set + setApiUrl(url) + console.log(chalk.green(`✓ API URL 已设置为: ${url}`)) + } else { + const apiUrl = getApiUrl() + const token = getToken() + console.log(chalk.blue('当前配置:')) + console.log(chalk.dim(`API URL: ${apiUrl}`)) + console.log(chalk.dim(`已登录: ${token ? '是' : '否'}`)) + } + }) +} + diff --git a/packages/cli/src/commands/debug.ts b/packages/cli/src/commands/debug.ts new file mode 100644 index 00000000..18a2eaa4 --- /dev/null +++ b/packages/cli/src/commands/debug.ts @@ -0,0 +1,55 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import ora from 'ora' +import { getApiUrl, getToken } from '../client' + +export function debugCommands(program: Command) { + program + .command('ping') + .description('测试 API 服务器连接') + .action(async () => { + const apiUrl = getApiUrl() + const token = getToken() + + console.log(chalk.blue('连接信息:')) + console.log(chalk.dim(` API URL: ${apiUrl}`)) + console.log(chalk.dim(` Token: ${token ? '已设置' : '未设置'}`)) + console.log() + + const spinner = ora('正在连接...').start() + + try { + // 尝试直接 fetch + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 5000) + + const response = await fetch(`${apiUrl}/`, { + signal: controller.signal, + headers: token ? { + 'Authorization': `Bearer ${token}` + } : {} + }) + + clearTimeout(timeoutId) + + if (response.ok) { + spinner.succeed(chalk.green('连接成功!')) + const text = await response.text() + console.log(chalk.dim('响应:'), text.substring(0, 100)) + } else { + spinner.fail(chalk.red(`连接失败: HTTP ${response.status}`)) + } + } catch (error) { + spinner.fail(chalk.red('连接失败')) + if (error instanceof Error) { + if (error.name === 'AbortError') { + console.error(chalk.yellow('连接超时 (5秒)')) + console.error(chalk.dim('请检查 API 服务器是否正在运行')) + } else { + console.error(chalk.red('错误:'), error.message) + } + } + } + }) +} + diff --git a/packages/cli/src/commands/memory.ts b/packages/cli/src/commands/memory.ts new file mode 100644 index 00000000..34a31237 --- /dev/null +++ b/packages/cli/src/commands/memory.ts @@ -0,0 +1,191 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import ora from 'ora' +import { table } from 'table' +import { createClient, requireAuth } from '../client' + +export function memoryCommands(program: Command) { + program + .command('search ') + .description('搜索记忆') + .option('-l, --limit ', '返回结果数量', '10') + .action(async (query, options) => { + try { + requireAuth() + const spinner = ora('搜索记忆...').start() + const client = createClient() + + const response = await client.memory.search.get({ + query: { + q: query, + limit: parseInt(options.limit), + }, + }) + + if (response.error) { + spinner.fail(chalk.red('搜索失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green(`找到 ${data.data.length} 条记忆`)) + + if (data.data.length === 0) { + console.log(chalk.yellow('未找到相关记忆')) + return + } + + data.data.forEach((item: any, index: number) => { + console.log() + console.log(chalk.blue(`[${index + 1}] 相似度: ${(item.similarity * 100).toFixed(2)}%`)) + console.log(chalk.dim(`时间: ${new Date(item.timestamp).toLocaleString('zh-CN')}`)) + console.log(chalk.white(item.content)) + }) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('add ') + .description('添加记忆') + .action(async (content) => { + try { + requireAuth() + const spinner = ora('添加记忆...').start() + const client = createClient() + + const response = await client.memory.post({ + content, + }) + + if (response.error) { + spinner.fail(chalk.red('添加记忆失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success) { + spinner.succeed(chalk.green('记忆已添加')) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('messages') + .alias('msg') + .description('获取消息历史') + .option('-p, --page ', '页码', '1') + .option('-l, --limit ', '每页数量', '20') + .action(async (options) => { + try { + requireAuth() + const spinner = ora('获取消息历史...').start() + const client = createClient() + + const response = await client.memory.message.get({ + query: { + page: parseInt(options.page), + limit: parseInt(options.limit), + }, + }) + + if (response.error) { + spinner.fail(chalk.red('获取消息失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + const { messages, pagination } = data.data + spinner.succeed(chalk.green(`消息历史 (${pagination.page}/${pagination.totalPages} 页)`)) + + if (messages.length === 0) { + console.log(chalk.yellow('暂无消息')) + return + } + + console.log(chalk.dim(`\n总计: ${pagination.total} 条消息\n`)) + + messages.forEach((msg: any) => { + const roleColor = msg.role === 'user' ? chalk.blue : chalk.green + const roleIcon = msg.role === 'user' ? '👤' : '🤖' + console.log(roleColor(`${roleIcon} ${msg.role.toUpperCase()}`)) + console.log(chalk.dim(new Date(msg.timestamp).toLocaleString('zh-CN'))) + console.log(chalk.white(msg.content)) + console.log() + }) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('filter') + .description('按日期范围过滤消息') + .option('-s, --start ', '开始日期 (ISO 8601)') + .option('-e, --end ', '结束日期 (ISO 8601)') + .action(async (options) => { + try { + requireAuth() + + if (!options.start || !options.end) { + console.error(chalk.red('请提供开始和结束日期')) + console.log(chalk.dim('示例: memohome memory filter -s 2024-01-01T00:00:00Z -e 2024-12-31T23:59:59Z')) + process.exit(1) + } + + const spinner = ora('过滤消息...').start() + const client = createClient() + + const response = await client.memory.message.filter.get({ + query: { + startDate: options.start, + endDate: options.end, + }, + }) + + if (response.error) { + spinner.fail(chalk.red('过滤消息失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green(`找到 ${data.data.length} 条消息`)) + + if (data.data.length === 0) { + console.log(chalk.yellow('未找到消息')) + return + } + + console.log() + + data.data.forEach((msg: any) => { + const roleColor = msg.role === 'user' ? chalk.blue : chalk.green + const roleIcon = msg.role === 'user' ? '👤' : '🤖' + console.log(roleColor(`${roleIcon} ${msg.role.toUpperCase()}`)) + console.log(chalk.dim(new Date(msg.timestamp).toLocaleString('zh-CN'))) + console.log(chalk.white(msg.content)) + console.log() + }) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/commands/model.ts b/packages/cli/src/commands/model.ts new file mode 100644 index 00000000..4e0041d7 --- /dev/null +++ b/packages/cli/src/commands/model.ts @@ -0,0 +1,400 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { table } from 'table' +import { createClient, requireAuth } from '../client' +import type { ApiResponse, Model } from '../types' +import { formatError } from '../utils' + +export function modelCommands(program: Command) { + program + .command('list') + .description('列出所有模型配置') + .action(async () => { + const spinner = ora('获取模型列表...').start() + try { + requireAuth() + const client = createClient() + + const response = await client.model.get() + + if (response.error) { + spinner.fail(chalk.red('获取模型列表失败')) + console.error(chalk.red(formatError(response.error.value))) + process.exit(1) + } + + // API 返回格式: { success, items, pagination } + const data = response.data as { success?: boolean; items?: Model[]; pagination?: unknown } | null + if (data?.success && data?.items) { + spinner.succeed(chalk.green('模型列表')) + + const models = data.items + if (models.length === 0) { + console.log(chalk.yellow('暂无模型配置')) + return + } + + const tableData = [ + ['ID', '名称', '模型ID', '类型', '客户端'], + ...models.map((item: unknown) => { + const modelItem = item as { id: string; model: Model } + return [ + modelItem.id.substring(0, 8) + '...', + modelItem.model.name || '-', + modelItem.model.modelId, + modelItem.model.type === 'embedding' ? chalk.yellow('embedding') : chalk.blue('chat'), + modelItem.model.clientType, + ] + }), + ] + + console.log(table(tableData)) + } + } catch (error) { + spinner.fail(chalk.red('操作失败')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + const { getApiUrl: getUrl } = await import('../config') + console.error(chalk.red('连接超时,请检查:')) + console.error(chalk.yellow(' 1. API 服务器是否正在运行')) + console.error(chalk.yellow(' 2. API 地址是否正确')) + console.error(chalk.dim(` 当前配置: ${getUrl()}`)) + } else { + console.error(chalk.red('错误:'), error.message) + } + } else { + console.error(chalk.red('错误:'), String(error)) + } + process.exit(1) + } + }) + + program + .command('create') + .description('创建模型配置') + .option('-n, --name ', '模型名称') + .option('-m, --model-id ', '模型ID') + .option('-u, --base-url ', 'API Base URL') + .option('-k, --api-key ', 'API Key') + .option('-c, --client-type ', '客户端类型 (openai/anthropic/google)') + .option('-t, --type ', '模型类型 (chat/embedding)', 'chat') + .option('-d, --dimensions ', 'Embedding 维度 (仅 embedding 类型需要)') + .action(async (options) => { + const spinner = ora('创建模型配置...').start() + try { + requireAuth() + + let { name, modelId, baseUrl, apiKey, clientType, type, dimensions } = options + + if (!name || !modelId || !baseUrl || !apiKey || !clientType) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: '模型名称:', + when: !name, + }, + { + type: 'input', + name: 'modelId', + message: '模型ID (如 gpt-4 或 text-embedding-3-small):', + when: !modelId, + }, + { + type: 'input', + name: 'baseUrl', + message: 'API Base URL:', + default: 'https://api.openai.com/v1', + when: !baseUrl, + }, + { + type: 'password', + name: 'apiKey', + message: 'API Key:', + when: !apiKey, + mask: '*', + }, + { + type: 'list', + name: 'clientType', + message: '客户端类型:', + choices: ['openai', 'anthropic', 'google'], + default: 'openai', + when: !clientType, + }, + { + type: 'list', + name: 'type', + message: '模型类型:', + choices: ['chat', 'embedding'], + default: 'chat', + when: !type, + }, + ]) + + name = name || answers.name + modelId = modelId || answers.modelId + baseUrl = baseUrl || answers.baseUrl + apiKey = apiKey || answers.apiKey + clientType = clientType || answers.clientType + type = type || answers.type + } + + // 如果是 embedding 类型,需要 dimensions + if (type === 'embedding' && !dimensions) { + const answer = await inquirer.prompt([ + { + type: 'number', + name: 'dimensions', + message: 'Embedding 维度 (如 1536):', + validate: (value: number) => { + if (value > 0) return true + return '维度必须是正整数' + }, + }, + ]) + dimensions = answer.dimensions + } + + spinner.text = '创建模型配置...' + const client = createClient() + + const payload: Record = { + name, + modelId, + baseUrl, + apiKey, + clientType, + type, + } + + // 如果是 embedding 类型,添加 dimensions + if (type === 'embedding') { + if (!dimensions) { + console.error(chalk.red('Embedding 模型需要指定 dimensions')) + process.exit(1) + } + payload.dimensions = typeof dimensions === 'number' ? dimensions : parseInt(dimensions) + } + + const response = await client.model.post(payload) + + if (response.error) { + spinner.fail(chalk.red('创建模型配置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + spinner.succeed(chalk.green('模型配置创建成功')) + console.log(chalk.blue(`名称: ${data.data.name}`)) + console.log(chalk.blue(`模型ID: ${data.data.modelId}`)) + console.log(chalk.blue(`类型: ${data.data.type || 'chat'}`)) + if (data.data.type === 'embedding' && data.data.dimensions) { + console.log(chalk.blue(`维度: ${data.data.dimensions}`)) + } + console.log(chalk.blue(`ID: ${data.data.id}`)) + } + } catch (error) { + spinner.fail(chalk.red('操作失败')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + const { getApiUrl: getUrl } = await import('../config') + console.error(chalk.red('连接超时,请检查:')) + console.error(chalk.yellow(' 1. API 服务器是否正在运行')) + console.error(chalk.yellow(' 2. API 地址是否正确')) + console.error(chalk.dim(` 当前配置: ${getUrl()}`)) + } else { + console.error(chalk.red('错误:'), error.message) + } + } else { + console.error(chalk.red('错误:'), String(error)) + } + process.exit(1) + } + }) + + program + .command('delete ') + .description('删除模型配置') + .action(async (id) => { + let spinner: ReturnType | undefined + try { + requireAuth() + + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: chalk.yellow(`确定要删除模型配置 ${id} 吗?`), + default: false, + }, + ]) + + if (!confirm) { + console.log(chalk.yellow('已取消')) + return + } + + spinner = ora('删除模型配置...').start() + const client = createClient() + + const response = await client.model({ id }).delete() + + if (response.error) { + spinner.fail(chalk.red('删除模型配置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + if (spinner) spinner.succeed(chalk.green('模型配置已删除')) + } catch (error) { + if (spinner) spinner.fail(chalk.red('操作失败')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + const { getApiUrl: getUrl } = await import('../config') + console.error(chalk.red('连接超时,请检查:')) + console.error(chalk.yellow(' 1. API 服务器是否正在运行')) + console.error(chalk.yellow(' 2. API 地址是否正确')) + console.error(chalk.dim(` 当前配置: ${getUrl()}`)) + } else { + console.error(chalk.red('错误:'), error.message) + } + } else { + console.error(chalk.red('错误:'), String(error)) + } + process.exit(1) + } + }) + + program + .command('get ') + .description('获取模型配置详情') + .action(async (id) => { + const spinner = ora('获取模型配置...').start() + try { + requireAuth() + const client = createClient() + + const response = await client.model({ id }).get() + + if (response.error) { + spinner.fail(chalk.red('获取模型配置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + const model = data.data + spinner.succeed(chalk.green('模型配置')) + console.log(chalk.blue(`ID: ${model.id}`)) + console.log(chalk.blue(`名称: ${model.name}`)) + console.log(chalk.blue(`模型ID: ${model.modelId}`)) + console.log(chalk.blue(`类型: ${model.type || 'chat'}`)) + if (model.type === 'embedding' && model.dimensions) { + console.log(chalk.blue(`维度: ${model.dimensions}`)) + } + console.log(chalk.blue(`Base URL: ${model.baseUrl}`)) + console.log(chalk.blue(`客户端类型: ${model.clientType}`)) + console.log(chalk.blue(`创建时间: ${new Date(model.createdAt).toLocaleString('zh-CN')}`)) + } + } catch (error) { + spinner.fail(chalk.red('操作失败')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + const { getApiUrl: getUrl } = await import('../config') + console.error(chalk.red('连接超时,请检查:')) + console.error(chalk.yellow(' 1. API 服务器是否正在运行')) + console.error(chalk.yellow(' 2. API 地址是否正确')) + console.error(chalk.dim(` 当前配置: ${getUrl()}`)) + } else { + console.error(chalk.red('错误:'), error.message) + } + } else { + console.error(chalk.red('错误:'), String(error)) + } + process.exit(1) + } + }) + + program + .command('defaults') + .description('查看默认模型配置') + .action(async () => { + const spinner = ora('获取默认模型配置...').start() + try { + requireAuth() + const client = createClient() + + const [chatRes, summaryRes, embeddingRes] = await Promise.all([ + client.model.chat.default.get(), + client.model.summary.default.get(), + client.model.embedding.default.get(), + ]) + + spinner.stop() + + console.log(chalk.green.bold('默认模型配置:')) + console.log() + + // Chat Model + const chatData = chatRes.data as ApiResponse | null + if (chatData?.success && chatData.data) { + const model = chatData.data + console.log(chalk.blue('💬 聊天模型:')) + console.log(chalk.dim(` 名称: ${model.name}`)) + console.log(chalk.dim(` 模型ID: ${model.modelId}`)) + console.log(chalk.dim(` ID: ${model.id}`)) + } else { + console.log(chalk.yellow('💬 聊天模型: 未配置')) + } + console.log() + + // Summary Model + const summaryData = summaryRes.data as ApiResponse | null + if (summaryData?.success && summaryData.data) { + const model = summaryData.data + console.log(chalk.blue('📝 摘要模型:')) + console.log(chalk.dim(` 名称: ${model.name}`)) + console.log(chalk.dim(` 模型ID: ${model.modelId}`)) + console.log(chalk.dim(` ID: ${model.id}`)) + } else { + console.log(chalk.yellow('📝 摘要模型: 未配置')) + } + console.log() + + // Embedding Model + const embeddingData = embeddingRes.data as ApiResponse | null + if (embeddingData?.success && embeddingData.data) { + const model = embeddingData.data + console.log(chalk.blue('🔍 嵌入模型:')) + console.log(chalk.dim(` 名称: ${model.name}`)) + console.log(chalk.dim(` 模型ID: ${model.modelId}`)) + console.log(chalk.dim(` ID: ${model.id}`)) + } else { + console.log(chalk.yellow('🔍 嵌入模型: 未配置')) + } + } catch (error) { + spinner.fail(chalk.red('操作失败')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + const { getApiUrl: getUrl } = await import('../config') + console.error(chalk.red('连接超时,请检查:')) + console.error(chalk.yellow(' 1. API 服务器是否正在运行')) + console.error(chalk.yellow(' 2. API 地址是否正确')) + console.error(chalk.dim(` 当前配置: ${getUrl()}`)) + } else { + console.error(chalk.red('错误:'), error.message) + } + } else { + console.error(chalk.red('错误:'), String(error)) + } + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/commands/schedule.ts b/packages/cli/src/commands/schedule.ts new file mode 100644 index 00000000..d960dba9 --- /dev/null +++ b/packages/cli/src/commands/schedule.ts @@ -0,0 +1,305 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { table } from 'table' +import { createClient, requireAuth } from '../client' + +export function scheduleCommands(program: Command) { + program + .command('list') + .description('列出所有定时任务') + .action(async () => { + try { + requireAuth() + const spinner = ora('获取定时任务列表...').start() + const client = createClient() + + const response = await client.schedule.get() + + if (response.error) { + spinner.fail(chalk.red('获取定时任务列表失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green('定时任务列表')) + + const schedules = data.data + if (schedules.length === 0) { + console.log(chalk.yellow('暂无定时任务')) + return + } + + const tableData = [ + ['ID', '标题', 'Cron', '启用', '创建时间'], + ...schedules.map((schedule: any) => [ + schedule.id.substring(0, 8) + '...', + schedule.title, + schedule.cronExpression, + schedule.enabled ? chalk.green('是') : chalk.red('否'), + new Date(schedule.createdAt).toLocaleString('zh-CN'), + ]), + ] + + console.log(table(tableData)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('create') + .description('创建定时任务') + .option('-t, --title ', '任务标题') + .option('-d, --description <description>', '任务描述') + .option('-c, --cron <expression>', 'Cron 表达式') + .option('-e, --enabled', '启用任务', false) + .action(async (options) => { + try { + requireAuth() + + let { title, description, cron, enabled } = options + + if (!title || !cron) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'title', + message: '任务标题:', + when: !title, + }, + { + type: 'input', + name: 'description', + message: '任务描述 (可选):', + when: !description, + }, + { + type: 'input', + name: 'cron', + message: 'Cron 表达式 (如: 0 9 * * *):', + when: !cron, + }, + { + type: 'confirm', + name: 'enabled', + message: '启用任务?', + default: false, + when: enabled === undefined, + }, + ]) + + title = title || answers.title + description = description || answers.description + cron = cron || answers.cron + enabled = enabled !== undefined ? enabled : answers.enabled + } + + const spinner = ora('创建定时任务...').start() + const client = createClient() + + const payload: any = { + title, + cronExpression: cron, + enabled, + } + + if (description) { + payload.description = description + } + + const response = await client.schedule.post(payload) + + if (response.error) { + spinner.fail(chalk.red('创建定时任务失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green('定时任务创建成功')) + console.log(chalk.blue(`标题: ${data.data.title}`)) + console.log(chalk.blue(`Cron: ${data.data.cronExpression}`)) + console.log(chalk.blue(`ID: ${data.data.id}`)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('get <id>') + .description('获取定时任务详情') + .action(async (id) => { + try { + requireAuth() + const spinner = ora('获取定时任务详情...').start() + const client = createClient() + + const response = await client.schedule({ id }).get() + + if (response.error) { + spinner.fail(chalk.red('获取定时任务失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + const schedule = data.data + spinner.succeed(chalk.green('定时任务详情')) + console.log(chalk.blue(`ID: ${schedule.id}`)) + console.log(chalk.blue(`标题: ${schedule.title}`)) + if (schedule.description) { + console.log(chalk.blue(`描述: ${schedule.description}`)) + } + console.log(chalk.blue(`Cron: ${schedule.cronExpression}`)) + console.log( + chalk.blue(`启用: ${schedule.enabled ? chalk.green('是') : chalk.red('否')}`) + ) + console.log( + chalk.blue(`创建时间: ${new Date(schedule.createdAt).toLocaleString('zh-CN')}`) + ) + console.log( + chalk.blue(`更新时间: ${new Date(schedule.updatedAt).toLocaleString('zh-CN')}`) + ) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('update <id>') + .description('更新定时任务') + .option('-t, --title <title>', '任务标题') + .option('-d, --description <description>', '任务描述') + .option('-c, --cron <expression>', 'Cron 表达式') + .option('-e, --enabled <boolean>', '启用任务 (true/false)') + .action(async (id, options) => { + try { + requireAuth() + + const updates: any = {} + + if (options.title) updates.title = options.title + if (options.description) updates.description = options.description + if (options.cron) updates.cronExpression = options.cron + if (options.enabled !== undefined) { + updates.enabled = options.enabled === 'true' || options.enabled === true + } + + if (Object.keys(updates).length === 0) { + console.log(chalk.yellow('未提供任何更新参数')) + return + } + + const spinner = ora('更新定时任务...').start() + const client = createClient() + + const response = await client.schedule({ id }).put(updates) + + if (response.error) { + spinner.fail(chalk.red('更新定时任务失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + spinner.succeed(chalk.green('定时任务已更新')) + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('delete <id>') + .description('删除定时任务') + .action(async (id) => { + try { + requireAuth() + + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: chalk.yellow(`确定要删除定时任务 ${id} 吗?`), + default: false, + }, + ]) + + if (!confirm) { + console.log(chalk.yellow('已取消')) + return + } + + const spinner = ora('删除定时任务...').start() + const client = createClient() + + const response = await client.schedule({ id }).delete() + + if (response.error) { + spinner.fail(chalk.red('删除定时任务失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + spinner.succeed(chalk.green('定时任务已删除')) + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('toggle <id>') + .description('切换定时任务启用状态') + .action(async (id) => { + try { + requireAuth() + const spinner = ora('切换任务状态...').start() + const client = createClient() + + // 首先获取当前状态 + const getResponse = await client.schedule({ id }).get() + + if (getResponse.error) { + spinner.fail(chalk.red('获取任务失败')) + console.error(chalk.red(getResponse.error.value)) + process.exit(1) + } + + const getData = getResponse.data as any + if (getData?.success && getData?.data) { + const currentEnabled = getData.data.enabled + + // 更新状态 + const updateResponse = await client.schedule({ id }).put({ + enabled: !currentEnabled, + }) + + if (updateResponse.error) { + spinner.fail(chalk.red('更新任务失败')) + console.error(chalk.red(updateResponse.error.value)) + process.exit(1) + } + + spinner.succeed( + chalk.green(`任务已${!currentEnabled ? '启用' : '禁用'}`) + ) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/commands/settings.ts b/packages/cli/src/commands/settings.ts new file mode 100644 index 00000000..790cdeb8 --- /dev/null +++ b/packages/cli/src/commands/settings.ts @@ -0,0 +1,181 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { createClient, requireAuth } from '../client' + +export function settingsCommands(program: Command) { + program + .command('get') + .description('获取当前用户设置') + .action(async () => { + try { + requireAuth() + const spinner = ora('获取设置...').start() + const client = createClient() + + const response = await client.settings.get() + + if (response.error) { + spinner.fail(chalk.red('获取设置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + const settings = data.data + spinner.succeed(chalk.green('当前设置')) + console.log() + console.log(chalk.blue('🎯 Agent 配置:')) + console.log(chalk.dim(` 语言: ${settings.language || '未设置'}`)) + console.log(chalk.dim(` 上下文加载时间: ${settings.maxContextLoadTime || '未设置'} 分钟`)) + console.log() + console.log(chalk.blue('🤖 默认模型:')) + console.log(chalk.dim(` 聊天模型ID: ${settings.defaultChatModel || '未设置'}`)) + console.log(chalk.dim(` 摘要模型ID: ${settings.defaultSummaryModel || '未设置'}`)) + console.log(chalk.dim(` 嵌入模型ID: ${settings.defaultEmbeddingModel || '未设置'}`)) + console.log() + console.log(chalk.blue('📊 其他:')) + console.log(chalk.dim(` 用户ID: ${settings.userId}`)) + console.log(chalk.dim(` 创建时间: ${new Date(settings.createdAt).toLocaleString('zh-CN')}`)) + console.log(chalk.dim(` 更新时间: ${new Date(settings.updatedAt).toLocaleString('zh-CN')}`)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('set') + .description('更新用户设置') + .option('--language <language>', '首选语言') + .option('--max-context-time <minutes>', '上下文加载时间(分钟)') + .option('--chat-model <id>', '默认聊天模型ID') + .option('--summary-model <id>', '默认摘要模型ID') + .option('--embedding-model <id>', '默认嵌入模型ID') + .action(async (options) => { + try { + requireAuth() + + const updates: any = {} + + if (options.language) updates.language = options.language + if (options.maxContextTime) + updates.maxContextLoadTime = parseInt(options.maxContextTime) + if (options.chatModel) updates.defaultChatModel = options.chatModel + if (options.summaryModel) updates.defaultSummaryModel = options.summaryModel + if (options.embeddingModel) + updates.defaultEmbeddingModel = options.embeddingModel + + if (Object.keys(updates).length === 0) { + console.log(chalk.yellow('未提供任何更新参数')) + console.log(chalk.dim('\n可用选项:')) + console.log(chalk.dim(' --language <language>')) + console.log(chalk.dim(' --max-context-time <minutes>')) + console.log(chalk.dim(' --chat-model <id>')) + console.log(chalk.dim(' --summary-model <id>')) + console.log(chalk.dim(' --embedding-model <id>')) + return + } + + const spinner = ora('更新设置...').start() + const client = createClient() + + const response = await client.settings.put(updates) + + if (response.error) { + spinner.fail(chalk.red('更新设置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success) { + spinner.succeed(chalk.green('设置已更新')) + console.log() + console.log(chalk.blue('更新的设置:')) + Object.entries(updates).forEach(([key, value]) => { + console.log(chalk.dim(` ${key}: ${value}`)) + }) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('setup') + .description('交互式设置向导') + .action(async () => { + try { + requireAuth() + + console.log(chalk.green.bold('\n🎨 设置向导\n')) + + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'language', + message: '首选语言:', + default: 'Chinese', + }, + { + type: 'number', + name: 'maxContextLoadTime', + message: '上下文加载时间(分钟):', + default: 60, + validate: (value) => { + const num = parseInt(value) + if (num < 1 || num > 1440) { + return '请输入 1-1440 之间的数字' + } + return true + }, + }, + { + type: 'input', + name: 'defaultChatModel', + message: '默认聊天模型ID (留空跳过):', + }, + { + type: 'input', + name: 'defaultSummaryModel', + message: '默认摘要模型ID (留空跳过):', + }, + { + type: 'input', + name: 'defaultEmbeddingModel', + message: '默认嵌入模型ID (留空跳过):', + }, + ]) + + // 过滤掉空值 + const updates: any = {} + Object.entries(answers).forEach(([key, value]) => { + if (value) { + updates[key] = value + } + }) + + const spinner = ora('保存设置...').start() + const client = createClient() + + const response = await client.settings.put(updates) + + if (response.error) { + spinner.fail(chalk.red('保存设置失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + spinner.succeed(chalk.green('设置已保存')) + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/commands/user.ts b/packages/cli/src/commands/user.ts new file mode 100644 index 00000000..b4727805 --- /dev/null +++ b/packages/cli/src/commands/user.ts @@ -0,0 +1,238 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { table } from 'table' +import { createClient, requireAuth } from '../client' + +export function userCommands(program: Command) { + program + .command('list') + .description('列出所有用户 (需要管理员权限)') + .action(async () => { + try { + requireAuth() + const spinner = ora('获取用户列表...').start() + const client = createClient() + + const response = await client.user.get() + + if (response.error) { + spinner.fail(chalk.red('获取用户列表失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green('用户列表')) + + const users = data.data + if (users.length === 0) { + console.log(chalk.yellow('暂无用户')) + return + } + + const tableData = [ + ['ID', '用户名', '角色', '创建时间'], + ...users.map((user: any) => [ + user.id, + user.username, + user.role === 'admin' ? chalk.red('管理员') : chalk.blue('用户'), + new Date(user.createdAt).toLocaleString('zh-CN'), + ]), + ] + + console.log(table(tableData)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('create') + .description('创建新用户 (需要管理员权限)') + .option('-u, --username <username>', '用户名') + .option('-p, --password <password>', '密码') + .option('-r, --role <role>', '角色 (user/admin)', 'user') + .action(async (options) => { + try { + requireAuth() + + let username = options.username + let password = options.password + let role = options.role + + if (!username || !password) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'username', + message: '用户名:', + when: !username, + }, + { + type: 'password', + name: 'password', + message: '密码:', + when: !password, + mask: '*', + }, + { + type: 'list', + name: 'role', + message: '角色:', + choices: ['user', 'admin'], + default: 'user', + when: !role, + }, + ]) + username = username || answers.username + password = password || answers.password + role = role || answers.role + } + + const spinner = ora('创建用户...').start() + const client = createClient() + + const response = await client.user.post({ + username, + password, + role, + }) + + if (response.error) { + spinner.fail(chalk.red('创建用户失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + spinner.succeed(chalk.green('用户创建成功')) + console.log(chalk.blue(`用户名: ${data.data.username}`)) + console.log(chalk.blue(`角色: ${data.data.role}`)) + console.log(chalk.blue(`ID: ${data.data.id}`)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('delete <id>') + .description('删除用户 (需要管理员权限)') + .action(async (id) => { + try { + requireAuth() + + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: chalk.yellow(`确定要删除用户 ${id} 吗?`), + default: false, + }, + ]) + + if (!confirm) { + console.log(chalk.yellow('已取消')) + return + } + + const spinner = ora('删除用户...').start() + const client = createClient() + + const response = await client.user({ id }).delete() + + if (response.error) { + spinner.fail(chalk.red('删除用户失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + spinner.succeed(chalk.green('用户已删除')) + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('get <id>') + .description('获取用户详情') + .action(async (id) => { + try { + requireAuth() + const spinner = ora('获取用户信息...').start() + const client = createClient() + + const response = await client.user({ id }).get() + + if (response.error) { + spinner.fail(chalk.red('获取用户信息失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + const data = response.data as any + if (data?.success && data?.data) { + const user = data.data + spinner.succeed(chalk.green('用户信息')) + console.log(chalk.blue(`ID: ${user.id}`)) + console.log(chalk.blue(`用户名: ${user.username}`)) + console.log(chalk.blue(`角色: ${user.role}`)) + console.log(chalk.blue(`创建时间: ${new Date(user.createdAt).toLocaleString('zh-CN')}`)) + } + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) + + program + .command('update-password <id>') + .description('更新用户密码 (需要管理员权限)') + .option('-p, --password <password>', '新密码') + .action(async (id, options) => { + try { + requireAuth() + + let password = options.password + + if (!password) { + const answers = await inquirer.prompt([ + { + type: 'password', + name: 'password', + message: '新密码:', + mask: '*', + }, + ]) + password = answers.password + } + + const spinner = ora('更新密码...').start() + const client = createClient() + + const response = await client.user({ id }).password.patch({ + password, + }) + + if (response.error) { + spinner.fail(chalk.red('更新密码失败')) + console.error(chalk.red(response.error.value)) + process.exit(1) + } + + spinner.succeed(chalk.green('密码已更新')) + } catch (error: any) { + console.error(chalk.red('错误:'), error.message) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts new file mode 100644 index 00000000..470bcc9f --- /dev/null +++ b/packages/cli/src/config.ts @@ -0,0 +1,72 @@ +import { homedir } from 'os' +import { join } from 'path' +import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs' + +const CONFIG_DIR = join(homedir(), '.memohome') +const CONFIG_FILE = join(CONFIG_DIR, 'config.json') + +export interface Config { + apiUrl: string + token?: string +} + +const DEFAULT_CONFIG: Config = { + apiUrl: process.env.API_BASE_URL || 'http://localhost:7002', +} + +export function ensureConfigDir() { + if (!existsSync(CONFIG_DIR)) { + mkdirSync(CONFIG_DIR, { recursive: true }) + } +} + +export function loadConfig(): Config { + ensureConfigDir() + + if (!existsSync(CONFIG_FILE)) { + saveConfig(DEFAULT_CONFIG) + return DEFAULT_CONFIG + } + + try { + const data = readFileSync(CONFIG_FILE, 'utf-8') + return { ...DEFAULT_CONFIG, ...JSON.parse(data) } + } catch { + console.error('Failed to load config, using defaults') + return DEFAULT_CONFIG + } +} + +export function saveConfig(config: Config) { + ensureConfigDir() + writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)) +} + +export function getToken(): string | null { + const config = loadConfig() + return config.token || null +} + +export function setToken(token: string) { + const config = loadConfig() + config.token = token + saveConfig(config) +} + +export function clearToken() { + const config = loadConfig() + delete config.token + saveConfig(config) +} + +export function getApiUrl(): string { + const config = loadConfig() + return config.apiUrl +} + +export function setApiUrl(url: string) { + const config = loadConfig() + config.apiUrl = url + saveConfig(config) +} + diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e69de29b..890f5685 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -0,0 +1,54 @@ +#!/usr/bin/env bun + +import { Command } from 'commander' +import chalk from 'chalk' +import { authCommands } from './commands/auth' +import { userCommands } from './commands/user' +import { modelCommands } from './commands/model' +import { agentCommands } from './commands/agent' +import { memoryCommands } from './commands/memory' +import { settingsCommands } from './commands/settings' +import { scheduleCommands } from './commands/schedule' +import { debugCommands } from './commands/debug' + +const program = new Command() + +program + .name('memohome') + .description(chalk.bold.blue('🏠 MemoHome CLI - 智能记忆管理助手')) + .version('1.0.0') + +// 认证命令 +const auth = program.command('auth').description('用户认证管理') +authCommands(auth) + +// 用户管理命令 +const user = program.command('user').description('用户管理 (需要管理员权限)') +userCommands(user) + +// 模型管理命令 +const model = program.command('model').description('AI 模型配置管理') +modelCommands(model) + +// Agent 对话命令 +const agent = program.command('agent').description('与 AI Agent 对话') +agentCommands(agent) + +// 记忆管理命令 +const memory = program.command('memory').description('记忆管理') +memoryCommands(memory) + +// 设置管理命令 +const settings = program.command('settings').description('用户设置管理') +settingsCommands(settings) + +// 日程管理命令 +const schedule = program.command('schedule').description('日程管理') +scheduleCommands(schedule) + +// 调试命令 +const debug = program.command('debug').description('调试工具') +debugCommands(debug) + +program.parse() + diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts new file mode 100644 index 00000000..b3504236 --- /dev/null +++ b/packages/cli/src/types.ts @@ -0,0 +1,71 @@ +// API 响应类型定义 + +export interface ApiResponse<T = unknown> { + success?: boolean + data?: T + error?: string +} + +export interface User { + id: string + username: string + role: string + createdAt: string +} + +export interface Model { + id: string + name: string + modelId: string + baseUrl: string + apiKey?: string + clientType: string + type?: 'chat' | 'embedding' + dimensions?: number + createdAt: string + updatedAt?: string +} + +export interface Memory { + content: string + timestamp: string + similarity?: number +} + +export interface Message { + role: 'user' | 'assistant' + content: string + timestamp: string +} + +export interface MessageListResponse { + messages: Message[] + pagination: { + page: number + limit: number + total: number + totalPages: number + } +} + +export interface Settings { + userId: string + language?: string + maxContextLoadTime?: number + defaultChatModel?: string + defaultSummaryModel?: string + defaultEmbeddingModel?: string + createdAt: string + updatedAt: string +} + +export interface Schedule { + id: string + title: string + description?: string + cronExpression: string + enabled: boolean + createdAt: string + updatedAt: string +} + diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts new file mode 100644 index 00000000..5666b2b4 --- /dev/null +++ b/packages/cli/src/utils.ts @@ -0,0 +1,63 @@ +import chalk from 'chalk' + +/** + * 格式化 API 错误信息 + */ +export function formatError(error: unknown): string { + if (error === null || error === undefined) { + return '未知错误' + } + + if (typeof error === 'string') { + return error + } + + if (typeof error === 'object') { + // 尝试提取常见的错误字段 + const errorObj = error as Record<string, unknown> + + if ('message' in errorObj && typeof errorObj.message === 'string') { + return errorObj.message + } + + if ('error' in errorObj && typeof errorObj.error === 'string') { + return errorObj.error + } + + // 如果有 status 和 statusText + if ('status' in errorObj && 'statusText' in errorObj) { + return `${errorObj.status} ${errorObj.statusText}` + } + + // 否则返回格式化的 JSON + try { + return JSON.stringify(error, null, 2) + } catch { + return String(error) + } + } + + return String(error) +} + +/** + * 打印错误并退出 + */ +export function exitWithError(message: string, error?: unknown): never { + console.error(chalk.red(message)) + if (error) { + console.error(chalk.red(formatError(error))) + } + process.exit(1) +} + +/** + * 处理 Eden Treaty 响应错误 + */ +export function handleApiError(response: { error?: { value: unknown } }, defaultMessage = '操作失败'): never { + if (response.error) { + exitWithError(defaultMessage, response.error.value) + } + exitWithError(defaultMessage, '未知错误') +} + diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000..280428ea --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "types": ["bun-types"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "jsx": "react-jsx", + "outDir": "./dist", + "rootDir": "./src", + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca55c7d4..a7f26b8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,46 @@ importers: specifier: latest version: 1.3.5 + packages/cli: + dependencies: + '@elysiajs/eden': + specifier: ^1.4.6 + version: 1.4.6(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)) + '@memohome/api': + specifier: workspace:* + version: link:../api + '@memohome/shared': + specifier: workspace:* + version: link:../shared + chalk: + specifier: ^5.4.1 + version: 5.6.2 + commander: + specifier: ^12.1.0 + version: 12.1.0 + elysia: + specifier: latest + version: 1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3) + inquirer: + specifier: ^12.3.0 + version: 12.11.1(@types/node@22.19.5) + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + ora: + specifier: ^8.1.1 + version: 8.2.0 + table: + specifier: ^6.8.2 + version: 6.9.0 + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.19.5 + bun-types: + specifier: latest + version: 1.3.5 + packages/db: dependencies: '@memohome/shared': @@ -189,18 +229,6 @@ importers: specifier: ^0.4.1 version: 0.4.1(zod-to-json-schema@3.25.1(zod@4.3.5))(zod@4.3.5) - packages/schedule: - dependencies: - '@memohome/db': - specifier: workspace:* - version: link:../db - '@memohome/shared': - specifier: workspace:* - version: link:../shared - drizzle-orm: - specifier: ^0.45.1 - version: 0.45.1(@cloudflare/workers-types@4.20260109.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.5)(pg@8.16.3)(sqlite3@5.1.7) - packages/shared: {} packages/ui: @@ -1142,6 +1170,140 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@internationalized/date@3.10.1': resolution: {integrity: sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==} @@ -1629,6 +1791,9 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@22.19.5': + resolution: {integrity: sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==} + '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} @@ -2042,6 +2207,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2137,6 +2306,13 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} @@ -2158,6 +2334,18 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cloudflare@4.5.0: resolution: {integrity: sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ==} @@ -2184,6 +2372,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -2425,6 +2617,9 @@ packages: typescript: optional: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2731,6 +2926,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2835,6 +3034,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2875,6 +3078,15 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inquirer@12.11.1: + resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -2908,6 +3120,10 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} @@ -2915,6 +3131,14 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-what@5.5.0: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} @@ -3151,9 +3375,16 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3226,6 +3457,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -3308,6 +3543,10 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3408,6 +3647,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -3431,6 +3674,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -3663,6 +3910,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -3692,6 +3943,10 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} + run-async@4.0.6: + resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} + engines: {node: '>=0.12.0'} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -3757,6 +4012,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -3808,6 +4067,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -3820,6 +4083,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -3859,6 +4126,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -3961,6 +4232,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -4246,6 +4520,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -4317,6 +4595,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -5036,6 +5318,131 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@22.19.5)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.5) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/confirm@5.1.21(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/core@10.3.2(@types/node@22.19.5)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.5) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/editor@4.2.23(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/expand@4.0.23(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/external-editor@1.0.3(@types/node@22.19.5)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/number@3.0.23(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/password@4.0.23(@types/node@22.19.5)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/prompts@7.10.1(@types/node@22.19.5)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.19.5) + '@inquirer/confirm': 5.1.21(@types/node@22.19.5) + '@inquirer/editor': 4.2.23(@types/node@22.19.5) + '@inquirer/expand': 4.0.23(@types/node@22.19.5) + '@inquirer/input': 4.3.1(@types/node@22.19.5) + '@inquirer/number': 3.0.23(@types/node@22.19.5) + '@inquirer/password': 4.0.23(@types/node@22.19.5) + '@inquirer/rawlist': 4.1.11(@types/node@22.19.5) + '@inquirer/search': 3.2.2(@types/node@22.19.5) + '@inquirer/select': 4.4.2(@types/node@22.19.5) + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/rawlist@4.1.11(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/search@3.2.2(@types/node@22.19.5)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.5) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/select@4.4.2(@types/node@22.19.5)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.5) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.5 + + '@inquirer/type@3.0.10(@types/node@22.19.5)': + optionalDependencies: + '@types/node': 22.19.5 + '@internationalized/date@3.10.1': dependencies: '@swc/helpers': 0.5.18 @@ -5513,6 +5920,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@22.19.5': + dependencies: + undici-types: 6.21.0 + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 @@ -5523,7 +5934,7 @@ snapshots: '@types/pg@8.16.0': dependencies: - '@types/node': 25.0.3 + '@types/node': 24.10.4 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -6074,6 +6485,8 @@ snapshots: assertion-error@2.0.1: {} + astral-regex@2.0.0: {} + asynckit@0.4.0: {} axios@1.7.7: @@ -6193,6 +6606,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + + chardet@2.1.1: {} + charenc@0.0.2: {} chownr@1.1.4: {} @@ -6208,6 +6625,14 @@ snapshots: clean-stack@2.2.0: optional: true + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + cloudflare@4.5.0(encoding@0.1.13): dependencies: '@types/node': 18.19.130 @@ -6237,6 +6662,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@12.1.0: {} + compare-versions@6.1.1: {} concat-map@0.0.1: {} @@ -6363,6 +6790,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -6746,6 +7175,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6896,6 +7327,10 @@ snapshots: safer-buffer: 2.1.2 optional: true + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -6927,6 +7362,18 @@ snapshots: ini@1.3.8: {} + inquirer@12.11.1(@types/node@22.19.5): + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.5) + '@inquirer/prompts': 7.10.1(@types/node@22.19.5) + '@inquirer/type': 3.0.10(@types/node@22.19.5) + mute-stream: 2.0.0 + run-async: 4.0.6 + rxjs: 7.8.2 + optionalDependencies: + '@types/node': 22.19.5 + ip-address@10.1.0: optional: true @@ -6950,11 +7397,17 @@ snapshots: dependencies: is-docker: 3.0.0 + is-interactive@2.0.0: {} + is-lambda@1.0.1: optional: true is-number@7.0.0: {} + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + is-what@5.5.0: {} is-wsl@3.1.0: @@ -7172,8 +7625,15 @@ snapshots: lodash.once@4.1.1: {} + lodash.truncate@4.4.2: {} + lodash@4.17.21: {} + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -7268,6 +7728,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-function@5.0.1: {} + mimic-response@3.1.0: {} minimatch@10.0.3: @@ -7347,6 +7809,8 @@ snapshots: mustache@4.2.0: {} + mute-stream@2.0.0: {} + nanoid@3.3.11: {} nanoid@5.1.6: {} @@ -7455,6 +7919,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@10.2.0: dependencies: default-browser: 5.4.0 @@ -7504,6 +7972,18 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.2 + p-finally@1.0.0: {} p-limit@3.1.0: @@ -7745,6 +8225,11 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + retry@0.12.0: optional: true @@ -7791,14 +8276,15 @@ snapshots: run-applescript@7.1.0: {} + run-async@4.0.6: {} + rxjs@7.8.2: dependencies: tslib: 2.8.1 safe-buffer@5.2.1: {} - safer-buffer@2.1.2: - optional: true + safer-buffer@2.1.2: {} semver@6.3.1: {} @@ -7844,6 +8330,12 @@ snapshots: slash@3.0.0: {} + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + smart-buffer@4.2.0: optional: true @@ -7902,6 +8394,8 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.2.2: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -7916,6 +8410,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -7950,6 +8450,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + table@6.9.0: + dependencies: + ajv: 8.13.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + tailwind-merge@3.4.0: {} tailwindcss@4.1.18: {} @@ -8049,6 +8557,8 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.21.0: {} + undici-types@7.16.0: {} undici@5.28.5: @@ -8322,6 +8832,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -8383,6 +8899,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76