mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
refactor: client
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# MemoHome CLI 架构说明
|
||||
|
||||
## 项目重构概述
|
||||
|
||||
本项目已重构为两个清晰分离的层次:
|
||||
|
||||
1. **Core 层** (`src/core/`): 纯粹的功能函数,不依赖任何 CLI UI 库
|
||||
2. **CLI 层** (`src/cli/`): 命令行交互界面,负责用户输入输出
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── core/ # 核心功能层(可被其他项目使用)
|
||||
│ ├── index.ts # 统一导出所有核心功能
|
||||
│ ├── auth.ts # 认证相关功能
|
||||
│ ├── user.ts # 用户管理功能
|
||||
│ ├── model.ts # 模型配置功能
|
||||
│ ├── agent.ts # AI 对话功能
|
||||
│ ├── memory.ts # 记忆管理功能
|
||||
│ ├── schedule.ts # 定时任务功能
|
||||
│ ├── settings.ts # 设置管理功能
|
||||
│ ├── debug.ts # 调试工具
|
||||
│ ├── config.ts # 配置管理
|
||||
│ └── client.ts # API 客户端
|
||||
├── cli/ # CLI 交互层
|
||||
│ ├── index.ts # CLI 入口
|
||||
│ └── commands/ # 命令定义
|
||||
│ ├── auth.ts
|
||||
│ ├── user.ts
|
||||
│ ├── model.ts
|
||||
│ ├── agent.ts
|
||||
│ ├── memory.ts
|
||||
│ ├── schedule.ts
|
||||
│ ├── config.ts
|
||||
│ └── debug.ts
|
||||
├── types/ # 类型定义
|
||||
│ └── index.ts
|
||||
├── utils/ # 工具函数
|
||||
│ └── index.ts
|
||||
└── index.ts # 主导出文件
|
||||
```
|
||||
|
||||
## Core 层特点
|
||||
|
||||
Core 层提供纯粹的功能函数,特点:
|
||||
|
||||
- ✅ **无 UI 依赖**: 不使用 chalk, ora, inquirer 等 CLI UI 库
|
||||
- ✅ **类型安全**: 提供完整的 TypeScript 类型定义
|
||||
- ✅ **错误处理**: 通过 throw Error 返回错误,调用者可自行处理
|
||||
- ✅ **可复用**: 可被 CLI 或其他项目(如 Web 应用)导入使用
|
||||
- ✅ **单一职责**: 每个模块只负责特定功能域
|
||||
|
||||
### Core 层 API 示例
|
||||
|
||||
```typescript
|
||||
// Auth
|
||||
import { login, logout, getCurrentUser } from '@memohome/cli/core'
|
||||
|
||||
await login({ username: 'admin', password: 'password' })
|
||||
const user = await getCurrentUser()
|
||||
logout()
|
||||
|
||||
// Agent
|
||||
import { chat, chatStream } from '@memohome/cli/core'
|
||||
|
||||
// 非流式对话
|
||||
const response = await chat({
|
||||
message: 'Hello',
|
||||
language: 'Chinese'
|
||||
})
|
||||
|
||||
// 流式对话
|
||||
await chatStream({ message: 'Hello' }, async (event) => {
|
||||
if (event.type === 'text-delta') {
|
||||
console.log(event.text)
|
||||
}
|
||||
})
|
||||
|
||||
// Model
|
||||
import { listModels, createModel } from '@memohome/cli/core'
|
||||
|
||||
const models = await listModels()
|
||||
const newModel = await createModel({
|
||||
name: 'GPT-4',
|
||||
modelId: 'gpt-4',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'sk-xxx',
|
||||
clientType: 'openai',
|
||||
type: 'chat'
|
||||
})
|
||||
|
||||
// Memory
|
||||
import { searchMemory, addMemory, getMessages } from '@memohome/cli/core'
|
||||
|
||||
const memories = await searchMemory({ query: 'test', limit: 10 })
|
||||
await addMemory({ content: 'Important note' })
|
||||
const messages = await getMessages({ page: 1, limit: 20 })
|
||||
```
|
||||
|
||||
## CLI 层特点
|
||||
|
||||
CLI 层负责用户交互,特点:
|
||||
|
||||
- ✅ **命令定义**: 使用 commander.js 定义命令
|
||||
- ✅ **美化输出**: 使用 chalk 颜色、ora 加载动画
|
||||
- ✅ **交互输入**: 使用 inquirer 提示用户输入
|
||||
- ✅ **错误显示**: 友好的错误信息展示
|
||||
- ✅ **调用 Core**: 所有业务逻辑调用 Core 层函数
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 作为 CLI 使用
|
||||
|
||||
```bash
|
||||
# 安装
|
||||
pnpm install
|
||||
|
||||
# 运行命令
|
||||
memohome auth login
|
||||
memohome agent chat "Hello"
|
||||
memohome model list
|
||||
```
|
||||
|
||||
### 2. 作为库使用
|
||||
|
||||
在其他项目中导入:
|
||||
|
||||
```typescript
|
||||
// 导入所有功能
|
||||
import * as memohome from '@memohome/cli'
|
||||
|
||||
// 或导入特定模块
|
||||
import { login, chat, listModels } from '@memohome/cli'
|
||||
import * as auth from '@memohome/cli/core'
|
||||
import type { User, Model } from '@memohome/cli/types'
|
||||
|
||||
// 使用
|
||||
await memohome.login({ username: 'admin', password: 'password' })
|
||||
const response = await memohome.chat({ message: 'Hello' })
|
||||
const models = await memohome.listModels()
|
||||
```
|
||||
|
||||
## 包导出
|
||||
|
||||
`package.json` 中的导出配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
".": "./src/index.ts", // 主入口,导出所有 core 功能
|
||||
"./core": "./src/core/index.ts", // Core 层
|
||||
"./types": "./src/types/index.ts", // 类型定义
|
||||
"./utils": "./src/utils/index.ts" // 工具函数
|
||||
},
|
||||
"bin": {
|
||||
"memohome": "./src/cli/index.ts" // CLI 入口
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. **关注点分离**: CLI UI 和业务逻辑完全分离
|
||||
2. **可测试性**: Core 层可以独立测试,无需模拟 CLI 环境
|
||||
3. **可复用性**: Core 层可在不同环境使用(CLI、Web、Desktop 等)
|
||||
4. **类型安全**: 完整的 TypeScript 类型定义
|
||||
5. **错误处理**: 统一的错误处理机制
|
||||
|
||||
## 迁移指南
|
||||
|
||||
如果你之前使用旧版本的代码,迁移方式:
|
||||
|
||||
### 旧代码(CLI 层调用)
|
||||
```typescript
|
||||
// 这些只能在 CLI 中使用,有 UI 输出
|
||||
```
|
||||
|
||||
### 新代码(Core 层调用)
|
||||
```typescript
|
||||
// 可以在任何地方使用,无 UI 依赖
|
||||
import { login, chat } from '@memohome/cli'
|
||||
|
||||
try {
|
||||
await login({ username: 'admin', password: 'password' })
|
||||
const response = await chat({ message: 'Hello' })
|
||||
console.log(response)
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message)
|
||||
}
|
||||
```
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新功能
|
||||
|
||||
1. 在 `src/core/` 中添加新的核心功能模块
|
||||
2. 在 `src/core/index.ts` 中导出
|
||||
3. 在 `src/cli/commands/` 中添加对应的 CLI 命令
|
||||
4. 在 `src/cli/index.ts` 中注册命令
|
||||
|
||||
### 测试
|
||||
|
||||
Core 层可以直接测试:
|
||||
|
||||
```typescript
|
||||
import { login, chat } from '../src/core'
|
||||
|
||||
test('login should work', async () => {
|
||||
const result = await login({ username: 'test', password: 'test' })
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- Core 层函数通过 `throw Error` 返回错误,调用者需要处理
|
||||
- CLI 层负责美化错误信息和用户反馈
|
||||
- 配置文件位于 `~/.memohome/config.json`
|
||||
- API 客户端使用 Eden Treaty
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
# MemoHome CLI 使用示例
|
||||
|
||||
## 作为 CLI 使用
|
||||
|
||||
### 认证
|
||||
```bash
|
||||
# 登录
|
||||
memohome auth login -u admin -p password
|
||||
|
||||
# 查看当前用户
|
||||
memohome auth whoami
|
||||
|
||||
# 登出
|
||||
memohome auth logout
|
||||
|
||||
# 配置 API URL
|
||||
memohome auth config -s http://localhost:7002
|
||||
```
|
||||
|
||||
### AI 对话
|
||||
```bash
|
||||
# 单次对话
|
||||
memohome agent chat "今天天气怎么样?"
|
||||
|
||||
# 交互模式
|
||||
memohome agent interactive
|
||||
|
||||
# 指定语言和上下文时间
|
||||
memohome agent chat "Hello" -l English -t 30
|
||||
```
|
||||
|
||||
### 模型管理
|
||||
```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
|
||||
|
||||
# 查看默认模型
|
||||
memohome model defaults
|
||||
|
||||
# 获取模型详情
|
||||
memohome model get <model-id>
|
||||
|
||||
# 删除模型
|
||||
memohome model delete <model-id>
|
||||
```
|
||||
|
||||
### 用户管理
|
||||
```bash
|
||||
# 列出所有用户
|
||||
memohome user list
|
||||
|
||||
# 创建用户
|
||||
memohome user create -u newuser -p password -r user
|
||||
|
||||
# 查看用户
|
||||
memohome user get <user-id>
|
||||
|
||||
# 更新密码
|
||||
memohome user update-password <user-id> -p newpassword
|
||||
|
||||
# 删除用户
|
||||
memohome user delete <user-id>
|
||||
```
|
||||
|
||||
### 记忆管理
|
||||
```bash
|
||||
# 搜索记忆
|
||||
memohome memory search "关键词" -l 10
|
||||
|
||||
# 添加记忆
|
||||
memohome memory add "重要的事情"
|
||||
|
||||
# 查看消息历史
|
||||
memohome memory messages -p 1 -l 20
|
||||
|
||||
# 按日期过滤消息
|
||||
memohome memory filter -s 2024-01-01T00:00:00Z -e 2024-12-31T23:59:59Z
|
||||
```
|
||||
|
||||
### 定时任务
|
||||
```bash
|
||||
# 列出所有任务
|
||||
memohome schedule list
|
||||
|
||||
# 创建任务
|
||||
memohome schedule create -t "每日报告" -c "0 9 * * *" -e
|
||||
|
||||
# 查看任务详情
|
||||
memohome schedule get <schedule-id>
|
||||
|
||||
# 更新任务
|
||||
memohome schedule update <schedule-id> -t "新标题" --enabled true
|
||||
|
||||
# 切换任务状态
|
||||
memohome schedule toggle <schedule-id>
|
||||
|
||||
# 删除任务
|
||||
memohome schedule delete <schedule-id>
|
||||
```
|
||||
|
||||
### 配置管理
|
||||
```bash
|
||||
# 查看配置
|
||||
memohome config get
|
||||
|
||||
# 设置配置
|
||||
memohome config set --language Chinese --max-context-time 60 --chat-model <model-id>
|
||||
|
||||
# 交互式配置向导
|
||||
memohome config setup
|
||||
```
|
||||
|
||||
### 调试工具
|
||||
```bash
|
||||
# 测试 API 连接
|
||||
memohome debug ping
|
||||
```
|
||||
|
||||
## 作为库使用
|
||||
|
||||
### 基础使用
|
||||
|
||||
```typescript
|
||||
import * as memohome from '@memohome/cli'
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// 登录
|
||||
const loginResult = await memohome.login({
|
||||
username: 'admin',
|
||||
password: 'password'
|
||||
})
|
||||
console.log('登录成功:', loginResult.user?.username)
|
||||
|
||||
// 获取当前用户
|
||||
const user = await memohome.getCurrentUser()
|
||||
console.log('当前用户:', user.username)
|
||||
|
||||
// AI 对话
|
||||
const response = await memohome.chat({
|
||||
message: 'Hello',
|
||||
language: 'Chinese'
|
||||
})
|
||||
console.log('AI 回复:', response)
|
||||
|
||||
// 列出模型
|
||||
const models = await memohome.listModels()
|
||||
console.log('模型数量:', models.length)
|
||||
|
||||
// 登出
|
||||
memohome.logout()
|
||||
} catch (error) {
|
||||
console.error('错误:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
### 流式对话
|
||||
|
||||
```typescript
|
||||
import { chatStream } from '@memohome/cli'
|
||||
|
||||
async function streamChat() {
|
||||
await chatStream(
|
||||
{
|
||||
message: '讲一个故事',
|
||||
language: 'Chinese'
|
||||
},
|
||||
async (event) => {
|
||||
if (event.type === 'text-delta') {
|
||||
process.stdout.write(event.text || '')
|
||||
} else if (event.type === 'tool-call') {
|
||||
console.log(`\n[使用工具: ${event.toolName}]`)
|
||||
} else if (event.type === 'error') {
|
||||
console.error('\n错误:', event.error)
|
||||
} else if (event.type === 'done') {
|
||||
console.log('\n完成')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
streamChat()
|
||||
```
|
||||
|
||||
### 模型管理
|
||||
|
||||
```typescript
|
||||
import { createModel, listModels, getDefaultModels } from '@memohome/cli'
|
||||
|
||||
async function manageModels() {
|
||||
// 创建 Chat 模型
|
||||
const chatModel = await createModel({
|
||||
name: 'GPT-4',
|
||||
modelId: 'gpt-4',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'sk-xxx',
|
||||
clientType: 'openai',
|
||||
type: 'chat'
|
||||
})
|
||||
console.log('创建的模型:', chatModel.id)
|
||||
|
||||
// 创建 Embedding 模型
|
||||
const embeddingModel = await createModel({
|
||||
name: 'Text Embedding',
|
||||
modelId: 'text-embedding-3-small',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'sk-xxx',
|
||||
clientType: 'openai',
|
||||
type: 'embedding',
|
||||
dimensions: 1536
|
||||
})
|
||||
console.log('Embedding 模型:', embeddingModel.id)
|
||||
|
||||
// 列出所有模型
|
||||
const models = await listModels()
|
||||
models.forEach(item => {
|
||||
console.log(`- ${item.model.name} (${item.model.type})`)
|
||||
})
|
||||
|
||||
// 获取默认模型
|
||||
const defaults = await getDefaultModels()
|
||||
console.log('默认 Chat 模型:', defaults.chat?.name)
|
||||
console.log('默认 Embedding 模型:', defaults.embedding?.name)
|
||||
}
|
||||
|
||||
manageModels()
|
||||
```
|
||||
|
||||
### 记忆管理
|
||||
|
||||
```typescript
|
||||
import { searchMemory, addMemory, getMessages } from '@memohome/cli'
|
||||
|
||||
async function manageMemory() {
|
||||
// 添加记忆
|
||||
await addMemory({ content: '今天学习了 TypeScript' })
|
||||
|
||||
// 搜索记忆
|
||||
const memories = await searchMemory({
|
||||
query: 'TypeScript',
|
||||
limit: 5
|
||||
})
|
||||
|
||||
memories.forEach(memory => {
|
||||
console.log(`相似度: ${(memory.similarity! * 100).toFixed(2)}%`)
|
||||
console.log(`内容: ${memory.content}`)
|
||||
console.log('---')
|
||||
})
|
||||
|
||||
// 获取消息历史
|
||||
const result = await getMessages({ page: 1, limit: 20 })
|
||||
console.log(`总消息数: ${result.pagination.total}`)
|
||||
result.messages.forEach(msg => {
|
||||
console.log(`${msg.role}: ${msg.content}`)
|
||||
})
|
||||
}
|
||||
|
||||
manageMemory()
|
||||
```
|
||||
|
||||
### 用户管理
|
||||
|
||||
```typescript
|
||||
import { createUser, listUsers, updateUserPassword } from '@memohome/cli'
|
||||
|
||||
async function manageUsers() {
|
||||
// 创建用户
|
||||
const newUser = await createUser({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
role: 'user'
|
||||
})
|
||||
console.log('创建的用户:', newUser.username)
|
||||
|
||||
// 列出所有用户
|
||||
const users = await listUsers()
|
||||
console.log('用户数量:', users.length)
|
||||
|
||||
// 更新密码
|
||||
await updateUserPassword({
|
||||
userId: newUser.id,
|
||||
password: 'newpassword123'
|
||||
})
|
||||
console.log('密码已更新')
|
||||
}
|
||||
|
||||
manageUsers()
|
||||
```
|
||||
|
||||
### 定时任务管理
|
||||
|
||||
```typescript
|
||||
import { createSchedule, listSchedules, toggleSchedule } from '@memohome/cli'
|
||||
|
||||
async function manageSchedules() {
|
||||
// 创建定时任务
|
||||
const schedule = await createSchedule({
|
||||
title: '每日报告',
|
||||
description: '生成每日工作报告',
|
||||
cronExpression: '0 9 * * *',
|
||||
enabled: true
|
||||
})
|
||||
console.log('创建的任务:', schedule.title)
|
||||
|
||||
// 列出所有任务
|
||||
const schedules = await listSchedules()
|
||||
schedules.forEach(s => {
|
||||
console.log(`${s.title}: ${s.cronExpression} (${s.enabled ? '启用' : '禁用'})`)
|
||||
})
|
||||
|
||||
// 切换任务状态
|
||||
const newStatus = await toggleSchedule(schedule.id)
|
||||
console.log('任务状态:', newStatus ? '启用' : '禁用')
|
||||
}
|
||||
|
||||
manageSchedules()
|
||||
```
|
||||
|
||||
### 配置管理
|
||||
|
||||
```typescript
|
||||
import { getSettings, updateSettings, setConfig } from '@memohome/cli'
|
||||
|
||||
async function manageConfig() {
|
||||
// 设置 API URL
|
||||
setConfig('http://localhost:7002')
|
||||
|
||||
// 获取用户设置
|
||||
const settings = await getSettings()
|
||||
console.log('当前语言:', settings.language)
|
||||
console.log('上下文时间:', settings.maxContextLoadTime)
|
||||
|
||||
// 更新设置
|
||||
await updateSettings({
|
||||
language: 'English',
|
||||
maxContextLoadTime: 120,
|
||||
defaultChatModel: 'model-id-xxx'
|
||||
})
|
||||
console.log('设置已更新')
|
||||
}
|
||||
|
||||
manageConfig()
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
```typescript
|
||||
import { login, chat } from '@memohome/cli'
|
||||
|
||||
async function handleErrors() {
|
||||
try {
|
||||
await login({
|
||||
username: 'wrong',
|
||||
password: 'wrong'
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('登录失败:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 未登录时调用需要认证的 API
|
||||
await chat({ message: 'Hello' })
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('需要先登录:', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleErrors()
|
||||
```
|
||||
|
||||
### 类型安全
|
||||
|
||||
```typescript
|
||||
import type { User, Model, Memory, Schedule } from '@memohome/cli/types'
|
||||
import { listUsers, listModels } from '@memohome/cli'
|
||||
|
||||
async function withTypes() {
|
||||
// 类型自动推导
|
||||
const users: User[] = await listUsers()
|
||||
const models = await listModels()
|
||||
|
||||
// 使用类型
|
||||
users.forEach((user: User) => {
|
||||
console.log(`${user.username} (${user.role})`)
|
||||
})
|
||||
|
||||
models.forEach((item) => {
|
||||
const model: Model = item.model
|
||||
console.log(`${model.name}: ${model.type}`)
|
||||
})
|
||||
}
|
||||
|
||||
withTypes()
|
||||
```
|
||||
|
||||
## Web 应用集成示例
|
||||
|
||||
```typescript
|
||||
// api/memohome.ts
|
||||
import * as memohome from '@memohome/cli'
|
||||
|
||||
export class MemoHomeService {
|
||||
async login(username: string, password: string) {
|
||||
try {
|
||||
return await memohome.login({ username, password })
|
||||
} catch (error) {
|
||||
throw new Error('登录失败')
|
||||
}
|
||||
}
|
||||
|
||||
async chat(message: string, onChunk: (text: string) => void) {
|
||||
await memohome.chatStream(
|
||||
{ message },
|
||||
async (event) => {
|
||||
if (event.type === 'text-delta' && event.text) {
|
||||
onChunk(event.text)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async getModels() {
|
||||
return await memohome.listModels()
|
||||
}
|
||||
}
|
||||
|
||||
// 在 React 组件中使用
|
||||
import { useState } from 'react'
|
||||
import { MemoHomeService } from './api/memohome'
|
||||
|
||||
function ChatComponent() {
|
||||
const [message, setMessage] = useState('')
|
||||
const [response, setResponse] = useState('')
|
||||
const service = new MemoHomeService()
|
||||
|
||||
const handleChat = async () => {
|
||||
setResponse('')
|
||||
await service.chat(message, (chunk) => {
|
||||
setResponse(prev => prev + chunk)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={message} onChange={e => setMessage(e.target.value)} />
|
||||
<button onClick={handleChat}>发送</button>
|
||||
<div>{response}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 测试示例
|
||||
|
||||
```typescript
|
||||
import { describe, test, expect, beforeAll } from 'bun:test'
|
||||
import { login, logout, chat, createModel } from '@memohome/cli'
|
||||
|
||||
describe('MemoHome Core API', () => {
|
||||
beforeAll(async () => {
|
||||
// 测试前登录
|
||||
await login({ username: 'test', password: 'test' })
|
||||
})
|
||||
|
||||
test('chat should return response', async () => {
|
||||
const response = await chat({ message: 'Hello' })
|
||||
expect(response).toBeDefined()
|
||||
expect(typeof response).toBe('string')
|
||||
})
|
||||
|
||||
test('createModel should work', async () => {
|
||||
const model = await createModel({
|
||||
name: 'Test Model',
|
||||
modelId: 'test-model',
|
||||
baseUrl: 'https://api.test.com',
|
||||
apiKey: 'test-key',
|
||||
clientType: 'openai',
|
||||
type: 'chat'
|
||||
})
|
||||
expect(model.id).toBeDefined()
|
||||
expect(model.name).toBe('Test Model')
|
||||
})
|
||||
|
||||
test('should throw error when not logged in', async () => {
|
||||
logout()
|
||||
await expect(chat({ message: 'Hello' })).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"name": "@memohome/cli",
|
||||
"name": "@memohome/client",
|
||||
"version": "1.0.0",
|
||||
"description": "Command line interface for MemoHome API",
|
||||
"description": "Command line interface and core API for MemoHome",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
".": "./src/index.ts",
|
||||
"./core": "./src/core/index.ts",
|
||||
"./types": "./src/types/index.ts",
|
||||
"./utils": "./src/utils/index.ts",
|
||||
"./cli": "./src/cli/index.ts"
|
||||
},
|
||||
"bin": {
|
||||
"memohome": "./src/index.ts"
|
||||
"memohome": "./src/cli/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "bun run src/index.ts",
|
||||
"dev": "bun run --watch src/index.ts"
|
||||
"start": "bun run src/cli/index.ts",
|
||||
"dev": "bun run --watch src/cli/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/eden": "^1.4.6",
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import * as agentCore from '../../core/agent'
|
||||
import { requireAuth } from '../../core/client'
|
||||
|
||||
export async function startInteractiveMode(options: { maxContextTime?: string; language?: string } = {}) {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
console.log(chalk.green.bold('🤖 MemoHome Agent Interactive Mode'))
|
||||
console.log(chalk.dim('Type /exit or /quit to exit, type /help for 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('Goodbye! 👋'))
|
||||
rl.close()
|
||||
process.exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
if (input === '/help') {
|
||||
console.log(chalk.green('\nAvailable commands:'))
|
||||
console.log(chalk.dim(' /exit, /quit - Exit interactive mode'))
|
||||
console.log(chalk.dim(' /help - Show help information\n'))
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(chalk.green('Agent: '))
|
||||
|
||||
await agentCore.chatStream(
|
||||
{
|
||||
message: input,
|
||||
maxContextLoadTime: parseInt(options.maxContextTime || '60'),
|
||||
language: options.language || 'Chinese',
|
||||
},
|
||||
async (event) => {
|
||||
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)
|
||||
} else if (event.type === 'done') {
|
||||
console.log('\n')
|
||||
rl.prompt()
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
rl.prompt()
|
||||
}
|
||||
})
|
||||
|
||||
rl.on('close', () => {
|
||||
console.log(chalk.yellow('\nGoodbye! 👋'))
|
||||
process.exit(0)
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
export function agentCommands(program: Command) {
|
||||
program
|
||||
.command('chat <message>')
|
||||
.description('Chat with AI Agent')
|
||||
.option('-t, --max-context-time <minutes>', 'Context load time (minutes)', '60')
|
||||
.option('-l, --language <language>', 'Response language', 'Chinese')
|
||||
.action(async (message, options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
console.log(chalk.blue('🤖 Agent: '))
|
||||
|
||||
await agentCore.chatStream(
|
||||
{
|
||||
message,
|
||||
maxContextLoadTime: parseInt(options.maxContextTime),
|
||||
language: options.language,
|
||||
},
|
||||
async (event) => {
|
||||
if (event.type === 'text-delta' && event.text) {
|
||||
process.stdout.write(event.text)
|
||||
} else if (event.type === 'tool-call') {
|
||||
console.log(chalk.dim(`\n[🔧 Using tool: ${event.toolName}]`))
|
||||
} else if (event.type === 'error') {
|
||||
console.error(chalk.red('\n❌ Error:'), event.error)
|
||||
} else if (event.type === 'done') {
|
||||
console.log('\n')
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('interactive')
|
||||
.alias('i')
|
||||
.description('Enter interactive conversation mode')
|
||||
.option('-t, --max-context-time <minutes>', 'Context load time (minutes)', '60')
|
||||
.option('-l, --language <language>', 'Response language', 'Chinese')
|
||||
.action(async (options) => {
|
||||
await startInteractiveMode(options)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ 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'
|
||||
import * as authCore from '../../core/auth'
|
||||
import { formatError } from '../../utils'
|
||||
|
||||
export function authCommands(program: Command) {
|
||||
program
|
||||
@@ -38,28 +37,15 @@ export function authCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Logging in...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.auth.login.post({
|
||||
username,
|
||||
password,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Login failed'))
|
||||
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)
|
||||
try {
|
||||
const result = await authCore.login({ username, password })
|
||||
spinner.succeed(chalk.green('Login successful!'))
|
||||
console.log(chalk.blue(`User: ${data.data.user.username}`))
|
||||
console.log(chalk.blue(`Role: ${data.data.user.role}`))
|
||||
} else {
|
||||
console.log(chalk.blue(`User: ${result.user?.username}`))
|
||||
console.log(chalk.blue(`Role: ${result.user?.role}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Login failed'))
|
||||
console.error(chalk.red('Invalid response format'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -73,13 +59,12 @@ export function authCommands(program: Command) {
|
||||
.command('logout')
|
||||
.description('Logout current user')
|
||||
.action(() => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
if (!authCore.isLoggedIn()) {
|
||||
console.log(chalk.yellow('Not currently logged in'))
|
||||
return
|
||||
}
|
||||
|
||||
clearToken()
|
||||
authCore.logout()
|
||||
console.log(chalk.green('✓ Logged out'))
|
||||
})
|
||||
|
||||
@@ -88,32 +73,24 @@ export function authCommands(program: Command) {
|
||||
.description('View current logged in user')
|
||||
.action(async () => {
|
||||
try {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
if (!authCore.isLoggedIn()) {
|
||||
console.log(chalk.yellow('Not currently logged in'))
|
||||
console.log(chalk.dim('Use "memohome auth login" to login'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Fetching user information...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.auth.me.get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user information'))
|
||||
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) {
|
||||
try {
|
||||
const user = await authCore.getCurrentUser()
|
||||
spinner.succeed(chalk.green('Logged in'))
|
||||
console.log(chalk.blue(`Username: ${data.data.username}`))
|
||||
console.log(chalk.blue(`Role: ${data.data.role}`))
|
||||
console.log(chalk.blue(`User ID: ${data.data.id}`))
|
||||
} else {
|
||||
console.log(chalk.blue(`Username: ${user.username}`))
|
||||
console.log(chalk.blue(`Role: ${user.role}`))
|
||||
console.log(chalk.blue(`User ID: ${user.id}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user information'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
@@ -129,14 +106,13 @@ export function authCommands(program: Command) {
|
||||
.action((options) => {
|
||||
if (options.set) {
|
||||
const url = options.set
|
||||
setApiUrl(url)
|
||||
authCore.setConfig(url)
|
||||
console.log(chalk.green(`✓ API URL set to: ${url}`))
|
||||
} else {
|
||||
const apiUrl = getApiUrl()
|
||||
const token = getToken()
|
||||
const config = authCore.getConfig()
|
||||
console.log(chalk.blue('Current configuration:'))
|
||||
console.log(chalk.dim(`API URL: ${apiUrl}`))
|
||||
console.log(chalk.dim(`Logged in: ${token ? 'Yes' : 'No'}`))
|
||||
console.log(chalk.dim(`API URL: ${config.apiUrl}`))
|
||||
console.log(chalk.dim(`Logged in: ${config.loggedIn ? 'Yes' : 'No'}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { createClient, requireAuth } from '../client'
|
||||
import * as settingsCore from '../../core/settings'
|
||||
import { formatError } from '../../utils'
|
||||
|
||||
export function configCommands(program: Command) {
|
||||
program
|
||||
@@ -10,21 +11,10 @@ export function configCommands(program: Command) {
|
||||
.description('Get current user settings')
|
||||
.action(async () => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching settings...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.settings.get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch settings'))
|
||||
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
|
||||
try {
|
||||
const settings = await settingsCore.getSettings()
|
||||
spinner.succeed(chalk.green('Current Settings'))
|
||||
console.log()
|
||||
console.log(chalk.blue('🎯 Agent Configuration:'))
|
||||
@@ -40,9 +30,14 @@ export function configCommands(program: Command) {
|
||||
console.log(chalk.dim(` User ID: ${settings.userId}`))
|
||||
console.log(chalk.dim(` Created At: ${new Date(settings.createdAt).toLocaleString('en-US')}`))
|
||||
console.log(chalk.dim(` Updated At: ${new Date(settings.updatedAt).toLocaleString('en-US')}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch settings'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -57,9 +52,13 @@ export function configCommands(program: Command) {
|
||||
.option('--embedding-model <id>', 'Default embedding model ID')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
const updates: any = {}
|
||||
const updates: {
|
||||
language?: string
|
||||
maxContextLoadTime?: number
|
||||
defaultChatModel?: string
|
||||
defaultSummaryModel?: string
|
||||
defaultEmbeddingModel?: string
|
||||
} = {}
|
||||
|
||||
if (options.language) updates.language = options.language
|
||||
if (options.maxContextTime)
|
||||
@@ -81,27 +80,23 @@ export function configCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Updating settings...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.settings.put(updates)
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to update settings'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success) {
|
||||
try {
|
||||
await settingsCore.updateSettings(updates)
|
||||
spinner.succeed(chalk.green('Settings updated'))
|
||||
console.log()
|
||||
console.log(chalk.blue('Updated settings:'))
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
console.log(chalk.dim(` ${key}: ${value}`))
|
||||
})
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to update settings'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -111,8 +106,6 @@ export function configCommands(program: Command) {
|
||||
.description('Interactive settings wizard')
|
||||
.action(async () => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
console.log(chalk.green.bold('\n🎨 Settings Wizard\n'))
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
@@ -153,27 +146,32 @@ export function configCommands(program: Command) {
|
||||
])
|
||||
|
||||
// Filter out empty values
|
||||
const updates: any = {}
|
||||
const updates: {
|
||||
language?: string
|
||||
maxContextLoadTime?: number
|
||||
defaultChatModel?: string
|
||||
defaultSummaryModel?: string
|
||||
defaultEmbeddingModel?: string
|
||||
} = {}
|
||||
Object.entries(answers).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
updates[key] = value
|
||||
updates[key as keyof typeof updates] = value as never
|
||||
}
|
||||
})
|
||||
|
||||
const spinner = ora('Saving settings...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.settings.put(updates)
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await settingsCore.updateSettings(updates)
|
||||
spinner.succeed(chalk.green('Settings saved'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to save settings'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(chalk.green('Settings saved'))
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import * as debugCore from '../../core/debug'
|
||||
|
||||
export function debugCommands(program: Command) {
|
||||
program
|
||||
.command('ping')
|
||||
.description('Test API server connection')
|
||||
.action(async () => {
|
||||
const info = debugCore.getConnectionInfo()
|
||||
|
||||
console.log(chalk.blue('Connection Info:'))
|
||||
console.log(chalk.dim(` API URL: ${info.apiUrl}`))
|
||||
console.log(chalk.dim(` Token: ${info.hasToken ? 'Set' : 'Not set'}`))
|
||||
console.log()
|
||||
|
||||
const spinner = ora('Connecting...').start()
|
||||
|
||||
const result = await debugCore.ping()
|
||||
|
||||
if (result.success) {
|
||||
spinner.succeed(chalk.green('Connection successful!'))
|
||||
if (result.message) {
|
||||
console.log(chalk.dim('Response:'), result.message)
|
||||
}
|
||||
} else {
|
||||
spinner.fail(chalk.red('Connection failed'))
|
||||
if (result.error) {
|
||||
if (result.error.includes('timeout')) {
|
||||
console.error(chalk.yellow('Connection timeout (5 seconds)'))
|
||||
console.error(chalk.dim('Please check if the API server is running'))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import { createClient, requireAuth } from '../client'
|
||||
import * as memoryCore from '../../core/memory'
|
||||
import { formatError } from '../../utils'
|
||||
|
||||
export function memoryCommands(program: Command) {
|
||||
program
|
||||
@@ -11,41 +11,35 @@ export function memoryCommands(program: Command) {
|
||||
.option('-l, --limit <limit>', 'Number of results to return', '10')
|
||||
.action(async (query, options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Searching memories...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.search.get({
|
||||
query: {
|
||||
q: query,
|
||||
try {
|
||||
const results = await memoryCore.searchMemory({
|
||||
query,
|
||||
limit: parseInt(options.limit),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Search failed'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
spinner.succeed(chalk.green(`Found ${results.length} memories`))
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
spinner.succeed(chalk.green(`Found ${data.data.length} memories`))
|
||||
|
||||
if (data.data.length === 0) {
|
||||
if (results.length === 0) {
|
||||
console.log(chalk.yellow('No related memories found'))
|
||||
return
|
||||
}
|
||||
|
||||
data.data.forEach((item: any, index: number) => {
|
||||
results.forEach((item, index) => {
|
||||
console.log()
|
||||
console.log(chalk.blue(`[${index + 1}] Similarity: ${(item.similarity * 100).toFixed(2)}%`))
|
||||
console.log(chalk.blue(`[${index + 1}] Similarity: ${((item.similarity || 0) * 100).toFixed(2)}%`))
|
||||
console.log(chalk.dim(`Time: ${new Date(item.timestamp).toLocaleString('en-US')}`))
|
||||
console.log(chalk.white(item.content))
|
||||
})
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Search failed'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -55,26 +49,19 @@ export function memoryCommands(program: Command) {
|
||||
.description('Add memory')
|
||||
.action(async (content) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Adding memory...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.post({
|
||||
content,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await memoryCore.addMemory({ content })
|
||||
spinner.succeed(chalk.green('Memory added'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to add memory'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success) {
|
||||
spinner.succeed(chalk.green('Memory added'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -87,26 +74,15 @@ export function memoryCommands(program: Command) {
|
||||
.option('-l, --limit <limit>', 'Items per page', '20')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching message history...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.message.get({
|
||||
query: {
|
||||
try {
|
||||
const result = await memoryCore.getMessages({
|
||||
page: parseInt(options.page),
|
||||
limit: parseInt(options.limit),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch messages'))
|
||||
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
|
||||
const { messages, pagination } = result
|
||||
spinner.succeed(chalk.green(`Message History (Page ${pagination.page}/${pagination.totalPages})`))
|
||||
|
||||
if (messages.length === 0) {
|
||||
@@ -116,7 +92,7 @@ export function memoryCommands(program: Command) {
|
||||
|
||||
console.log(chalk.dim(`\nTotal: ${pagination.total} messages\n`))
|
||||
|
||||
messages.forEach((msg: any) => {
|
||||
messages.forEach((msg) => {
|
||||
const roleColor = msg.role === 'user' ? chalk.blue : chalk.green
|
||||
const roleIcon = msg.role === 'user' ? '👤' : '🤖'
|
||||
console.log(roleColor(`${roleIcon} ${msg.role.toUpperCase()}`))
|
||||
@@ -124,9 +100,14 @@ export function memoryCommands(program: Command) {
|
||||
console.log(chalk.white(msg.content))
|
||||
console.log()
|
||||
})
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch messages'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -138,8 +119,6 @@ export function memoryCommands(program: Command) {
|
||||
.option('-e, --end <date>', 'End date (ISO 8601)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
if (!options.start || !options.end) {
|
||||
console.error(chalk.red('Please provide start and end dates'))
|
||||
console.log(chalk.dim('Example: memohome memory filter -s 2024-01-01T00:00:00Z -e 2024-12-31T23:59:59Z'))
|
||||
@@ -147,33 +126,23 @@ export function memoryCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Filtering messages...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.message.filter.get({
|
||||
query: {
|
||||
try {
|
||||
const messages = await memoryCore.filterMessages({
|
||||
startDate: options.start,
|
||||
endDate: options.end,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to filter messages'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
spinner.succeed(chalk.green(`Found ${messages.length} messages`))
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
spinner.succeed(chalk.green(`Found ${data.data.length} messages`))
|
||||
|
||||
if (data.data.length === 0) {
|
||||
if (messages.length === 0) {
|
||||
console.log(chalk.yellow('No messages found'))
|
||||
return
|
||||
}
|
||||
|
||||
console.log()
|
||||
|
||||
data.data.forEach((msg: any) => {
|
||||
messages.forEach((msg) => {
|
||||
const roleColor = msg.role === 'user' ? chalk.blue : chalk.green
|
||||
const roleIcon = msg.role === 'user' ? '👤' : '🤖'
|
||||
console.log(roleColor(`${roleIcon} ${msg.role.toUpperCase()}`))
|
||||
@@ -181,9 +150,14 @@ export function memoryCommands(program: Command) {
|
||||
console.log(chalk.white(msg.content))
|
||||
console.log()
|
||||
})
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to filter messages'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import * as modelCore from '../../core/model'
|
||||
import { formatError } from '../../utils'
|
||||
import { getApiUrl } from '../../core/config'
|
||||
|
||||
export function modelCommands(program: Command) {
|
||||
program
|
||||
.command('list')
|
||||
.description('List all model configurations')
|
||||
.action(async () => {
|
||||
const spinner = ora('Fetching model list...').start()
|
||||
try {
|
||||
const models = await modelCore.listModels()
|
||||
spinner.succeed(chalk.green('Model List'))
|
||||
|
||||
if (models.length === 0) {
|
||||
console.log(chalk.yellow('No model configurations found'))
|
||||
return
|
||||
}
|
||||
|
||||
const tableData = [
|
||||
['ID', 'Name', 'Model ID', 'Type', 'Client'],
|
||||
...models.map((item) => [
|
||||
item.id.substring(0, 8) + '...',
|
||||
item.model.name || '-',
|
||||
item.model.modelId,
|
||||
item.model.type === 'embedding' ? chalk.yellow('embedding') : chalk.blue('chat'),
|
||||
item.model.clientType,
|
||||
]),
|
||||
]
|
||||
|
||||
console.log(table(tableData))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getApiUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('create')
|
||||
.description('Create model configuration')
|
||||
.option('-n, --name <name>', 'Model name')
|
||||
.option('-m, --model-id <modelId>', 'Model ID')
|
||||
.option('-u, --base-url <baseUrl>', 'API Base URL')
|
||||
.option('-k, --api-key <apiKey>', 'API Key')
|
||||
.option('-c, --client-type <clientType>', 'Client type (openai/anthropic/google)')
|
||||
.option('-t, --type <type>', 'Model type (chat/embedding)', 'chat')
|
||||
.option('-d, --dimensions <dimensions>', 'Embedding dimensions (required for embedding type)')
|
||||
.action(async (options) => {
|
||||
const spinner = ora('Creating model configuration...').start()
|
||||
try {
|
||||
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: 'Model name:',
|
||||
when: !name,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'modelId',
|
||||
message: 'Model ID (e.g., gpt-4 or 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: 'Client type:',
|
||||
choices: ['openai', 'anthropic', 'google'],
|
||||
default: 'openai',
|
||||
when: !clientType,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'type',
|
||||
message: 'Model type:',
|
||||
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
|
||||
}
|
||||
|
||||
// If embedding type, dimensions is required
|
||||
if (type === 'embedding' && !dimensions) {
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'number',
|
||||
name: 'dimensions',
|
||||
message: 'Embedding dimensions (e.g., 1536):',
|
||||
validate: (value: number) => {
|
||||
if (value > 0) return true
|
||||
return 'Dimensions must be a positive integer'
|
||||
},
|
||||
},
|
||||
])
|
||||
dimensions = answer.dimensions
|
||||
}
|
||||
|
||||
spinner.text = 'Creating model configuration...'
|
||||
|
||||
const model = await modelCore.createModel({
|
||||
name,
|
||||
modelId,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
clientType,
|
||||
type: type as 'chat' | 'embedding',
|
||||
dimensions: dimensions ? (typeof dimensions === 'number' ? dimensions : parseInt(dimensions)) : undefined,
|
||||
})
|
||||
|
||||
spinner.succeed(chalk.green('Model configuration created successfully'))
|
||||
console.log(chalk.blue(`Name: ${model.name}`))
|
||||
console.log(chalk.blue(`Model ID: ${model.modelId}`))
|
||||
console.log(chalk.blue(`Type: ${model.type || 'chat'}`))
|
||||
if (model.type === 'embedding' && model.dimensions) {
|
||||
console.log(chalk.blue(`Dimensions: ${model.dimensions}`))
|
||||
}
|
||||
console.log(chalk.blue(`ID: ${model.id}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('delete <id>')
|
||||
.description('Delete model configuration')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: chalk.yellow(`Are you sure you want to delete model configuration ${id}?`),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('Cancelled'))
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = ora('Deleting model configuration...').start()
|
||||
await modelCore.deleteModel(id)
|
||||
spinner.succeed(chalk.green('Model configuration deleted'))
|
||||
} catch (error) {
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('get <id>')
|
||||
.description('Get model configuration details')
|
||||
.action(async (id) => {
|
||||
const spinner = ora('Fetching model configuration...').start()
|
||||
try {
|
||||
const model = await modelCore.getModel(id)
|
||||
spinner.succeed(chalk.green('Model Configuration'))
|
||||
console.log(chalk.blue(`ID: ${model.id}`))
|
||||
console.log(chalk.blue(`Name: ${model.name}`))
|
||||
console.log(chalk.blue(`Model ID: ${model.modelId}`))
|
||||
console.log(chalk.blue(`Type: ${model.type || 'chat'}`))
|
||||
if (model.type === 'embedding' && model.dimensions) {
|
||||
console.log(chalk.blue(`Dimensions: ${model.dimensions}`))
|
||||
}
|
||||
console.log(chalk.blue(`Base URL: ${model.baseUrl}`))
|
||||
console.log(chalk.blue(`Client Type: ${model.clientType}`))
|
||||
console.log(chalk.blue(`Created At: ${new Date(model.createdAt).toLocaleString('en-US')}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('defaults')
|
||||
.description('View default model configurations')
|
||||
.action(async () => {
|
||||
const spinner = ora('Fetching default model configurations...').start()
|
||||
try {
|
||||
const defaults = await modelCore.getDefaultModels()
|
||||
spinner.stop()
|
||||
|
||||
console.log(chalk.green.bold('Default Model Configurations:'))
|
||||
console.log()
|
||||
|
||||
// Chat Model
|
||||
if (defaults.chat) {
|
||||
console.log(chalk.blue('💬 Chat Model:'))
|
||||
console.log(chalk.dim(` Name: ${defaults.chat.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${defaults.chat.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${defaults.chat.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('💬 Chat Model: Not configured'))
|
||||
}
|
||||
console.log()
|
||||
|
||||
// Summary Model
|
||||
if (defaults.summary) {
|
||||
console.log(chalk.blue('📝 Summary Model:'))
|
||||
console.log(chalk.dim(` Name: ${defaults.summary.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${defaults.summary.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${defaults.summary.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('📝 Summary Model: Not configured'))
|
||||
}
|
||||
console.log()
|
||||
|
||||
// Embedding Model
|
||||
if (defaults.embedding) {
|
||||
console.log(chalk.blue('🔍 Embedding Model:'))
|
||||
console.log(chalk.dim(` Name: ${defaults.embedding.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${defaults.embedding.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${defaults.embedding.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('🔍 Embedding Model: Not configured'))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+70
-111
@@ -3,7 +3,8 @@ import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import { createClient, requireAuth } from '../client'
|
||||
import * as scheduleCore from '../../core/schedule'
|
||||
import { formatError } from '../../utils'
|
||||
|
||||
export function scheduleCommands(program: Command) {
|
||||
program
|
||||
@@ -11,23 +12,12 @@ export function scheduleCommands(program: Command) {
|
||||
.description('List all scheduled tasks')
|
||||
.action(async () => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching scheduled tasks list...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule.get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch scheduled tasks list'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
try {
|
||||
const schedules = await scheduleCore.listSchedules()
|
||||
spinner.succeed(chalk.green('Scheduled Tasks List'))
|
||||
|
||||
const schedules = data.data
|
||||
if (schedules.length === 0) {
|
||||
console.log(chalk.yellow('No scheduled tasks'))
|
||||
return
|
||||
@@ -35,7 +25,7 @@ export function scheduleCommands(program: Command) {
|
||||
|
||||
const tableData = [
|
||||
['ID', 'Title', 'Cron', 'Enabled', 'Created At'],
|
||||
...schedules.map((schedule: any) => [
|
||||
...schedules.map((schedule) => [
|
||||
schedule.id.substring(0, 8) + '...',
|
||||
schedule.title,
|
||||
schedule.cronExpression,
|
||||
@@ -45,9 +35,14 @@ export function scheduleCommands(program: Command) {
|
||||
]
|
||||
|
||||
console.log(table(tableData))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch scheduled tasks list'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -61,8 +56,6 @@ export function scheduleCommands(program: Command) {
|
||||
.option('-e, --enabled', 'Enable task', false)
|
||||
.action(async (options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
let { title, description, cron, enabled } = options
|
||||
|
||||
if (!title || !cron) {
|
||||
@@ -101,35 +94,27 @@ export function scheduleCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Creating scheduled task...').start()
|
||||
const client = createClient()
|
||||
|
||||
const payload: any = {
|
||||
title,
|
||||
cronExpression: cron,
|
||||
enabled,
|
||||
}
|
||||
try {
|
||||
const schedule = await scheduleCore.createSchedule({
|
||||
title,
|
||||
description,
|
||||
cronExpression: cron,
|
||||
enabled,
|
||||
})
|
||||
|
||||
if (description) {
|
||||
payload.description = description
|
||||
}
|
||||
|
||||
const response = await client.schedule.post(payload)
|
||||
|
||||
if (response.error) {
|
||||
spinner.succeed(chalk.green('Scheduled task created successfully'))
|
||||
console.log(chalk.blue(`Title: ${schedule.title}`))
|
||||
console.log(chalk.blue(`Cron: ${schedule.cronExpression}`))
|
||||
console.log(chalk.blue(`ID: ${schedule.id}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to create scheduled task'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
spinner.succeed(chalk.green('Scheduled task created successfully'))
|
||||
console.log(chalk.blue(`Title: ${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:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -139,21 +124,10 @@ export function scheduleCommands(program: Command) {
|
||||
.description('Get scheduled task details')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching scheduled task details...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch scheduled task'))
|
||||
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
|
||||
try {
|
||||
const schedule = await scheduleCore.getSchedule(id)
|
||||
spinner.succeed(chalk.green('Scheduled Task Details'))
|
||||
console.log(chalk.blue(`ID: ${schedule.id}`))
|
||||
console.log(chalk.blue(`Title: ${schedule.title}`))
|
||||
@@ -170,9 +144,14 @@ export function scheduleCommands(program: Command) {
|
||||
console.log(
|
||||
chalk.blue(`Updated At: ${new Date(schedule.updatedAt).toLocaleString('en-US')}`)
|
||||
)
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch scheduled task'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -186,9 +165,12 @@ export function scheduleCommands(program: Command) {
|
||||
.option('-e, --enabled <boolean>', 'Enable task (true/false)')
|
||||
.action(async (id, options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
const updates: any = {}
|
||||
const updates: {
|
||||
title?: string
|
||||
description?: string
|
||||
cronExpression?: string
|
||||
enabled?: boolean
|
||||
} = {}
|
||||
|
||||
if (options.title) updates.title = options.title
|
||||
if (options.description) updates.description = options.description
|
||||
@@ -203,19 +185,18 @@ export function scheduleCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Updating scheduled task...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).put(updates)
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await scheduleCore.updateSchedule(id, updates)
|
||||
spinner.succeed(chalk.green('Scheduled task updated'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to update scheduled task'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(chalk.green('Scheduled task updated'))
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -225,8 +206,6 @@ export function scheduleCommands(program: Command) {
|
||||
.description('Delete scheduled task')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
@@ -242,19 +221,18 @@ export function scheduleCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Deleting scheduled task...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await scheduleCore.deleteSchedule(id)
|
||||
spinner.succeed(chalk.green('Scheduled task deleted'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to delete scheduled task'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(chalk.green('Scheduled task deleted'))
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -264,40 +242,21 @@ export function scheduleCommands(program: Command) {
|
||||
.description('Toggle scheduled task enabled status')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Toggling task status...').start()
|
||||
const client = createClient()
|
||||
|
||||
// First get current status
|
||||
const getResponse = await client.schedule({ id }).get()
|
||||
|
||||
if (getResponse.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch task'))
|
||||
console.error(chalk.red(getResponse.error.value))
|
||||
try {
|
||||
const newStatus = await scheduleCore.toggleSchedule(id)
|
||||
spinner.succeed(
|
||||
chalk.green(`Task ${newStatus ? 'enabled' : 'disabled'}`)
|
||||
)
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to toggle task'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const getData = getResponse.data as any
|
||||
if (getData?.success && getData?.data) {
|
||||
const currentEnabled = getData.data.enabled
|
||||
|
||||
// Update status
|
||||
const updateResponse = await client.schedule({ id }).put({
|
||||
enabled: !currentEnabled,
|
||||
})
|
||||
|
||||
if (updateResponse.error) {
|
||||
spinner.fail(chalk.red('Failed to update task'))
|
||||
console.error(chalk.red(updateResponse.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(
|
||||
chalk.green(`Task ${!currentEnabled ? 'enabled' : 'disabled'}`)
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -3,7 +3,8 @@ import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import { createClient, requireAuth } from '../client'
|
||||
import * as userCore from '../../core/user'
|
||||
import { formatError } from '../../utils'
|
||||
|
||||
export function userCommands(program: Command) {
|
||||
program
|
||||
@@ -11,23 +12,12 @@ export function userCommands(program: Command) {
|
||||
.description('List all users (requires admin privileges)')
|
||||
.action(async () => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching user list...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user.get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user list'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
try {
|
||||
const users = await userCore.listUsers()
|
||||
spinner.succeed(chalk.green('User List'))
|
||||
|
||||
const users = data.data
|
||||
if (users.length === 0) {
|
||||
console.log(chalk.yellow('No users'))
|
||||
return
|
||||
@@ -35,7 +25,7 @@ export function userCommands(program: Command) {
|
||||
|
||||
const tableData = [
|
||||
['ID', 'Username', 'Role', 'Created At'],
|
||||
...users.map((user: any) => [
|
||||
...users.map((user) => [
|
||||
user.id,
|
||||
user.username,
|
||||
user.role === 'admin' ? chalk.red('Admin') : chalk.blue('User'),
|
||||
@@ -44,9 +34,14 @@ export function userCommands(program: Command) {
|
||||
]
|
||||
|
||||
console.log(table(tableData))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user list'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -59,8 +54,6 @@ export function userCommands(program: Command) {
|
||||
.option('-r, --role <role>', 'Role (user/admin)', 'user')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
let username = options.username
|
||||
let password = options.password
|
||||
let role = options.role
|
||||
@@ -95,29 +88,21 @@ export function userCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Creating user...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user.post({
|
||||
username,
|
||||
password,
|
||||
role,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
const user = await userCore.createUser({ username, password, role })
|
||||
spinner.succeed(chalk.green('User created successfully'))
|
||||
console.log(chalk.blue(`Username: ${user.username}`))
|
||||
console.log(chalk.blue(`Role: ${user.role}`))
|
||||
console.log(chalk.blue(`ID: ${user.id}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to create user'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
spinner.succeed(chalk.green('User created successfully'))
|
||||
console.log(chalk.blue(`Username: ${data.data.username}`))
|
||||
console.log(chalk.blue(`Role: ${data.data.role}`))
|
||||
console.log(chalk.blue(`ID: ${data.data.id}`))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -127,8 +112,6 @@ export function userCommands(program: Command) {
|
||||
.description('Delete user (requires admin privileges)')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
@@ -144,19 +127,18 @@ export function userCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Deleting user...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await userCore.deleteUser(id)
|
||||
spinner.succeed(chalk.green('User deleted'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to delete user'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(chalk.green('User deleted'))
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -166,29 +148,23 @@ export function userCommands(program: Command) {
|
||||
.description('Get user details')
|
||||
.action(async (id) => {
|
||||
try {
|
||||
requireAuth()
|
||||
const spinner = ora('Fetching user information...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user information'))
|
||||
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
|
||||
try {
|
||||
const user = await userCore.getUser(id)
|
||||
spinner.succeed(chalk.green('User Information'))
|
||||
console.log(chalk.blue(`ID: ${user.id}`))
|
||||
console.log(chalk.blue(`Username: ${user.username}`))
|
||||
console.log(chalk.blue(`Role: ${user.role}`))
|
||||
console.log(chalk.blue(`Created At: ${new Date(user.createdAt).toLocaleString('en-US')}`))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to fetch user information'))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -199,8 +175,6 @@ export function userCommands(program: Command) {
|
||||
.option('-p, --password <password>', 'New password')
|
||||
.action(async (id, options) => {
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
let password = options.password
|
||||
|
||||
if (!password) {
|
||||
@@ -216,21 +190,18 @@ export function userCommands(program: Command) {
|
||||
}
|
||||
|
||||
const spinner = ora('Updating password...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id }).password.patch({
|
||||
password,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
await userCore.updateUserPassword({ userId: id, password })
|
||||
spinner.succeed(chalk.green('Password updated'))
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to update password'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
console.error(chalk.red(formatError(error)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
spinner.succeed(chalk.green('Password updated'))
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/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, startInteractiveMode } from './commands/agent'
|
||||
import { memoryCommands } from './commands/memory'
|
||||
import { configCommands } from './commands/config'
|
||||
import { scheduleCommands } from './commands/schedule'
|
||||
import { debugCommands } from './commands/debug'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program
|
||||
.name('memohome')
|
||||
.description(chalk.bold.blue('🏠 MemoHome Agent'))
|
||||
.version('1.0.0')
|
||||
|
||||
// Authentication commands
|
||||
const auth = program.command('auth').description('User authentication management')
|
||||
authCommands(auth)
|
||||
|
||||
// User management commands
|
||||
const user = program.command('user').description('User management (requires admin privileges)')
|
||||
userCommands(user)
|
||||
|
||||
// Model management commands
|
||||
const model = program.command('model').description('AI model configuration management')
|
||||
modelCommands(model)
|
||||
|
||||
// Agent conversation commands
|
||||
const agent = program.command('agent').description('Chat with AI Agent')
|
||||
agentCommands(agent)
|
||||
|
||||
// Memory management commands
|
||||
const memory = program.command('memory').description('Memory management')
|
||||
memoryCommands(memory)
|
||||
|
||||
// Config management commands
|
||||
const config = program.command('config').description('User configuration management')
|
||||
configCommands(config)
|
||||
|
||||
// Schedule management commands
|
||||
const schedule = program.command('schedule').description('Schedule management')
|
||||
scheduleCommands(schedule)
|
||||
|
||||
// Debug commands
|
||||
const debug = program.command('debug').description('Debug tools')
|
||||
debugCommands(debug)
|
||||
|
||||
// If no arguments provided, start interactive mode
|
||||
if (process.argv.length === 2) {
|
||||
startInteractiveMode().catch((error) => {
|
||||
console.error('Failed to start interactive mode:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
} else {
|
||||
program.parse()
|
||||
}
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
import type { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import { requireAuth, getApiUrl, getToken } from '../client'
|
||||
|
||||
export async function startInteractiveMode(options: { maxContextTime?: string; language?: string } = {}) {
|
||||
try {
|
||||
requireAuth()
|
||||
const token = getToken()!
|
||||
const apiUrl = getApiUrl()
|
||||
|
||||
console.log(chalk.green.bold('🤖 MemoHome Agent Interactive Mode'))
|
||||
console.log(chalk.dim('Type /exit or /quit to exit, type /help for 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('Goodbye! 👋'))
|
||||
rl.close()
|
||||
process.exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
if (input === '/help') {
|
||||
console.log(chalk.green('\nAvailable commands:'))
|
||||
console.log(chalk.dim(' /exit, /quit - Exit interactive mode'))
|
||||
console.log(chalk.dim(' /help - Show help information\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 || '60'),
|
||||
language: options.language || 'Chinese',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json() as { error?: string }
|
||||
console.error(chalk.red('Chat failed:'), errorData.error || 'Unknown error')
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Unable to read response stream')
|
||||
}
|
||||
|
||||
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 {
|
||||
// Skip unparseable JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
rl.prompt()
|
||||
}
|
||||
})
|
||||
|
||||
rl.on('close', () => {
|
||||
console.log(chalk.yellow('\nGoodbye! 👋'))
|
||||
process.exit(0)
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
export function agentCommands(program: Command) {
|
||||
program
|
||||
.command('chat <message>')
|
||||
.description('Chat with AI Agent')
|
||||
.option('-t, --max-context-time <minutes>', 'Context load time (minutes)', '60')
|
||||
.option('-l, --language <language>', 'Response 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('Chat failed:'), errorData.error || 'Unknown error')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Unable to read response stream')
|
||||
}
|
||||
|
||||
let buffer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
buffer += chunk
|
||||
|
||||
// Process line by line
|
||||
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[🔧 Using tool: ${event.toolName}]`))
|
||||
} else if (event.type === 'error') {
|
||||
console.error(chalk.red('\n❌ Error:'), event.error)
|
||||
}
|
||||
} catch {
|
||||
// Skip unparseable JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error(chalk.red('Error:'), message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('interactive')
|
||||
.alias('i')
|
||||
.description('Enter interactive conversation mode')
|
||||
.option('-t, --max-context-time <minutes>', 'Context load time (minutes)', '60')
|
||||
.option('-l, --language <language>', 'Response language', 'Chinese')
|
||||
.action(async (options) => {
|
||||
await startInteractiveMode(options)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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('Test API server connection')
|
||||
.action(async () => {
|
||||
const apiUrl = getApiUrl()
|
||||
const token = getToken()
|
||||
|
||||
console.log(chalk.blue('Connection Info:'))
|
||||
console.log(chalk.dim(` API URL: ${apiUrl}`))
|
||||
console.log(chalk.dim(` Token: ${token ? 'Set' : 'Not set'}`))
|
||||
console.log()
|
||||
|
||||
const spinner = ora('Connecting...').start()
|
||||
|
||||
try {
|
||||
// Try direct 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('Connection successful!'))
|
||||
const text = await response.text()
|
||||
console.log(chalk.dim('Response:'), text.substring(0, 100))
|
||||
} else {
|
||||
spinner.fail(chalk.red(`Connection failed: HTTP ${response.status}`))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Connection failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError') {
|
||||
console.error(chalk.yellow('Connection timeout (5 seconds)'))
|
||||
console.error(chalk.dim('Please check if the API server is running'))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,400 +0,0 @@
|
||||
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('List all model configurations')
|
||||
.action(async () => {
|
||||
const spinner = ora('Fetching model list...').start()
|
||||
try {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model.get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch model list'))
|
||||
console.error(chalk.red(formatError(response.error.value)))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// API response format: { success, items, pagination }
|
||||
const data = response.data as { success?: boolean; items?: Model[]; pagination?: unknown } | null
|
||||
if (data?.success && data?.items) {
|
||||
spinner.succeed(chalk.green('Model List'))
|
||||
|
||||
const models = data.items
|
||||
if (models.length === 0) {
|
||||
console.log(chalk.yellow('No model configurations found'))
|
||||
return
|
||||
}
|
||||
|
||||
const tableData = [
|
||||
['ID', 'Name', 'Model ID', 'Type', 'Client'],
|
||||
...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('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
const { getApiUrl: getUrl } = await import('../config')
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('create')
|
||||
.description('Create model configuration')
|
||||
.option('-n, --name <name>', 'Model name')
|
||||
.option('-m, --model-id <modelId>', 'Model ID')
|
||||
.option('-u, --base-url <baseUrl>', 'API Base URL')
|
||||
.option('-k, --api-key <apiKey>', 'API Key')
|
||||
.option('-c, --client-type <clientType>', 'Client type (openai/anthropic/google)')
|
||||
.option('-t, --type <type>', 'Model type (chat/embedding)', 'chat')
|
||||
.option('-d, --dimensions <dimensions>', 'Embedding dimensions (required for embedding type)')
|
||||
.action(async (options) => {
|
||||
const spinner = ora('Creating model configuration...').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: 'Model name:',
|
||||
when: !name,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'modelId',
|
||||
message: 'Model ID (e.g., gpt-4 or 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: 'Client type:',
|
||||
choices: ['openai', 'anthropic', 'google'],
|
||||
default: 'openai',
|
||||
when: !clientType,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'type',
|
||||
message: 'Model type:',
|
||||
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
|
||||
}
|
||||
|
||||
// If embedding type, dimensions is required
|
||||
if (type === 'embedding' && !dimensions) {
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'number',
|
||||
name: 'dimensions',
|
||||
message: 'Embedding dimensions (e.g., 1536):',
|
||||
validate: (value: number) => {
|
||||
if (value > 0) return true
|
||||
return 'Dimensions must be a positive integer'
|
||||
},
|
||||
},
|
||||
])
|
||||
dimensions = answer.dimensions
|
||||
}
|
||||
|
||||
spinner.text = 'Creating model configuration...'
|
||||
const client = createClient()
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
name,
|
||||
modelId,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
clientType,
|
||||
type,
|
||||
}
|
||||
|
||||
// If embedding type, add dimensions
|
||||
if (type === 'embedding') {
|
||||
if (!dimensions) {
|
||||
console.error(chalk.red('Embedding models require dimensions to be specified'))
|
||||
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('Failed to create model configuration'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as ApiResponse<Model> | null
|
||||
if (data?.success && data?.data) {
|
||||
spinner.succeed(chalk.green('Model configuration created successfully'))
|
||||
console.log(chalk.blue(`Name: ${data.data.name}`))
|
||||
console.log(chalk.blue(`Model ID: ${data.data.modelId}`))
|
||||
console.log(chalk.blue(`Type: ${data.data.type || 'chat'}`))
|
||||
if (data.data.type === 'embedding' && data.data.dimensions) {
|
||||
console.log(chalk.blue(`Dimensions: ${data.data.dimensions}`))
|
||||
}
|
||||
console.log(chalk.blue(`ID: ${data.data.id}`))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
const { getApiUrl: getUrl } = await import('../config')
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('delete <id>')
|
||||
.description('Delete model configuration')
|
||||
.action(async (id) => {
|
||||
let spinner: ReturnType<typeof ora> | undefined
|
||||
try {
|
||||
requireAuth()
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: chalk.yellow(`Are you sure you want to delete model configuration ${id}?`),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('Cancelled'))
|
||||
return
|
||||
}
|
||||
|
||||
spinner = ora('Deleting model configuration...').start()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to delete model configuration'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (spinner) spinner.succeed(chalk.green('Model configuration deleted'))
|
||||
} catch (error) {
|
||||
if (spinner) spinner.fail(chalk.red('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
const { getApiUrl: getUrl } = await import('../config')
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('get <id>')
|
||||
.description('Get model configuration details')
|
||||
.action(async (id) => {
|
||||
const spinner = ora('Fetching model configuration...').start()
|
||||
try {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
spinner.fail(chalk.red('Failed to fetch model configuration'))
|
||||
console.error(chalk.red(response.error.value))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = response.data as ApiResponse<Model> | null
|
||||
if (data?.success && data?.data) {
|
||||
const model = data.data
|
||||
spinner.succeed(chalk.green('Model Configuration'))
|
||||
console.log(chalk.blue(`ID: ${model.id}`))
|
||||
console.log(chalk.blue(`Name: ${model.name}`))
|
||||
console.log(chalk.blue(`Model ID: ${model.modelId}`))
|
||||
console.log(chalk.blue(`Type: ${model.type || 'chat'}`))
|
||||
if (model.type === 'embedding' && model.dimensions) {
|
||||
console.log(chalk.blue(`Dimensions: ${model.dimensions}`))
|
||||
}
|
||||
console.log(chalk.blue(`Base URL: ${model.baseUrl}`))
|
||||
console.log(chalk.blue(`Client Type: ${model.clientType}`))
|
||||
console.log(chalk.blue(`Created At: ${new Date(model.createdAt).toLocaleString('en-US')}`))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
const { getApiUrl: getUrl } = await import('../config')
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command('defaults')
|
||||
.description('View default model configurations')
|
||||
.action(async () => {
|
||||
const spinner = ora('Fetching default model configurations...').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('Default Model Configurations:'))
|
||||
console.log()
|
||||
|
||||
// Chat Model
|
||||
const chatData = chatRes.data as ApiResponse<Model> | null
|
||||
if (chatData?.success && chatData.data) {
|
||||
const model = chatData.data
|
||||
console.log(chalk.blue('💬 Chat Model:'))
|
||||
console.log(chalk.dim(` Name: ${model.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${model.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${model.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('💬 Chat Model: Not configured'))
|
||||
}
|
||||
console.log()
|
||||
|
||||
// Summary Model
|
||||
const summaryData = summaryRes.data as ApiResponse<Model> | null
|
||||
if (summaryData?.success && summaryData.data) {
|
||||
const model = summaryData.data
|
||||
console.log(chalk.blue('📝 Summary Model:'))
|
||||
console.log(chalk.dim(` Name: ${model.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${model.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${model.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('📝 Summary Model: Not configured'))
|
||||
}
|
||||
console.log()
|
||||
|
||||
// Embedding Model
|
||||
const embeddingData = embeddingRes.data as ApiResponse<Model> | null
|
||||
if (embeddingData?.success && embeddingData.data) {
|
||||
const model = embeddingData.data
|
||||
console.log(chalk.blue('🔍 Embedding Model:'))
|
||||
console.log(chalk.dim(` Name: ${model.name}`))
|
||||
console.log(chalk.dim(` Model ID: ${model.modelId}`))
|
||||
console.log(chalk.dim(` ID: ${model.id}`))
|
||||
} else {
|
||||
console.log(chalk.yellow('🔍 Embedding Model: Not configured'))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Operation failed'))
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
const { getApiUrl: getUrl } = await import('../config')
|
||||
console.error(chalk.red('Connection timeout, please check:'))
|
||||
console.error(chalk.yellow(' 1. Is the API server running?'))
|
||||
console.error(chalk.yellow(' 2. Is the API URL correct?'))
|
||||
console.error(chalk.dim(` Current config: ${getUrl()}`))
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message)
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), String(error))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { requireAuth, getToken, getApiUrl } from './client'
|
||||
|
||||
export interface ChatParams {
|
||||
message: string
|
||||
maxContextLoadTime?: number
|
||||
language?: string
|
||||
}
|
||||
|
||||
export interface StreamEvent {
|
||||
type: 'text-delta' | 'tool-call' | 'error' | 'done'
|
||||
text?: string
|
||||
toolName?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type StreamCallback = (event: StreamEvent) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (streaming)
|
||||
*/
|
||||
export async function chatStream(
|
||||
params: ChatParams,
|
||||
onEvent: StreamCallback
|
||||
): Promise<void> {
|
||||
requireAuth()
|
||||
const token = getToken()!
|
||||
const apiUrl = getApiUrl()
|
||||
|
||||
const response = await fetch(`${apiUrl}/agent/stream`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: params.message,
|
||||
maxContextLoadTime: params.maxContextLoadTime || 60,
|
||||
language: params.language || 'Chinese',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json() as { error?: string }
|
||||
throw new Error(errorData.error || 'Chat failed')
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Unable to read response stream')
|
||||
}
|
||||
|
||||
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]') {
|
||||
await onEvent({ type: 'done' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const event = JSON.parse(data)
|
||||
|
||||
if (event.type === 'text-delta' && event.text) {
|
||||
await onEvent({ type: 'text-delta', text: event.text })
|
||||
} else if (event.type === 'tool-call') {
|
||||
await onEvent({ type: 'tool-call', toolName: event.toolName })
|
||||
} else if (event.type === 'error') {
|
||||
await onEvent({ type: 'error', error: event.error })
|
||||
}
|
||||
} catch {
|
||||
// Skip unparseable JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat with AI Agent (non-streaming, collect full response)
|
||||
*/
|
||||
export async function chat(params: ChatParams): Promise<string> {
|
||||
let fullResponse = ''
|
||||
|
||||
await chatStream(params, async (event) => {
|
||||
if (event.type === 'text-delta' && event.text) {
|
||||
fullResponse += event.text
|
||||
} else if (event.type === 'error') {
|
||||
throw new Error(event.error)
|
||||
}
|
||||
})
|
||||
|
||||
return fullResponse
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { createClient } from './client'
|
||||
import { setToken, clearToken, getToken, getApiUrl, setApiUrl } from './config'
|
||||
|
||||
export interface LoginParams {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
success: boolean
|
||||
token?: string
|
||||
user?: {
|
||||
username: string
|
||||
role: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
username: string
|
||||
role: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface ConfigInfo {
|
||||
apiUrl: string
|
||||
loggedIn: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to MemoHome API
|
||||
*/
|
||||
export async function login(params: LoginParams): Promise<LoginResult> {
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.auth.login.post({
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
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)
|
||||
return {
|
||||
success: true,
|
||||
token: data.data.token,
|
||||
user: data.data.user as UserInfo,
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid response format')
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout current user
|
||||
*/
|
||||
export function logout(): void {
|
||||
clearToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is logged in
|
||||
*/
|
||||
export function isLoggedIn(): boolean {
|
||||
return getToken() !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current logged in user info
|
||||
*/
|
||||
export async function getCurrentUser(): Promise<UserInfo> {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
|
||||
const client = createClient()
|
||||
const response = await client.auth.me.get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
const data = response.data as { success?: boolean; data?: UserInfo } | null
|
||||
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch user information')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current API configuration
|
||||
*/
|
||||
export function getConfig(): ConfigInfo {
|
||||
return {
|
||||
apiUrl: getApiUrl(),
|
||||
loggedIn: isLoggedIn(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API URL
|
||||
*/
|
||||
export function setConfig(apiUrl: string): void {
|
||||
setApiUrl(apiUrl)
|
||||
}
|
||||
|
||||
// Re-export config functions for convenience
|
||||
export { getToken, getApiUrl, setToken, clearToken, setApiUrl }
|
||||
|
||||
@@ -32,7 +32,6 @@ export function loadConfig(): Config {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { getApiUrl, getToken } from './config'
|
||||
|
||||
export interface PingResult {
|
||||
success: boolean
|
||||
status?: number
|
||||
message?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Test API server connection
|
||||
*/
|
||||
export async function ping(): Promise<PingResult> {
|
||||
const apiUrl = getApiUrl()
|
||||
const token = getToken()
|
||||
|
||||
try {
|
||||
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) {
|
||||
const text = await response.text()
|
||||
return {
|
||||
success: true,
|
||||
status: response.status,
|
||||
message: text.substring(0, 100),
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
status: response.status,
|
||||
error: `HTTP ${response.status}`,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.name === 'AbortError') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Connection timeout (5 seconds)',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unknown error',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection info
|
||||
*/
|
||||
export function getConnectionInfo(): {
|
||||
apiUrl: string
|
||||
hasToken: boolean
|
||||
} {
|
||||
return {
|
||||
apiUrl: getApiUrl(),
|
||||
hasToken: getToken() !== null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* MemoHome Core API
|
||||
*
|
||||
* This module provides core functionality that can be used by CLI and other applications.
|
||||
* All functions are independent of CLI-specific UI concerns (no chalk, ora, inquirer, etc.)
|
||||
*/
|
||||
|
||||
// Auth
|
||||
export {
|
||||
login,
|
||||
logout,
|
||||
isLoggedIn,
|
||||
getCurrentUser,
|
||||
getConfig,
|
||||
setConfig,
|
||||
getToken,
|
||||
getApiUrl,
|
||||
setToken,
|
||||
clearToken,
|
||||
setApiUrl,
|
||||
type LoginParams,
|
||||
type LoginResult,
|
||||
type UserInfo,
|
||||
type ConfigInfo,
|
||||
} from './auth'
|
||||
|
||||
// User
|
||||
export {
|
||||
listUsers,
|
||||
createUser,
|
||||
getUser,
|
||||
deleteUser,
|
||||
updateUserPassword,
|
||||
type CreateUserParams,
|
||||
type UpdatePasswordParams,
|
||||
} from './user'
|
||||
|
||||
// Model
|
||||
export {
|
||||
listModels,
|
||||
createModel,
|
||||
getModel,
|
||||
deleteModel,
|
||||
getDefaultModels,
|
||||
type CreateModelParams,
|
||||
type ModelListItem,
|
||||
} from './model'
|
||||
|
||||
// Agent
|
||||
export {
|
||||
chat,
|
||||
chatStream,
|
||||
type ChatParams,
|
||||
type StreamEvent,
|
||||
type StreamCallback,
|
||||
} from './agent'
|
||||
|
||||
// Memory
|
||||
export {
|
||||
searchMemory,
|
||||
addMemory,
|
||||
getMessages,
|
||||
filterMessages,
|
||||
type SearchMemoryParams,
|
||||
type AddMemoryParams,
|
||||
type GetMessagesParams,
|
||||
type FilterMessagesParams,
|
||||
} from './memory'
|
||||
|
||||
// Schedule
|
||||
export {
|
||||
listSchedules,
|
||||
createSchedule,
|
||||
getSchedule,
|
||||
updateSchedule,
|
||||
deleteSchedule,
|
||||
toggleSchedule,
|
||||
type CreateScheduleParams,
|
||||
type UpdateScheduleParams,
|
||||
} from './schedule'
|
||||
|
||||
// Settings
|
||||
export {
|
||||
getSettings,
|
||||
updateSettings,
|
||||
type UpdateSettingsParams,
|
||||
} from './settings'
|
||||
|
||||
// Debug
|
||||
export {
|
||||
ping,
|
||||
getConnectionInfo,
|
||||
type PingResult,
|
||||
} from './debug'
|
||||
|
||||
// Config
|
||||
export {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
ensureConfigDir,
|
||||
type Config,
|
||||
} from './config'
|
||||
|
||||
// Client
|
||||
export {
|
||||
createClient,
|
||||
requireAuth,
|
||||
} from './client'
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { createClient, requireAuth } from './client'
|
||||
import type { Memory, Message, MessageListResponse } from '../types'
|
||||
|
||||
export interface SearchMemoryParams {
|
||||
query: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface AddMemoryParams {
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface GetMessagesParams {
|
||||
page?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface FilterMessagesParams {
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Search memories
|
||||
*/
|
||||
export async function searchMemory(params: SearchMemoryParams): Promise<Memory[]> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.search.get({
|
||||
query: {
|
||||
q: params.query,
|
||||
limit: params.limit || 10,
|
||||
},
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Search failed')
|
||||
}
|
||||
|
||||
/**
|
||||
* Add memory
|
||||
*/
|
||||
export async function addMemory(params: AddMemoryParams): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.post({
|
||||
content: params.content,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (!data?.success) {
|
||||
throw new Error('Failed to add memory')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message history
|
||||
*/
|
||||
export async function getMessages(params: GetMessagesParams = {}): Promise<MessageListResponse> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.message.get({
|
||||
query: {
|
||||
page: params.page || 1,
|
||||
limit: params.limit || 20,
|
||||
},
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch messages')
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter messages by date range
|
||||
*/
|
||||
export async function filterMessages(params: FilterMessagesParams): Promise<Message[]> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.memory.message.filter.get({
|
||||
query: {
|
||||
startDate: params.startDate,
|
||||
endDate: params.endDate,
|
||||
},
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to filter messages')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { createClient, requireAuth } from './client'
|
||||
import type { Model, ApiResponse } from '../types'
|
||||
|
||||
export interface CreateModelParams {
|
||||
name: string
|
||||
modelId: string
|
||||
baseUrl: string
|
||||
apiKey: string
|
||||
clientType: string
|
||||
type?: 'chat' | 'embedding'
|
||||
dimensions?: number
|
||||
}
|
||||
|
||||
export interface ModelListItem {
|
||||
id: string
|
||||
model: Model
|
||||
}
|
||||
|
||||
/**
|
||||
* List all models
|
||||
*/
|
||||
export async function listModels(): Promise<ModelListItem[]> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model.get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
const data = response.data as { success?: boolean; items?: ModelListItem[] } | null
|
||||
if (data?.success && data?.items) {
|
||||
return data.items
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch model list')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create model configuration
|
||||
*/
|
||||
export async function createModel(params: CreateModelParams): Promise<Model> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
modelId: params.modelId,
|
||||
baseUrl: params.baseUrl,
|
||||
apiKey: params.apiKey,
|
||||
clientType: params.clientType,
|
||||
type: params.type || 'chat',
|
||||
}
|
||||
|
||||
// If embedding type, add dimensions
|
||||
if (params.type === 'embedding') {
|
||||
if (!params.dimensions) {
|
||||
throw new Error('Embedding models require dimensions to be specified')
|
||||
}
|
||||
payload.dimensions = params.dimensions
|
||||
}
|
||||
|
||||
const response = await client.model.post(payload)
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
const data = response.data as ApiResponse<Model> | null
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to create model configuration')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model by ID
|
||||
*/
|
||||
export async function getModel(id: string): Promise<Model> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
const data = response.data as ApiResponse<Model> | null
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch model configuration')
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete model
|
||||
*/
|
||||
export async function deleteModel(id: string): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.model({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default models
|
||||
*/
|
||||
export async function getDefaultModels(): Promise<{
|
||||
chat?: Model
|
||||
summary?: Model
|
||||
embedding?: Model
|
||||
}> {
|
||||
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(),
|
||||
])
|
||||
|
||||
const result: { chat?: Model; summary?: Model; embedding?: Model } = {}
|
||||
|
||||
const chatData = chatRes.data as ApiResponse<Model> | null
|
||||
if (chatData?.success && chatData.data) {
|
||||
result.chat = chatData.data
|
||||
}
|
||||
|
||||
const summaryData = summaryRes.data as ApiResponse<Model> | null
|
||||
if (summaryData?.success && summaryData.data) {
|
||||
result.summary = summaryData.data
|
||||
}
|
||||
|
||||
const embeddingData = embeddingRes.data as ApiResponse<Model> | null
|
||||
if (embeddingData?.success && embeddingData.data) {
|
||||
result.embedding = embeddingData.data
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { createClient, requireAuth } from './client'
|
||||
import type { Schedule } from '../types'
|
||||
|
||||
export interface CreateScheduleParams {
|
||||
title: string
|
||||
description?: string
|
||||
cronExpression: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface UpdateScheduleParams {
|
||||
title?: string
|
||||
description?: string
|
||||
cronExpression?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* List all schedules
|
||||
*/
|
||||
export async function listSchedules(): Promise<Schedule[]> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule.get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch scheduled tasks list')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create schedule
|
||||
*/
|
||||
export async function createSchedule(params: CreateScheduleParams): Promise<Schedule> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const payload: any = {
|
||||
title: params.title,
|
||||
cronExpression: params.cronExpression,
|
||||
enabled: params.enabled,
|
||||
}
|
||||
|
||||
if (params.description) {
|
||||
payload.description = params.description
|
||||
}
|
||||
|
||||
const response = await client.schedule.post(payload)
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to create scheduled task')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schedule by ID
|
||||
*/
|
||||
export async function getSchedule(id: string): Promise<Schedule> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch scheduled task')
|
||||
}
|
||||
|
||||
/**
|
||||
* Update schedule
|
||||
*/
|
||||
export async function updateSchedule(id: string, params: UpdateScheduleParams): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).put(params)
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete schedule
|
||||
*/
|
||||
export async function deleteSchedule(id: string): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.schedule({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle schedule enabled status
|
||||
*/
|
||||
export async function toggleSchedule(id: string): Promise<boolean> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
// First get current status
|
||||
const getResponse = await client.schedule({ id }).get()
|
||||
|
||||
if (getResponse.error) {
|
||||
throw new Error(getResponse.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const getData = getResponse.data as any
|
||||
if (getData?.success && getData?.data) {
|
||||
const currentEnabled = getData.data.enabled
|
||||
|
||||
// Update status
|
||||
const updateResponse = await client.schedule({ id }).put({
|
||||
enabled: !currentEnabled,
|
||||
})
|
||||
|
||||
if (updateResponse.error) {
|
||||
throw new Error(updateResponse.error.value)
|
||||
}
|
||||
|
||||
return !currentEnabled
|
||||
}
|
||||
|
||||
throw new Error('Failed to toggle task status')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { createClient, requireAuth } from './client'
|
||||
import type { Settings } from '../types'
|
||||
|
||||
export interface UpdateSettingsParams {
|
||||
language?: string
|
||||
maxContextLoadTime?: number
|
||||
defaultChatModel?: string
|
||||
defaultSummaryModel?: string
|
||||
defaultEmbeddingModel?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user settings
|
||||
*/
|
||||
export async function getSettings(): Promise<Settings> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.settings.get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch settings')
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user settings
|
||||
*/
|
||||
export async function updateSettings(params: UpdateSettingsParams): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.settings.put(params)
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (!data?.success) {
|
||||
throw new Error('Failed to update settings')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { createClient, requireAuth } from './client'
|
||||
import type { User } from '../types'
|
||||
|
||||
export interface CreateUserParams {
|
||||
username: string
|
||||
password: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface UpdatePasswordParams {
|
||||
userId: string
|
||||
password: string
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users
|
||||
*/
|
||||
export async function listUsers(): Promise<User[]> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user.get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch user list')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
*/
|
||||
export async function createUser(params: CreateUserParams): Promise<User> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user.post({
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
role: params.role,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to create user')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
*/
|
||||
export async function getUser(id: string): Promise<User> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id }).get()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = response.data as any
|
||||
if (data?.success && data?.data) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch user information')
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
*/
|
||||
export async function deleteUser(id: string): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id }).delete()
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user password
|
||||
*/
|
||||
export async function updateUserPassword(params: UpdatePasswordParams): Promise<void> {
|
||||
requireAuth()
|
||||
const client = createClient()
|
||||
|
||||
const response = await client.user({ id: params.userId }).password.patch({
|
||||
password: params.password,
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.value)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-59
@@ -1,62 +1,36 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* MemoHome CLI Package
|
||||
*
|
||||
* This package provides both:
|
||||
* 1. A command-line interface (CLI) for interacting with MemoHome API
|
||||
* 2. Core functionality that can be imported and used in other projects
|
||||
*
|
||||
* @example CLI Usage (from terminal)
|
||||
* ```bash
|
||||
* memohome auth login
|
||||
* memohome agent chat "Hello"
|
||||
* ```
|
||||
*
|
||||
* @example Core API Usage (from code)
|
||||
* ```typescript
|
||||
* import { login, chat, listModels } from '@memohome/cli'
|
||||
*
|
||||
* // Login
|
||||
* await login({ username: 'admin', password: 'password' })
|
||||
*
|
||||
* // Chat with agent
|
||||
* const response = await chat({ message: 'Hello' })
|
||||
*
|
||||
* // List models
|
||||
* const models = await listModels()
|
||||
* ```
|
||||
*/
|
||||
|
||||
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, startInteractiveMode } from './commands/agent'
|
||||
import { memoryCommands } from './commands/memory'
|
||||
import { configCommands } from './commands/config'
|
||||
import { scheduleCommands } from './commands/schedule'
|
||||
import { debugCommands } from './commands/debug'
|
||||
// Export all core functionality
|
||||
export * from './core'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program
|
||||
.name('memohome')
|
||||
.description(chalk.bold.blue('🏠 MemoHome Agent'))
|
||||
.version('1.0.0')
|
||||
|
||||
// Authentication commands
|
||||
const auth = program.command('auth').description('User authentication management')
|
||||
authCommands(auth)
|
||||
|
||||
// User management commands
|
||||
const user = program.command('user').description('User management (requires admin privileges)')
|
||||
userCommands(user)
|
||||
|
||||
// Model management commands
|
||||
const model = program.command('model').description('AI model configuration management')
|
||||
modelCommands(model)
|
||||
|
||||
// Agent conversation commands
|
||||
const agent = program.command('agent').description('Chat with AI Agent')
|
||||
agentCommands(agent)
|
||||
|
||||
// Memory management commands
|
||||
const memory = program.command('memory').description('Memory management')
|
||||
memoryCommands(memory)
|
||||
|
||||
// Config management commands
|
||||
const config = program.command('config').description('User configuration management')
|
||||
configCommands(config)
|
||||
|
||||
// Schedule management commands
|
||||
const schedule = program.command('schedule').description('Schedule management')
|
||||
scheduleCommands(schedule)
|
||||
|
||||
// Debug commands
|
||||
const debug = program.command('debug').description('Debug tools')
|
||||
debugCommands(debug)
|
||||
|
||||
// If no arguments provided, start interactive mode
|
||||
if (process.argv.length === 2) {
|
||||
startInteractiveMode().catch((error) => {
|
||||
console.error('Failed to start interactive mode:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
} else {
|
||||
program.parse()
|
||||
}
|
||||
// Export types
|
||||
export * from './types'
|
||||
|
||||
// Export utilities
|
||||
export * from './utils'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
/**
|
||||
* Format API error information
|
||||
*/
|
||||
@@ -40,24 +38,3 @@ export function formatError(error: unknown): string {
|
||||
return String(error)
|
||||
}
|
||||
|
||||
/**
|
||||
* Print error and exit
|
||||
*/
|
||||
export function exitWithError(message: string, error?: unknown): never {
|
||||
console.error(chalk.red(message))
|
||||
if (error) {
|
||||
console.error(chalk.red(formatError(error)))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Eden Treaty response errors
|
||||
*/
|
||||
export function handleApiError(response: { error?: { value: unknown } }, defaultMessage = 'Operation failed'): never {
|
||||
if (response.error) {
|
||||
exitWithError(defaultMessage, response.error.value)
|
||||
}
|
||||
exitWithError(defaultMessage, 'Unknown error')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user