mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
refactor: cli
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@memoh/client",
|
||||
"name": "@memoh/cli",
|
||||
"version": "1.0.0",
|
||||
"description": "Command line interface and core API for Memoh",
|
||||
"exports": {
|
||||
@@ -10,26 +10,27 @@
|
||||
"./cli": "./src/cli/index.ts"
|
||||
},
|
||||
"bin": {
|
||||
"memoh": "./src/cli/index.ts"
|
||||
"memoh": "./dist/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "bun run src/cli/index.ts",
|
||||
"dev": "bun run --watch src/cli/index.ts"
|
||||
"dev": "bun run --watch src/cli/index.ts",
|
||||
"build": "tsup",
|
||||
"build:watch": "tsup --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/eden": "^1.4.6",
|
||||
"elysia": "latest",
|
||||
"@memoh/sdk": "workspace:*",
|
||||
"commander": "^12.1.0",
|
||||
"chalk": "^5.4.1",
|
||||
"ora": "^8.1.1",
|
||||
"inquirer": "^12.3.0",
|
||||
"table": "^6.8.2",
|
||||
"node-fetch": "^3.3.2"
|
||||
"table": "^6.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"bun-types": "latest",
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"tsup": "^8.4.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"module": "src/index.ts",
|
||||
|
||||
+90
-92
@@ -5,55 +5,47 @@ import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
import readline from 'node:readline/promises'
|
||||
import { stdin as input, stdout as output } from 'node:process'
|
||||
import { apiRequest } from '../core/api'
|
||||
import { readConfig, TokenInfo } from '../utils/store'
|
||||
import { ensureAuth, getErrorMessage, resolveBotId, BotSummary } from './shared'
|
||||
|
||||
import {
|
||||
getBots,
|
||||
postBots,
|
||||
putBotsById,
|
||||
deleteBotsById,
|
||||
getModels,
|
||||
type BotsBot,
|
||||
type ModelsGetResponse,
|
||||
} from '@memoh/sdk'
|
||||
import { client } from '@memoh/sdk/client'
|
||||
import { ensureAuth, getErrorMessage, resolveBotId } from './shared'
|
||||
import { streamChat } from './stream'
|
||||
|
||||
type Bot = BotSummary & {
|
||||
metadata?: Record<string, unknown>
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
type BotListResponse = {
|
||||
items: Bot[]
|
||||
}
|
||||
|
||||
type ModelResponse = {
|
||||
model_id?: string
|
||||
model?: {
|
||||
model_id: string
|
||||
type: 'chat' | 'embedding'
|
||||
}
|
||||
type?: 'chat' | 'embedding'
|
||||
}
|
||||
|
||||
const getModelId = (item: ModelResponse) => item.model?.model_id ?? item.model_id ?? ''
|
||||
const getModelType = (item: ModelResponse) => item.model?.type ?? item.type ?? 'chat'
|
||||
const getModelId = (item: ModelsGetResponse) => item.model_id ?? ''
|
||||
const getModelType = (item: ModelsGetResponse) => item.type ?? 'chat'
|
||||
|
||||
const ensureModelsReady = async () => {
|
||||
const token = ensureAuth()
|
||||
const [chatModels, embeddingModels] = await Promise.all([
|
||||
apiRequest<ModelResponse[]>('/models?type=chat', {}, token),
|
||||
apiRequest<ModelResponse[]>('/models?type=embedding', {}, token),
|
||||
ensureAuth()
|
||||
const [chatResult, embeddingResult] = await Promise.all([
|
||||
getModels({ query: { type: 'chat' }, throwOnError: true }),
|
||||
getModels({ query: { type: 'embedding' }, throwOnError: true }),
|
||||
])
|
||||
if (chatModels.length === 0 || embeddingModels.length === 0) {
|
||||
const chatModels = chatResult.data ?? []
|
||||
const embeddingModels = embeddingResult.data ?? []
|
||||
if (!Array.isArray(chatModels) || chatModels.length === 0 || !Array.isArray(embeddingModels) || embeddingModels.length === 0) {
|
||||
console.log(chalk.red('Model configuration incomplete.'))
|
||||
console.log(chalk.yellow('At least one chat model and one embedding model are required.'))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
const renderBotsTable = (items: BotSummary[]) => {
|
||||
const renderBotsTable = (items: BotsBot[]) => {
|
||||
const rows: string[][] = [['ID', 'Name', 'Type', 'Active', 'Owner']]
|
||||
for (const bot of items) {
|
||||
rows.push([
|
||||
bot.id,
|
||||
bot.display_name || bot.id,
|
||||
bot.type,
|
||||
bot.id ?? '',
|
||||
bot.display_name || bot.id || '',
|
||||
bot.type ?? '',
|
||||
bot.is_active ? 'yes' : 'no',
|
||||
bot.owner_user_id,
|
||||
bot.owner_user_id ?? '',
|
||||
])
|
||||
}
|
||||
return table(rows)
|
||||
@@ -67,14 +59,17 @@ export const registerBotCommands = (program: Command) => {
|
||||
.description('List bots')
|
||||
.option('--owner <user_id>', 'Filter by owner user ID (admin only)')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
const query = opts.owner ? `?owner_id=${encodeURIComponent(String(opts.owner))}` : ''
|
||||
const resp = await apiRequest<BotListResponse>(`/bots${query}`, {}, token)
|
||||
if (!resp.items.length) {
|
||||
ensureAuth()
|
||||
const { data } = await getBots({
|
||||
query: opts.owner ? { owner_id: opts.owner } : undefined,
|
||||
throwOnError: true,
|
||||
})
|
||||
const items = data.items ?? []
|
||||
if (!items.length) {
|
||||
console.log(chalk.yellow('No bots found.'))
|
||||
return
|
||||
}
|
||||
console.log(renderBotsTable(resp.items))
|
||||
console.log(renderBotsTable(items))
|
||||
})
|
||||
|
||||
bot
|
||||
@@ -90,7 +85,7 @@ export const registerBotCommands = (program: Command) => {
|
||||
console.log(chalk.red('Use only one of --active or --inactive.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
let type = opts.type
|
||||
if (!type) {
|
||||
const answer = await inquirer.prompt<{ type: string }>([
|
||||
@@ -110,17 +105,20 @@ export const registerBotCommands = (program: Command) => {
|
||||
const name = opts.name ?? (await inquirer.prompt<{ name: string }>([
|
||||
{ type: 'input', name: 'name', message: 'Bot name (optional):', default: '' },
|
||||
])).name
|
||||
const payload: Record<string, unknown> = {
|
||||
const body: Record<string, unknown> = {
|
||||
type: String(type),
|
||||
}
|
||||
if (String(name).trim()) payload.display_name = String(name).trim()
|
||||
if (opts.avatar) payload.avatar_url = String(opts.avatar).trim()
|
||||
if (opts.active) payload.is_active = true
|
||||
if (opts.inactive) payload.is_active = false
|
||||
if (String(name).trim()) body.display_name = String(name).trim()
|
||||
if (opts.avatar) body.avatar_url = String(opts.avatar).trim()
|
||||
if (opts.active) body.is_active = true
|
||||
if (opts.inactive) body.is_active = false
|
||||
const spinner = ora('Creating bot...').start()
|
||||
try {
|
||||
const created = await apiRequest<Bot>('/bots', { method: 'POST', body: JSON.stringify(payload) }, token)
|
||||
spinner.succeed(`Bot created: ${created.display_name || created.id}`)
|
||||
const { data } = await postBots({
|
||||
body: body as any,
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed(`Bot created: ${data.display_name || data.id}`)
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to create bot')
|
||||
process.exit(1)
|
||||
@@ -140,14 +138,14 @@ export const registerBotCommands = (program: Command) => {
|
||||
console.log(chalk.red('Use only one of --active or --inactive.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, id)
|
||||
const payload: Record<string, unknown> = {}
|
||||
if (opts.name) payload.display_name = String(opts.name).trim()
|
||||
if (opts.avatar) payload.avatar_url = String(opts.avatar).trim()
|
||||
if (opts.active) payload.is_active = true
|
||||
if (opts.inactive) payload.is_active = false
|
||||
if (Object.keys(payload).length === 0) {
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(id)
|
||||
const body: Record<string, unknown> = {}
|
||||
if (opts.name) body.display_name = String(opts.name).trim()
|
||||
if (opts.avatar) body.avatar_url = String(opts.avatar).trim()
|
||||
if (opts.active) body.is_active = true
|
||||
if (opts.inactive) body.is_active = false
|
||||
if (Object.keys(body).length === 0) {
|
||||
const answers = await inquirer.prompt<{ name: string; avatar: string; status: string }>([
|
||||
{ type: 'input', name: 'name', message: 'Bot name (leave empty to skip):', default: '' },
|
||||
{ type: 'input', name: 'avatar', message: 'Bot avatar URL (leave empty to skip):', default: '' },
|
||||
@@ -162,18 +160,22 @@ export const registerBotCommands = (program: Command) => {
|
||||
],
|
||||
},
|
||||
])
|
||||
if (answers.name.trim()) payload.display_name = answers.name.trim()
|
||||
if (answers.avatar.trim()) payload.avatar_url = answers.avatar.trim()
|
||||
if (answers.status === 'active') payload.is_active = true
|
||||
if (answers.status === 'inactive') payload.is_active = false
|
||||
if (answers.name.trim()) body.display_name = answers.name.trim()
|
||||
if (answers.avatar.trim()) body.avatar_url = answers.avatar.trim()
|
||||
if (answers.status === 'active') body.is_active = true
|
||||
if (answers.status === 'inactive') body.is_active = false
|
||||
}
|
||||
if (Object.keys(payload).length === 0) {
|
||||
if (Object.keys(body).length === 0) {
|
||||
console.log(chalk.red('No updates provided.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const spinner = ora('Updating bot...').start()
|
||||
try {
|
||||
await apiRequest(`/bots/${encodeURIComponent(botId)}`, { method: 'PUT', body: JSON.stringify(payload) }, token)
|
||||
await putBotsById({
|
||||
path: { id: botId },
|
||||
body: body as any,
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Bot updated')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to update bot')
|
||||
@@ -186,15 +188,18 @@ export const registerBotCommands = (program: Command) => {
|
||||
.description('Delete a bot')
|
||||
.argument('[id]')
|
||||
.action(async (id) => {
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, id)
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(id)
|
||||
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
|
||||
{ type: 'confirm', name: 'confirmed', message: `Delete bot ${botId}?`, default: false },
|
||||
])
|
||||
if (!confirmed) return
|
||||
const spinner = ora('Deleting bot...').start()
|
||||
try {
|
||||
await apiRequest(`/bots/${encodeURIComponent(botId)}`, { method: 'DELETE' }, token)
|
||||
await deleteBotsById({
|
||||
path: { id: botId },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Bot deleted')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to delete bot')
|
||||
@@ -206,15 +211,12 @@ export const registerBotCommands = (program: Command) => {
|
||||
.command('chat')
|
||||
.description('Chat with a bot (stream)')
|
||||
.argument('[id]')
|
||||
.option('--session <id>', 'Reuse a session id')
|
||||
.action(async (id, opts) => {
|
||||
.action(async (id) => {
|
||||
await ensureModelsReady()
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, id)
|
||||
const config = readConfig()
|
||||
const sessionId = String(opts.session || config.session_id)
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(id)
|
||||
const rl = readline.createInterface({ input, output })
|
||||
console.log(chalk.green(`Chatting with ${chalk.bold(botId)} (session ${sessionId}). Type \`exit\` to quit.`))
|
||||
console.log(chalk.green(`Chatting with ${chalk.bold(botId)}. Type \`exit\` to quit.`))
|
||||
while (true) {
|
||||
const line = (await rl.question(chalk.cyan('> '))).trim()
|
||||
if (!line) {
|
||||
@@ -224,14 +226,7 @@ export const registerBotCommands = (program: Command) => {
|
||||
if (line.toLowerCase() === 'exit') {
|
||||
break
|
||||
}
|
||||
try {
|
||||
const ok = await streamChat(line, botId, sessionId, token)
|
||||
if (!ok) {
|
||||
console.log(chalk.red('Chat failed or stream unavailable.'))
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.log(chalk.red(getErrorMessage(err) || 'Chat failed'))
|
||||
}
|
||||
await streamChat(line, botId)
|
||||
}
|
||||
rl.close()
|
||||
})
|
||||
@@ -243,8 +238,8 @@ export const registerBotCommands = (program: Command) => {
|
||||
.option('--as <usage>', 'chat | memory | embedding')
|
||||
.option('--model <model_id>', 'Model ID')
|
||||
.action(async (id, opts) => {
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, id)
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(id)
|
||||
let enableAs = opts.as
|
||||
if (!enableAs) {
|
||||
const answer = await inquirer.prompt<{ usage: string }>([{
|
||||
@@ -260,9 +255,10 @@ export const registerBotCommands = (program: Command) => {
|
||||
console.log(chalk.red('Enable as must be one of chat, memory, embedding.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const models = await apiRequest<ModelResponse[]>('/models', {}, token)
|
||||
const { data: models } = await getModels({ throwOnError: true })
|
||||
const modelList = Array.isArray(models) ? models as ModelsGetResponse[] : []
|
||||
const requiredType = enableAs === 'embedding' ? 'embedding' : 'chat'
|
||||
const candidates = models.filter(m => getModelType(m) === requiredType)
|
||||
const candidates = modelList.filter(m => getModelType(m) === requiredType)
|
||||
if (candidates.length === 0) {
|
||||
console.log(chalk.red(`No ${requiredType} models available.`))
|
||||
process.exit(1)
|
||||
@@ -282,16 +278,19 @@ export const registerBotCommands = (program: Command) => {
|
||||
console.log(chalk.red('Selected model not found.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const payload: Record<string, unknown> = {}
|
||||
if (enableAs === 'chat') payload.chat_model_id = getModelId(selected)
|
||||
if (enableAs === 'memory') payload.memory_model_id = getModelId(selected)
|
||||
if (enableAs === 'embedding') payload.embedding_model_id = getModelId(selected)
|
||||
const body: Record<string, unknown> = {}
|
||||
if (enableAs === 'chat') body.chat_model_id = getModelId(selected)
|
||||
if (enableAs === 'memory') body.memory_model_id = getModelId(selected)
|
||||
if (enableAs === 'embedding') body.embedding_model_id = getModelId(selected)
|
||||
const spinner = ora('Updating bot settings...').start()
|
||||
try {
|
||||
await apiRequest(`/bots/${encodeURIComponent(botId)}/settings`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload),
|
||||
}, token)
|
||||
// Use raw client because bot_id path parameter is not typed in SDK
|
||||
await client.put({
|
||||
url: `/bots/${encodeURIComponent(botId)}/settings`,
|
||||
body,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Model enabled')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to enable model')
|
||||
@@ -299,4 +298,3 @@ export const registerBotCommands = (program: Command) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,77 +4,38 @@ import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { table } from 'table'
|
||||
|
||||
import { apiRequest } from '../core/api'
|
||||
import {
|
||||
getChannels,
|
||||
getChannelsByPlatform,
|
||||
getBotsByIdChannelByPlatform,
|
||||
putBotsByIdChannelByPlatform,
|
||||
getUsersMeChannelsByPlatform,
|
||||
putUsersMeChannelsByPlatform,
|
||||
type HandlersChannelMeta,
|
||||
} from '@memoh/sdk'
|
||||
import { ensureAuth, getErrorMessage, resolveBotId } from './shared'
|
||||
|
||||
type ChannelFieldSchema = {
|
||||
type: 'string' | 'secret' | 'bool' | 'number' | 'enum'
|
||||
required: boolean
|
||||
title?: string
|
||||
description?: string
|
||||
enum?: string[]
|
||||
example?: unknown
|
||||
}
|
||||
|
||||
type ChannelConfigSchema = {
|
||||
version: number
|
||||
fields: Record<string, ChannelFieldSchema>
|
||||
}
|
||||
|
||||
type ChannelMeta = {
|
||||
type: string
|
||||
display_name: string
|
||||
configless: boolean
|
||||
capabilities: Record<string, boolean>
|
||||
config_schema: ChannelConfigSchema
|
||||
user_config_schema: ChannelConfigSchema
|
||||
}
|
||||
|
||||
type ChannelUserBinding = {
|
||||
id: string
|
||||
channel_type: string
|
||||
user_id: string
|
||||
config: Record<string, unknown>
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
type ChannelConfig = {
|
||||
id: string
|
||||
bot_id: string
|
||||
channel_type: string
|
||||
credentials: Record<string, unknown>
|
||||
external_identity: string
|
||||
self_identity: Record<string, unknown>
|
||||
routing: Record<string, unknown>
|
||||
capabilities: Record<string, unknown>
|
||||
status: string
|
||||
verified_at: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
const renderChannelsTable = (items: ChannelMeta[]) => {
|
||||
const renderChannelsTable = (items: HandlersChannelMeta[]) => {
|
||||
const rows: string[][] = [['Type', 'Name', 'Configless']]
|
||||
for (const item of items) {
|
||||
rows.push([item.type, item.display_name, item.configless ? 'yes' : 'no'])
|
||||
rows.push([item.type ?? '', item.display_name ?? '', item.configless ? 'yes' : 'no'])
|
||||
}
|
||||
return table(rows)
|
||||
}
|
||||
|
||||
const fetchChannels = async (token: ReturnType<typeof ensureAuth>) => {
|
||||
return apiRequest<ChannelMeta[]>('/channels', {}, token)
|
||||
const fetchChannelList = async () => {
|
||||
const { data } = await getChannels({ throwOnError: true })
|
||||
return data as HandlersChannelMeta[]
|
||||
}
|
||||
|
||||
const resolveChannelType = async (
|
||||
token: ReturnType<typeof ensureAuth>,
|
||||
preset?: string,
|
||||
options?: { allowConfigless?: boolean }
|
||||
) => {
|
||||
if (preset && preset.trim()) {
|
||||
return preset.trim()
|
||||
}
|
||||
const channels = await fetchChannels(token)
|
||||
const channels = await fetchChannelList()
|
||||
const allowConfigless = options?.allowConfigless ?? false
|
||||
const candidates = channels.filter(item => allowConfigless || !item.configless)
|
||||
if (candidates.length === 0) {
|
||||
@@ -167,8 +128,8 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.command('list')
|
||||
.description('List available channels')
|
||||
.action(async () => {
|
||||
const token = ensureAuth()
|
||||
const channels = await fetchChannels(token)
|
||||
ensureAuth()
|
||||
const channels = await fetchChannelList()
|
||||
if (!channels.length) {
|
||||
console.log(chalk.yellow('No channels available.'))
|
||||
return
|
||||
@@ -181,10 +142,13 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.description('Show channel meta and schema')
|
||||
.argument('[type]')
|
||||
.action(async (type) => {
|
||||
const token = ensureAuth()
|
||||
const channelType = await resolveChannelType(token, type, { allowConfigless: true })
|
||||
const meta = await apiRequest<ChannelMeta>(`/channels/${encodeURIComponent(channelType)}`, {}, token)
|
||||
console.log(JSON.stringify(meta, null, 2))
|
||||
ensureAuth()
|
||||
const channelType = await resolveChannelType(type, { allowConfigless: true })
|
||||
const { data } = await getChannelsByPlatform({
|
||||
path: { platform: channelType },
|
||||
throwOnError: true,
|
||||
})
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
})
|
||||
|
||||
const config = channel.command('config').description('Bot channel configuration')
|
||||
@@ -195,11 +159,14 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.argument('[bot_id]')
|
||||
.option('--type <type>', 'Channel type')
|
||||
.action(async (botId, opts) => {
|
||||
const token = ensureAuth()
|
||||
const resolvedBotId = await resolveBotId(token, botId)
|
||||
const channelType = await resolveChannelType(token, opts.type)
|
||||
const resp = await apiRequest<ChannelConfig>(`/bots/${encodeURIComponent(resolvedBotId)}/channel/${encodeURIComponent(channelType)}`, {}, token)
|
||||
console.log(JSON.stringify(resp, null, 2))
|
||||
ensureAuth()
|
||||
const resolvedBotId = await resolveBotId(botId)
|
||||
const channelType = await resolveChannelType(opts.type)
|
||||
const { data } = await getBotsByIdChannelByPlatform({
|
||||
path: { id: resolvedBotId, platform: channelType },
|
||||
throwOnError: true,
|
||||
})
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
})
|
||||
|
||||
config
|
||||
@@ -212,9 +179,9 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.option('--encrypt_key <encrypt_key>')
|
||||
.option('--verification_token <verification_token>')
|
||||
.action(async (botId, opts) => {
|
||||
const token = ensureAuth()
|
||||
const resolvedBotId = await resolveBotId(token, botId)
|
||||
const channelType = await resolveChannelType(token, opts.type)
|
||||
ensureAuth()
|
||||
const resolvedBotId = await resolveBotId(botId)
|
||||
const channelType = await resolveChannelType(opts.type)
|
||||
if (channelType !== 'feishu') {
|
||||
console.log(chalk.red(`Channel type ${channelType} is not supported by this command.`))
|
||||
process.exit(1)
|
||||
@@ -222,10 +189,11 @@ export const registerChannelCommands = (program: Command) => {
|
||||
const credentials = await collectFeishuCredentials(opts)
|
||||
const spinner = ora('Updating channel config...').start()
|
||||
try {
|
||||
await apiRequest(`/bots/${encodeURIComponent(resolvedBotId)}/channel/${encodeURIComponent(channelType)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ credentials }),
|
||||
}, token)
|
||||
await putBotsByIdChannelByPlatform({
|
||||
path: { id: resolvedBotId, platform: channelType },
|
||||
body: { credentials },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Channel config updated')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to update channel config')
|
||||
@@ -240,10 +208,13 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.description('Get current user channel binding')
|
||||
.option('--type <type>', 'Channel type')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
const channelType = await resolveChannelType(token, opts.type)
|
||||
const resp = await apiRequest<ChannelUserBinding>(`/users/me/channels/${encodeURIComponent(channelType)}`, {}, token)
|
||||
console.log(JSON.stringify(resp, null, 2))
|
||||
ensureAuth()
|
||||
const channelType = await resolveChannelType(opts.type)
|
||||
const { data } = await getUsersMeChannelsByPlatform({
|
||||
path: { platform: channelType },
|
||||
throwOnError: true,
|
||||
})
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
})
|
||||
|
||||
binding
|
||||
@@ -253,8 +224,8 @@ export const registerChannelCommands = (program: Command) => {
|
||||
.option('--open_id <open_id>')
|
||||
.option('--user_id <user_id>')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
const channelType = await resolveChannelType(token, opts.type)
|
||||
ensureAuth()
|
||||
const channelType = await resolveChannelType(opts.type)
|
||||
if (channelType !== 'feishu') {
|
||||
console.log(chalk.red(`Channel type ${channelType} is not supported by this command.`))
|
||||
process.exit(1)
|
||||
@@ -262,10 +233,11 @@ export const registerChannelCommands = (program: Command) => {
|
||||
const configPayload = await collectFeishuUserConfig(opts)
|
||||
const spinner = ora('Updating user binding...').start()
|
||||
try {
|
||||
await apiRequest(`/users/me/channels/${encodeURIComponent(channelType)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ config: configPayload }),
|
||||
}, token)
|
||||
await putUsersMeChannelsByPlatform({
|
||||
path: { platform: channelType },
|
||||
body: { config: configPayload },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('User binding updated')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to update user binding')
|
||||
@@ -273,4 +245,3 @@ export const registerChannelCommands = (program: Command) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+232
-244
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env bun
|
||||
import { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
@@ -8,77 +7,44 @@ import readline from 'node:readline/promises'
|
||||
import { stdin as input, stdout as output } from 'node:process'
|
||||
|
||||
import packageJson from '../../package.json'
|
||||
import { apiRequest } from '../core/api'
|
||||
import { setupClient, client } from '../core/client'
|
||||
import { registerBotCommands } from './bot'
|
||||
import { registerChannelCommands } from './channel'
|
||||
import { streamChat } from './stream'
|
||||
import {
|
||||
readConfig,
|
||||
writeConfig,
|
||||
readToken,
|
||||
writeToken,
|
||||
clearToken,
|
||||
TokenInfo,
|
||||
getBaseURL,
|
||||
type TokenInfo,
|
||||
} from '../utils/store'
|
||||
import { ensureAuth, getErrorMessage, resolveBotId } from './shared'
|
||||
|
||||
type Provider = {
|
||||
id: string
|
||||
name: string
|
||||
client_type: string
|
||||
base_url: string
|
||||
api_key?: string
|
||||
}
|
||||
import {
|
||||
postAuthLogin,
|
||||
getUsersMe,
|
||||
getProviders,
|
||||
postProviders,
|
||||
getProvidersNameByName,
|
||||
deleteProvidersById,
|
||||
getModels,
|
||||
postModels,
|
||||
deleteModelsModelByModelId,
|
||||
type ProvidersGetResponse,
|
||||
type ModelsGetResponse,
|
||||
type ScheduleSchedule,
|
||||
type ScheduleListResponse,
|
||||
} from '@memoh/sdk'
|
||||
|
||||
type Model = {
|
||||
model_id: string
|
||||
name?: string
|
||||
llm_provider_id: string
|
||||
is_multimodal: boolean
|
||||
type: 'chat' | 'embedding'
|
||||
dimensions?: number
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Initialize SDK client
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type ModelResponse = Partial<Model> & {
|
||||
model_id?: string
|
||||
model?: Model
|
||||
}
|
||||
setupClient()
|
||||
|
||||
type Schedule = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
pattern: string
|
||||
max_calls?: number | null
|
||||
current_calls?: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
enabled: boolean
|
||||
command: string
|
||||
user_id?: string
|
||||
}
|
||||
|
||||
type ScheduleListResponse = {
|
||||
items: Schedule[]
|
||||
}
|
||||
|
||||
|
||||
type Bot = {
|
||||
id: string
|
||||
name?: string
|
||||
display_name?: string
|
||||
description?: string
|
||||
avatar?: string
|
||||
type?: string
|
||||
owner_user_id: string
|
||||
is_public?: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
type BotListResponse = {
|
||||
items: Bot[]
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Program setup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const program = new Command()
|
||||
program
|
||||
@@ -89,90 +55,48 @@ program
|
||||
registerBotCommands(program)
|
||||
registerChannelCommands(program)
|
||||
|
||||
const ensureAuth = () => {
|
||||
const token = readToken()
|
||||
if (!token?.access_token) {
|
||||
console.log(chalk.red('Not logged in. Run `memoh login` first.'))
|
||||
process.exit(1)
|
||||
}
|
||||
return token
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Model/Provider helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const getModelId = (item: ModelsGetResponse) => item.model_id ?? ''
|
||||
const getProviderId = (item: ModelsGetResponse) => item.llm_provider_id ?? ''
|
||||
const getModelType = (item: ModelsGetResponse) => item.type ?? 'chat'
|
||||
const getModelMultimodal = (item: ModelsGetResponse) => item.is_multimodal ?? false
|
||||
|
||||
const ensureModelsReady = async () => {
|
||||
const token = ensureAuth()
|
||||
const [chatModels, embeddingModels] = await Promise.all([
|
||||
apiRequest<ModelResponse[]>('/models?type=chat', {}, token),
|
||||
apiRequest<ModelResponse[]>('/models?type=embedding', {}, token),
|
||||
ensureAuth()
|
||||
const [chatResult, embeddingResult] = await Promise.all([
|
||||
getModels({ query: { type: 'chat' }, throwOnError: true }),
|
||||
getModels({ query: { type: 'embedding' }, throwOnError: true }),
|
||||
])
|
||||
if (chatModels.length === 0 || embeddingModels.length === 0) {
|
||||
const chatModels = chatResult.data ?? []
|
||||
const embeddingModels = embeddingResult.data ?? []
|
||||
if (!Array.isArray(chatModels) || chatModels.length === 0 ||
|
||||
!Array.isArray(embeddingModels) || embeddingModels.length === 0) {
|
||||
console.log(chalk.red('Model configuration incomplete.'))
|
||||
console.log(chalk.yellow('At least one chat model and one embedding model are required.'))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
const getErrorMessage = (err: unknown) => {
|
||||
if (err && typeof err === 'object' && 'message' in err) {
|
||||
const value = (err as { message?: unknown }).message
|
||||
if (typeof value === 'string') return value
|
||||
}
|
||||
return 'Unknown error'
|
||||
}
|
||||
|
||||
const resolveBotId = async (token: TokenInfo, preset?: string) => {
|
||||
if (preset && preset.trim()) {
|
||||
return preset.trim()
|
||||
}
|
||||
const spinner = ora('Fetching bots...').start()
|
||||
let bots: Bot[] = []
|
||||
try {
|
||||
const resp = await apiRequest<BotListResponse>('/bots', {}, token)
|
||||
bots = resp.items
|
||||
spinner.stop()
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(`Failed to fetch bots: ${getErrorMessage(err)}`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (bots.length === 0) {
|
||||
console.log(chalk.yellow('No bots found. Please create a bot first.'))
|
||||
process.exit(0)
|
||||
}
|
||||
const { botId } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'botId',
|
||||
message: 'Select a bot to chat with:',
|
||||
choices: bots.map(b => ({
|
||||
name: `${b.display_name || b.name || b.id || 'unknown'} ${b.type ? chalk.gray(b.type) : ''}`.trim(),
|
||||
value: b.id,
|
||||
})),
|
||||
},
|
||||
])
|
||||
return botId as string
|
||||
}
|
||||
|
||||
const getModelId = (item: ModelResponse) => item.model?.model_id ?? item.model_id ?? ''
|
||||
const getProviderId = (item: ModelResponse) => item.model?.llm_provider_id ?? item.llm_provider_id ?? ''
|
||||
const getModelType = (item: ModelResponse) => item.model?.type ?? item.type ?? 'chat'
|
||||
const getModelMultimodal = (item: ModelResponse) => item.model?.is_multimodal ?? item.is_multimodal ?? false
|
||||
|
||||
const renderProvidersTable = (providers: Provider[], models: ModelResponse[]) => {
|
||||
const renderProvidersTable = (providers: ProvidersGetResponse[], models: ModelsGetResponse[]) => {
|
||||
const rows: string[][] = [['Provider', 'Type', 'Base URL', 'Models']]
|
||||
for (const provider of providers) {
|
||||
const providerModels = models
|
||||
.filter(m => getProviderId(m) === provider.id)
|
||||
.map(m => `${getModelId(m)} (${getModelType(m)})`)
|
||||
rows.push([
|
||||
provider.name,
|
||||
provider.client_type,
|
||||
provider.base_url,
|
||||
provider.name ?? '',
|
||||
provider.client_type ?? '',
|
||||
provider.base_url ?? '',
|
||||
providerModels.join(', ') || '-',
|
||||
])
|
||||
}
|
||||
return table(rows)
|
||||
}
|
||||
|
||||
const renderModelsTable = (models: ModelResponse[], providers: Provider[]) => {
|
||||
const renderModelsTable = (models: ModelsGetResponse[], providers: ProvidersGetResponse[]) => {
|
||||
const providerMap = new Map(providers.map(p => [p.id, p.name]))
|
||||
const rows: string[][] = [['Model ID', 'Type', 'Provider', 'Multimodal']]
|
||||
for (const item of models) {
|
||||
@@ -186,22 +110,26 @@ const renderModelsTable = (models: ModelResponse[], providers: Provider[]) => {
|
||||
return table(rows)
|
||||
}
|
||||
|
||||
const renderSchedulesTable = (items: Schedule[]) => {
|
||||
const renderSchedulesTable = (items: ScheduleSchedule[]) => {
|
||||
const rows: string[][] = [['ID', 'Name', 'Pattern', 'Enabled', 'Max Calls', 'Current Calls', 'Command']]
|
||||
for (const item of items) {
|
||||
rows.push([
|
||||
item.id,
|
||||
item.name,
|
||||
item.pattern,
|
||||
item.id ?? '',
|
||||
item.name ?? '',
|
||||
item.pattern ?? '',
|
||||
item.enabled ? 'yes' : 'no',
|
||||
item.max_calls === null || item.max_calls === undefined ? '-' : String(item.max_calls),
|
||||
item.current_calls === undefined ? '-' : String(item.current_calls),
|
||||
item.command,
|
||||
item.command ?? '',
|
||||
])
|
||||
}
|
||||
return table(rows)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Auth commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
program
|
||||
.command('login')
|
||||
.description('Login')
|
||||
@@ -212,14 +140,21 @@ program
|
||||
])
|
||||
const spinner = ora('Logging in...').start()
|
||||
try {
|
||||
const resp = await apiRequest<TokenInfo>('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
const { data } = await postAuthLogin({
|
||||
body: {
|
||||
username: answers.username,
|
||||
password: answers.password,
|
||||
}),
|
||||
}, null)
|
||||
writeToken(resp)
|
||||
},
|
||||
throwOnError: true,
|
||||
})
|
||||
const tokenInfo: TokenInfo = {
|
||||
access_token: data.access_token ?? '',
|
||||
token_type: data.token_type ?? 'bearer',
|
||||
expires_at: data.expires_at ?? '',
|
||||
user_id: data.user_id ?? '',
|
||||
username: data.username,
|
||||
}
|
||||
writeToken(tokenInfo)
|
||||
spinner.succeed('Logged in')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Login failed')
|
||||
@@ -238,33 +173,25 @@ program
|
||||
program
|
||||
.command('whoami')
|
||||
.description('Show current user')
|
||||
.action(() => {
|
||||
const token = readToken()
|
||||
if (!token?.access_token) {
|
||||
console.log(chalk.red('Not logged in.'))
|
||||
process.exit(1)
|
||||
}
|
||||
if (token.username) {
|
||||
console.log(`username: ${token.username}`)
|
||||
}
|
||||
if (token.user_id) {
|
||||
console.log(`user_id: ${token.user_id}`)
|
||||
return
|
||||
}
|
||||
const payload = token.access_token.split('.')[1]
|
||||
if (!payload) {
|
||||
console.log(chalk.yellow('Token found but payload missing.'))
|
||||
return
|
||||
}
|
||||
const decoded = Buffer.from(payload, 'base64').toString('utf-8')
|
||||
.action(async () => {
|
||||
const token = ensureAuth()
|
||||
try {
|
||||
const data = JSON.parse(decoded)
|
||||
console.log(`user_id: ${data.user_id ?? data.sub ?? 'unknown'}`)
|
||||
const { data } = await getUsersMe({ throwOnError: true })
|
||||
if (data.username) console.log(`username: ${data.username}`)
|
||||
if (data.display_name) console.log(`display_name: ${data.display_name}`)
|
||||
if (data.id) console.log(`user_id: ${data.id}`)
|
||||
if (data.role) console.log(`role: ${data.role}`)
|
||||
} catch {
|
||||
console.log(chalk.yellow('Unable to parse token payload.'))
|
||||
// Fallback to token info if API call fails
|
||||
if (token.username) console.log(`username: ${token.username}`)
|
||||
if (token.user_id) console.log(`user_id: ${token.user_id}`)
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const configCmd = program
|
||||
.command('config')
|
||||
.description('Show or update current config')
|
||||
@@ -301,6 +228,10 @@ configCmd
|
||||
console.log(chalk.green('Config updated'))
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const provider = program.command('provider').description('Provider management')
|
||||
|
||||
provider
|
||||
@@ -308,12 +239,20 @@ provider
|
||||
.description('List providers')
|
||||
.option('--provider <name>', 'Filter by provider name')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
const providers = opts.provider
|
||||
? [await apiRequest<Provider>(`/providers/name/${encodeURIComponent(opts.provider)}`, {}, token)]
|
||||
: await apiRequest<Provider[]>('/providers', {}, token)
|
||||
const models = await apiRequest<ModelResponse[]>('/models', {}, token)
|
||||
console.log(renderProvidersTable(providers, models))
|
||||
ensureAuth()
|
||||
let providers: ProvidersGetResponse[]
|
||||
if (opts.provider) {
|
||||
const { data } = await getProvidersNameByName({
|
||||
path: { name: opts.provider },
|
||||
throwOnError: true,
|
||||
})
|
||||
providers = [data]
|
||||
} else {
|
||||
const { data } = await getProviders({ throwOnError: true })
|
||||
providers = data as ProvidersGetResponse[]
|
||||
}
|
||||
const { data: models } = await getModels({ throwOnError: true })
|
||||
console.log(renderProvidersTable(providers, models as ModelsGetResponse[]))
|
||||
})
|
||||
|
||||
provider
|
||||
@@ -324,7 +263,7 @@ provider
|
||||
.option('--base_url <url>')
|
||||
.option('--api_key <key>')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
const questions = []
|
||||
if (!opts.name) questions.push({ type: 'input', name: 'name', message: 'Provider name:' })
|
||||
if (!opts.type) {
|
||||
@@ -338,15 +277,17 @@ provider
|
||||
if (!opts.base_url) questions.push({ type: 'input', name: 'base_url', message: 'Base URL:' })
|
||||
if (!opts.api_key) questions.push({ type: 'password', name: 'api_key', message: 'API key:' })
|
||||
const answers = questions.length ? await inquirer.prompt(questions) : {}
|
||||
const payload = {
|
||||
name: opts.name ?? answers.name,
|
||||
client_type: opts.type ?? answers.client_type,
|
||||
base_url: opts.base_url ?? answers.base_url,
|
||||
api_key: opts.api_key ?? answers.api_key,
|
||||
}
|
||||
const spinner = ora('Creating provider...').start()
|
||||
try {
|
||||
await apiRequest('/providers', { method: 'POST', body: JSON.stringify(payload) }, token)
|
||||
await postProviders({
|
||||
body: {
|
||||
name: opts.name ?? answers.name,
|
||||
client_type: opts.type ?? answers.client_type,
|
||||
base_url: opts.base_url ?? answers.base_url,
|
||||
api_key: opts.api_key ?? answers.api_key,
|
||||
},
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Provider created')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to create provider')
|
||||
@@ -359,15 +300,21 @@ provider
|
||||
.description('Delete provider')
|
||||
.option('--provider <name>', 'Provider name')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
if (!opts.provider) {
|
||||
console.log(chalk.red('Provider name is required.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const providerInfo = await apiRequest<Provider>(`/providers/name/${encodeURIComponent(opts.provider)}`, {}, token)
|
||||
const { data: providerInfo } = await getProvidersNameByName({
|
||||
path: { name: opts.provider },
|
||||
throwOnError: true,
|
||||
})
|
||||
const spinner = ora('Deleting provider...').start()
|
||||
try {
|
||||
await apiRequest(`/providers/${providerInfo.id}`, { method: 'DELETE' }, token)
|
||||
await deleteProvidersById({
|
||||
path: { id: providerInfo.id! },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Provider deleted')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to delete provider')
|
||||
@@ -375,18 +322,25 @@ provider
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Model commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const model = program.command('model').description('Model management')
|
||||
|
||||
model
|
||||
.command('list')
|
||||
.description('List models')
|
||||
.action(async () => {
|
||||
const token = ensureAuth()
|
||||
const [models, providers] = await Promise.all([
|
||||
apiRequest<ModelResponse[]>('/models', {}, token),
|
||||
apiRequest<Provider[]>('/providers', {}, token),
|
||||
ensureAuth()
|
||||
const [modelsResult, providersResult] = await Promise.all([
|
||||
getModels({ throwOnError: true }),
|
||||
getProviders({ throwOnError: true }),
|
||||
])
|
||||
console.log(renderModelsTable(models, providers))
|
||||
console.log(renderModelsTable(
|
||||
modelsResult.data as ModelsGetResponse[],
|
||||
providersResult.data as ProvidersGetResponse[],
|
||||
))
|
||||
})
|
||||
|
||||
model
|
||||
@@ -399,8 +353,9 @@ model
|
||||
.option('--dimensions <dimensions>')
|
||||
.option('--multimodal', 'Is multimodal')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
const providers = await apiRequest<Provider[]>('/providers', {}, token)
|
||||
ensureAuth()
|
||||
const { data: providerList } = await getProviders({ throwOnError: true })
|
||||
const providers = providerList as ProvidersGetResponse[]
|
||||
let provider = providers.find(p => p.name === opts.provider)
|
||||
if (!provider) {
|
||||
const answer = await inquirer.prompt([{
|
||||
@@ -435,17 +390,19 @@ model
|
||||
process.exit(1)
|
||||
}
|
||||
const isMultimodal = Boolean(opts.multimodal)
|
||||
const payload = {
|
||||
model_id: modelId,
|
||||
name: opts.name ?? modelId,
|
||||
llm_provider_id: provider.id,
|
||||
is_multimodal: isMultimodal,
|
||||
type: modelType,
|
||||
dimensions,
|
||||
}
|
||||
const spinner = ora('Creating model...').start()
|
||||
try {
|
||||
await apiRequest('/models', { method: 'POST', body: JSON.stringify(payload) }, token)
|
||||
await postModels({
|
||||
body: {
|
||||
model_id: modelId,
|
||||
name: opts.name ?? modelId,
|
||||
llm_provider_id: provider.id,
|
||||
is_multimodal: isMultimodal,
|
||||
type: modelType,
|
||||
dimensions,
|
||||
},
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Model created')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to create model')
|
||||
@@ -458,14 +415,17 @@ model
|
||||
.description('Delete model')
|
||||
.option('--model <model>')
|
||||
.action(async (opts) => {
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
if (!opts.model) {
|
||||
console.log(chalk.red('Model name is required.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const spinner = ora('Deleting model...').start()
|
||||
try {
|
||||
await apiRequest(`/models/model/${encodeURIComponent(opts.model)}`, { method: 'DELETE' }, token)
|
||||
await deleteModelsModelByModelId({
|
||||
path: { modelId: opts.model },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Model deleted')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to delete model')
|
||||
@@ -473,15 +433,29 @@ model
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Schedule commands (uses raw client due to untyped bot_id path param)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const schedule = program.command('schedule').description('Schedule management')
|
||||
.option('--bot <id>', 'Bot ID (required for schedule operations)')
|
||||
|
||||
const resolveScheduleBotId = async (opts: { bot?: string }) => {
|
||||
return await resolveBotId(opts.bot)
|
||||
}
|
||||
|
||||
schedule
|
||||
.command('list')
|
||||
.description('List schedules')
|
||||
.action(async () => {
|
||||
const token = ensureAuth()
|
||||
const resp = await apiRequest<ScheduleListResponse>('/schedule', {}, token)
|
||||
if (!resp.items.length) {
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const { data } = await client.get({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule`,
|
||||
throwOnError: true,
|
||||
})
|
||||
const resp = data as ScheduleListResponse
|
||||
if (!resp.items?.length) {
|
||||
console.log(chalk.yellow('No schedules found.'))
|
||||
return
|
||||
}
|
||||
@@ -493,9 +467,13 @@ schedule
|
||||
.description('Get schedule')
|
||||
.argument('<id>')
|
||||
.action(async (id) => {
|
||||
const token = ensureAuth()
|
||||
const resp = await apiRequest<Schedule>(`/schedule/${encodeURIComponent(id)}`, {}, token)
|
||||
console.log(JSON.stringify(resp, null, 2))
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const { data } = await client.get({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule/${encodeURIComponent(id)}`,
|
||||
throwOnError: true,
|
||||
})
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
})
|
||||
|
||||
schedule
|
||||
@@ -542,13 +520,19 @@ schedule
|
||||
description: opts.description ?? answers.description,
|
||||
pattern: opts.pattern ?? answers.pattern,
|
||||
command: opts.command ?? answers.command,
|
||||
max_calls: maxCalls,
|
||||
max_calls: maxCalls !== undefined ? { set: true, value: maxCalls } : undefined,
|
||||
enabled: opts.enabled ? true : (opts.disabled ? false : undefined),
|
||||
}
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const spinner = ora('Creating schedule...').start()
|
||||
try {
|
||||
await apiRequest('/schedule', { method: 'POST', body: JSON.stringify(payload) }, token)
|
||||
await client.post({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule`,
|
||||
body: payload,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Schedule created')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to create schedule')
|
||||
@@ -583,7 +567,7 @@ schedule
|
||||
console.log(chalk.red('max_calls must be a positive integer.'))
|
||||
process.exit(1)
|
||||
}
|
||||
payload.max_calls = parsed
|
||||
payload.max_calls = { set: true, value: parsed }
|
||||
}
|
||||
if (opts.enabled) payload.enabled = true
|
||||
if (opts.disabled) payload.enabled = false
|
||||
@@ -591,13 +575,16 @@ schedule
|
||||
console.log(chalk.red('No updates provided.'))
|
||||
process.exit(1)
|
||||
}
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const spinner = ora('Updating schedule...').start()
|
||||
try {
|
||||
await apiRequest(`/schedule/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload),
|
||||
}, token)
|
||||
await client.put({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule/${encodeURIComponent(id)}`,
|
||||
body: payload,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Schedule updated')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to update schedule')
|
||||
@@ -610,15 +597,22 @@ schedule
|
||||
.description('Enable/disable schedule')
|
||||
.argument('<id>')
|
||||
.action(async (id) => {
|
||||
const token = ensureAuth()
|
||||
const current = await apiRequest<Schedule>(`/schedule/${encodeURIComponent(id)}`, {}, token)
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const { data: current } = await client.get({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule/${encodeURIComponent(id)}`,
|
||||
throwOnError: true,
|
||||
})
|
||||
const currentSchedule = current as ScheduleSchedule
|
||||
const spinner = ora('Updating schedule...').start()
|
||||
try {
|
||||
await apiRequest(`/schedule/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ enabled: !current.enabled }),
|
||||
}, token)
|
||||
spinner.succeed(`Schedule ${current.enabled ? 'disabled' : 'enabled'}`)
|
||||
await client.put({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule/${encodeURIComponent(id)}`,
|
||||
body: { enabled: !currentSchedule.enabled },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed(`Schedule ${currentSchedule.enabled ? 'disabled' : 'enabled'}`)
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to update schedule')
|
||||
process.exit(1)
|
||||
@@ -630,10 +624,14 @@ schedule
|
||||
.description('Delete schedule')
|
||||
.argument('<id>')
|
||||
.action(async (id) => {
|
||||
const token = ensureAuth()
|
||||
ensureAuth()
|
||||
const botId = await resolveScheduleBotId(schedule.opts())
|
||||
const spinner = ora('Deleting schedule...').start()
|
||||
try {
|
||||
await apiRequest(`/schedule/${encodeURIComponent(id)}`, { method: 'DELETE' }, token)
|
||||
await client.delete({
|
||||
url: `/bots/${encodeURIComponent(botId)}/schedule/${encodeURIComponent(id)}`,
|
||||
throwOnError: true,
|
||||
})
|
||||
spinner.succeed('Schedule deleted')
|
||||
} catch (err: unknown) {
|
||||
spinner.fail(getErrorMessage(err) || 'Failed to delete schedule')
|
||||
@@ -641,14 +639,16 @@ schedule
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Default action: interactive chat
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
program
|
||||
.option('--bot <id>', 'Bot id to chat with')
|
||||
.action(async () => {
|
||||
await ensureModelsReady()
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, program.opts().bot)
|
||||
const config = readConfig()
|
||||
const sessionId = config.session_id
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(program.opts().bot)
|
||||
|
||||
const rl = readline.createInterface({ input, output })
|
||||
console.log(chalk.green(`Chatting with ${chalk.bold(botId)}. Type \`exit\` to quit.`))
|
||||
@@ -662,18 +662,15 @@ program
|
||||
if (line.toLowerCase() === 'exit') {
|
||||
break
|
||||
}
|
||||
try {
|
||||
const ok = await streamChat(line, botId, sessionId, token)
|
||||
if (!ok) {
|
||||
console.log(chalk.red('Chat failed or stream unavailable.'))
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.log(chalk.red(getErrorMessage(err) || 'Chat failed'))
|
||||
}
|
||||
await streamChat(line, botId)
|
||||
}
|
||||
rl.close()
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Version command
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
program
|
||||
.command('version')
|
||||
.description('Show version information')
|
||||
@@ -681,25 +678,24 @@ program
|
||||
console.log(`Memoh CLI v${packageJson.version}`)
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TUI command
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
program
|
||||
.command('tui')
|
||||
.description('Terminal UI chat session')
|
||||
.option('--bot <id>', 'Bot id to chat with')
|
||||
.action(async (opts: { bot?: string }) => {
|
||||
await ensureModelsReady()
|
||||
const token = ensureAuth()
|
||||
const botId = await resolveBotId(token, opts.bot)
|
||||
await runTui(botId, token)
|
||||
ensureAuth()
|
||||
const botId = await resolveBotId(opts.bot)
|
||||
await runTui(botId)
|
||||
})
|
||||
|
||||
program.parseAsync(process.argv)
|
||||
|
||||
// streamChat is imported from ./stream
|
||||
|
||||
const runTui = async (botId: string, token: TokenInfo) => {
|
||||
const config = readConfig()
|
||||
const sessionId = config.session_id
|
||||
|
||||
const runTui = async (botId: string) => {
|
||||
const rl = readline.createInterface({ input, output })
|
||||
console.log(chalk.green(`TUI session (line mode) with ${chalk.bold(botId)}. Type \`exit\` to quit.`))
|
||||
while (true) {
|
||||
@@ -711,15 +707,7 @@ const runTui = async (botId: string, token: TokenInfo) => {
|
||||
if (line.toLowerCase() === 'exit') {
|
||||
break
|
||||
}
|
||||
try {
|
||||
const ok = await streamChat(line, botId, sessionId, token)
|
||||
if (!ok) {
|
||||
console.log(chalk.red('Chat failed or stream unavailable.'))
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.log(chalk.red(getErrorMessage(err) || 'Chat failed'))
|
||||
}
|
||||
await streamChat(line, botId)
|
||||
}
|
||||
rl.close()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,21 +2,10 @@ import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
|
||||
import { apiRequest } from '../core/api'
|
||||
import { readToken, TokenInfo } from '../utils/store'
|
||||
import { getBots, type BotsBot } from '@memoh/sdk'
|
||||
import { readToken, type TokenInfo } from '../utils/store'
|
||||
|
||||
export type BotSummary = {
|
||||
id: string
|
||||
owner_user_id: string
|
||||
type: string
|
||||
display_name: string
|
||||
avatar_url?: string
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
type BotListResponse = {
|
||||
items: BotSummary[]
|
||||
}
|
||||
export type BotSummary = BotsBot
|
||||
|
||||
export const ensureAuth = (): TokenInfo => {
|
||||
const token = readToken()
|
||||
@@ -35,18 +24,18 @@ export const getErrorMessage = (err: unknown) => {
|
||||
return 'Unknown error'
|
||||
}
|
||||
|
||||
export const fetchBots = async (token: TokenInfo) => {
|
||||
const resp = await apiRequest<BotListResponse>('/bots', {}, token)
|
||||
return resp.items
|
||||
export const fetchBots = async () => {
|
||||
const { data } = await getBots({ throwOnError: true })
|
||||
return data.items ?? []
|
||||
}
|
||||
|
||||
export const resolveBotId = async (token: TokenInfo, preset?: string) => {
|
||||
export const resolveBotId = async (preset?: string) => {
|
||||
if (preset && preset.trim()) {
|
||||
return preset.trim()
|
||||
}
|
||||
const spinner = ora('Fetching bots...').start()
|
||||
try {
|
||||
const bots = await fetchBots(token)
|
||||
const bots = await fetchBots()
|
||||
spinner.stop()
|
||||
if (bots.length === 0) {
|
||||
console.log(chalk.yellow('No bots found. Please create a bot first.'))
|
||||
@@ -58,7 +47,7 @@ export const resolveBotId = async (token: TokenInfo, preset?: string) => {
|
||||
name: 'botId',
|
||||
message: 'Select a bot:',
|
||||
choices: bots.map(bot => ({
|
||||
name: `${bot.display_name || bot.id} ${chalk.gray(bot.type)}`,
|
||||
name: `${bot.display_name || bot.id} ${chalk.gray(bot.type ?? '')}`,
|
||||
value: bot.id,
|
||||
})),
|
||||
},
|
||||
@@ -69,4 +58,3 @@ export const resolveBotId = async (token: TokenInfo, preset?: string) => {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+240
-230
@@ -1,5 +1,95 @@
|
||||
import chalk from 'chalk'
|
||||
import { readConfig, getBaseURL, TokenInfo } from '../utils/store'
|
||||
import { client } from '@memoh/sdk/client'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SSE stream types (aligned with frontend useChat.ts)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface StreamEvent {
|
||||
type?: string
|
||||
delta?: string
|
||||
toolName?: string
|
||||
input?: unknown
|
||||
result?: unknown
|
||||
error?: string
|
||||
message?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SSE parsing (directly from frontend useChat.ts)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Read an SSE stream line-by-line, calling onData for each `data:` payload.
|
||||
* Handles standard SSE format (events separated by double newlines).
|
||||
*/
|
||||
async function readSSEStream(
|
||||
body: ReadableStream<Uint8Array>,
|
||||
onData: (payload: string) => void,
|
||||
): Promise<void> {
|
||||
const reader = body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read()
|
||||
if (done) break
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
const chunks = buffer.split('\n\n')
|
||||
buffer = chunks.pop() ?? ''
|
||||
|
||||
for (const chunk of chunks) {
|
||||
for (const line of chunk.split('\n')) {
|
||||
if (!line.startsWith('data:')) continue
|
||||
const payload = line.replace(/^data:\s*/, '').trim()
|
||||
if (payload && payload !== '[DONE]') onData(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining buffer
|
||||
if (buffer.trim()) {
|
||||
for (const line of buffer.split('\n')) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed.startsWith('data:')) continue
|
||||
const payload = trimmed.replace(/^data:\s*/, '').trim()
|
||||
if (payload && payload !== '[DONE]') onData(payload)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a raw SSE payload string into a StreamEvent.
|
||||
* Handles double-encoded JSON and plain text deltas.
|
||||
* (directly from frontend useChat.ts)
|
||||
*/
|
||||
function parseStreamPayload(payload: string): StreamEvent | null {
|
||||
let current: unknown = payload
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
if (typeof current !== 'string') break
|
||||
const raw = current.trim()
|
||||
if (!raw || raw === '[DONE]') return null
|
||||
try {
|
||||
current = JSON.parse(raw)
|
||||
} catch {
|
||||
return { type: 'text_delta', delta: raw } as StreamEvent
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof current === 'string') {
|
||||
return { type: 'text_delta', delta: current.trim() } as StreamEvent
|
||||
}
|
||||
if (current && typeof current === 'object') {
|
||||
return current as StreamEvent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool display configuration
|
||||
@@ -9,17 +99,10 @@ type ToolDisplayMode = 'inline' | 'expanded'
|
||||
|
||||
interface ToolDisplayConfig {
|
||||
mode: ToolDisplayMode
|
||||
/** For expanded mode: which parameter to show as detail content */
|
||||
expandParam?: string
|
||||
/** Label shown in the display header */
|
||||
label?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools listed here will be displayed in "expanded" mode with a box showing
|
||||
* the specified parameter content. Everything else defaults to single-line.
|
||||
* exec uses a custom single-line format instead of the box.
|
||||
*/
|
||||
const TOOL_DISPLAY: Record<string, ToolDisplayConfig> = {
|
||||
exec: { mode: 'expanded', label: 'exec' },
|
||||
write: { mode: 'expanded', expandParam: 'content', label: 'write' },
|
||||
@@ -35,17 +118,11 @@ const getToolDisplay = (toolName: string): ToolDisplayConfig => {
|
||||
|
||||
const BOX_WIDTH = 60
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// exec-specific helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Extract the actual shell command from exec input like { command: "bash", args: ["-lc", "echo hi"] } */
|
||||
const extractExecCommand = (toolInput: unknown): string => {
|
||||
if (!toolInput || typeof toolInput !== 'object') return ''
|
||||
const input = toolInput as Record<string, unknown>
|
||||
const command = typeof input.command === 'string' ? input.command : ''
|
||||
const args = Array.isArray(input.args) ? input.args.map(String) : []
|
||||
// If shell + -c/-lc flag, extract the actual script
|
||||
if (/^(bash|sh|zsh)$/.test(command) && args.length >= 2) {
|
||||
const flag = args[0]
|
||||
if (flag === '-c' || flag === '-lc') {
|
||||
@@ -60,10 +137,6 @@ const formatExecCall = (toolInput: unknown) => {
|
||||
return chalk.dim(' ▶ ') + chalk.white('$ ') + chalk.bold.white(cmd)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// edit-specific helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const extractEditInput = (toolInput: unknown) => {
|
||||
if (!toolInput || typeof toolInput !== 'object') {
|
||||
return { path: '', oldText: '', newText: '' }
|
||||
@@ -125,11 +198,8 @@ const formatEditCall = (toolInput: unknown) => {
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
/** Try to unwrap MCP content-block results into a plain object */
|
||||
const unwrapToolResult = (result: unknown): Record<string, unknown> | null => {
|
||||
if (!result) return null
|
||||
|
||||
// Helper to extract from MCP content blocks array
|
||||
const extractFromContentBlocks = (arr: unknown[]): Record<string, unknown> | null => {
|
||||
for (const block of arr) {
|
||||
if (block && typeof block === 'object') {
|
||||
@@ -141,25 +211,15 @@ const unwrapToolResult = (result: unknown): Record<string, unknown> | null => {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// MCP content array: [{ type: "text", text: "{...}" }]
|
||||
if (Array.isArray(result)) {
|
||||
return extractFromContentBlocks(result)
|
||||
}
|
||||
|
||||
// Object - might be MCP wrapper { content: [...] } or direct result
|
||||
if (Array.isArray(result)) return extractFromContentBlocks(result)
|
||||
if (typeof result === 'object') {
|
||||
const obj = result as Record<string, unknown>
|
||||
// MCP wrapper: { content: [{ type: "text", text: "{...}" }], isError: ... }
|
||||
if (Array.isArray(obj.content)) {
|
||||
const extracted = extractFromContentBlocks(obj.content)
|
||||
if (extracted) return extracted
|
||||
}
|
||||
// Direct object with known result fields
|
||||
return obj
|
||||
}
|
||||
|
||||
// JSON string
|
||||
if (typeof result === 'string') {
|
||||
try { return JSON.parse(result) } catch { /* ignore */ }
|
||||
}
|
||||
@@ -169,15 +229,12 @@ const unwrapToolResult = (result: unknown): Record<string, unknown> | null => {
|
||||
const formatExecResult = (result: unknown) => {
|
||||
const r = unwrapToolResult(result)
|
||||
if (!r) return chalk.dim(' ╰─ done')
|
||||
|
||||
const exitCode = typeof r.exit_code === 'number' ? r.exit_code : (r.ok ? 0 : 1)
|
||||
const ok = exitCode === 0
|
||||
const stdout = typeof r.stdout === 'string' ? r.stdout.trim() : ''
|
||||
const stderr = typeof r.stderr === 'string' ? r.stderr.trim() : ''
|
||||
|
||||
const lines: string[] = []
|
||||
lines.push(chalk.dim(' ╰─ ') + (ok ? chalk.green(`✓ exit ${exitCode}`) : chalk.red(`✗ exit ${exitCode}`)))
|
||||
|
||||
const output = ok ? stdout : (stderr || stdout)
|
||||
if (output) {
|
||||
const outputLines = output.split('\n')
|
||||
@@ -194,10 +251,6 @@ const formatExecResult = (result: unknown) => {
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generic tool formatting
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const formatToolCallInline = (toolName: string, toolInput: unknown) => {
|
||||
let params = ''
|
||||
if (toolInput && typeof toolInput === 'object') {
|
||||
@@ -216,7 +269,6 @@ const formatToolCallInline = (toolName: string, toolInput: unknown) => {
|
||||
const formatToolCallExpanded = (config: ToolDisplayConfig, toolName: string, toolInput: unknown) => {
|
||||
const label = config.label ?? toolName
|
||||
const inputObj = (toolInput && typeof toolInput === 'object' ? toolInput : {}) as Record<string, unknown>
|
||||
|
||||
const summaryParts: string[] = []
|
||||
for (const [k, v] of Object.entries(inputObj)) {
|
||||
if (k === config.expandParam) continue
|
||||
@@ -224,26 +276,18 @@ const formatToolCallExpanded = (config: ToolDisplayConfig, toolName: string, too
|
||||
summaryParts.push(`${k}: ${s.length > 50 ? s.slice(0, 47) + '...' : s}`)
|
||||
}
|
||||
const summary = summaryParts.length ? ' ' + summaryParts.join(', ') : ''
|
||||
|
||||
let detail = ''
|
||||
if (config.expandParam && config.expandParam in inputObj) {
|
||||
const raw = inputObj[config.expandParam]
|
||||
if (typeof raw === 'string') {
|
||||
detail = raw
|
||||
} else if (Array.isArray(raw)) {
|
||||
detail = raw.join(' ')
|
||||
} else {
|
||||
detail = JSON.stringify(raw, null, 2)
|
||||
}
|
||||
if (typeof raw === 'string') detail = raw
|
||||
else if (Array.isArray(raw)) detail = raw.join(' ')
|
||||
else detail = JSON.stringify(raw, null, 2)
|
||||
}
|
||||
|
||||
const topBorder = '┌' + '─'.repeat(BOX_WIDTH - 2) + '┐'
|
||||
const botBorder = '└' + '─'.repeat(BOX_WIDTH - 2) + '┘'
|
||||
|
||||
const lines: string[] = []
|
||||
lines.push(chalk.cyan(topBorder))
|
||||
lines.push(chalk.cyan('│ ') + chalk.bold.white(label) + chalk.gray(summary))
|
||||
|
||||
if (detail) {
|
||||
lines.push(chalk.cyan('│ ') + chalk.dim('─'.repeat(BOX_WIDTH - 4)))
|
||||
const detailLines = detail.split('\n')
|
||||
@@ -257,16 +301,12 @@ const formatToolCallExpanded = (config: ToolDisplayConfig, toolName: string, too
|
||||
lines.push(chalk.cyan('│ ') + chalk.dim(`... (${detailLines.length - maxLines} more lines)`))
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(chalk.cyan(botBorder))
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
const formatToolResult = (toolName: string, result: unknown) => {
|
||||
// exec has its own result formatter
|
||||
if (toolName === 'exec') {
|
||||
return formatExecResult(result)
|
||||
}
|
||||
if (toolName === 'exec') return formatExecResult(result)
|
||||
const config = getToolDisplay(toolName)
|
||||
if (config.mode === 'expanded' || toolName === 'edit') {
|
||||
const r = unwrapToolResult(result)
|
||||
@@ -281,193 +321,163 @@ const formatToolResult = (toolName: string, result: unknown) => {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Text extraction helpers (fallback for unknown event formats)
|
||||
// Event handler for terminal display
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const extractTextFromMessage = (message: unknown) => {
|
||||
if (typeof message === 'string') return message
|
||||
if (message && typeof message === 'object') {
|
||||
const value = message as { text?: unknown; parts?: unknown[] }
|
||||
if (typeof value.text === 'string') return value.text
|
||||
if (Array.isArray(value.parts)) {
|
||||
const lines = value.parts
|
||||
.map((part) => {
|
||||
if (!part || typeof part !== 'object') return ''
|
||||
const typed = part as { text?: unknown; url?: unknown; emoji?: unknown }
|
||||
if (typeof typed.text === 'string' && typed.text.trim()) return typed.text
|
||||
if (typeof typed.url === 'string' && typed.url.trim()) return typed.url
|
||||
if (typeof typed.emoji === 'string' && typed.emoji.trim()) return typed.emoji
|
||||
return ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
if (lines.length) return lines.join('\n')
|
||||
}
|
||||
}
|
||||
return null
|
||||
function handleStreamEvent(event: StreamEvent): boolean {
|
||||
const type = (event.type ?? '').toLowerCase()
|
||||
// Track whether text has been written without a trailing newline
|
||||
return handleStreamEventInner(type, event)
|
||||
}
|
||||
|
||||
const extractTextFromEvent = (payload: string) => {
|
||||
try {
|
||||
const event = JSON.parse(payload)
|
||||
if (typeof event === 'string') return event
|
||||
if (typeof event?.text === 'string') return event.text
|
||||
const messageText = extractTextFromMessage(event?.message)
|
||||
if (messageText) return messageText
|
||||
if (typeof event?.delta === 'string') return event.delta
|
||||
if (typeof event?.delta?.content === 'string') return event.delta.content
|
||||
if (typeof event?.content === 'string') return event.content
|
||||
if (typeof event?.data === 'string') return event.data
|
||||
if (typeof event?.data?.text === 'string') return event.data.text
|
||||
if (typeof event?.data?.delta?.content === 'string') return event.data.delta.content
|
||||
const nestedMessageText = extractTextFromMessage(event?.data?.message)
|
||||
if (nestedMessageText) return nestedMessageText
|
||||
return null
|
||||
} catch {
|
||||
return payload
|
||||
let _printedText = false
|
||||
|
||||
function handleStreamEventInner(type: string, event: StreamEvent): boolean {
|
||||
switch (type) {
|
||||
case 'text_start':
|
||||
break
|
||||
|
||||
case 'text_delta':
|
||||
if (typeof event.delta === 'string') {
|
||||
process.stdout.write(event.delta)
|
||||
_printedText = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'text_end':
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
_printedText = false
|
||||
}
|
||||
break
|
||||
|
||||
case 'tool_call_start': {
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
_printedText = false
|
||||
}
|
||||
const toolName = event.toolName as string
|
||||
const toolInput = event.input
|
||||
if (toolName === 'exec') {
|
||||
console.log(formatExecCall(toolInput))
|
||||
} else if (toolName === 'edit') {
|
||||
console.log(formatEditCall(toolInput))
|
||||
} else {
|
||||
const displayConfig = getToolDisplay(toolName)
|
||||
if (displayConfig.mode === 'expanded') {
|
||||
console.log(formatToolCallExpanded(displayConfig, toolName, toolInput))
|
||||
} else {
|
||||
console.log(formatToolCallInline(toolName, toolInput))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tool_call_end': {
|
||||
const toolName = event.toolName as string
|
||||
const result = event.result
|
||||
const resultLine = formatToolResult(toolName, result)
|
||||
if (resultLine) console.log(resultLine)
|
||||
break
|
||||
}
|
||||
|
||||
case 'reasoning_start':
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
_printedText = false
|
||||
}
|
||||
process.stdout.write(chalk.dim(' 💭 '))
|
||||
break
|
||||
|
||||
case 'reasoning_delta':
|
||||
if (typeof event.delta === 'string') {
|
||||
process.stdout.write(chalk.dim(event.delta))
|
||||
_printedText = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'reasoning_end':
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
_printedText = false
|
||||
}
|
||||
break
|
||||
|
||||
case 'error': {
|
||||
const errMsg = typeof event.message === 'string'
|
||||
? event.message
|
||||
: typeof event.error === 'string'
|
||||
? event.error
|
||||
: 'Stream error'
|
||||
console.log(chalk.red(`Error: ${errMsg}`))
|
||||
break
|
||||
}
|
||||
|
||||
case 'processing_started':
|
||||
case 'processing_completed':
|
||||
case 'processing_failed':
|
||||
case 'agent_start':
|
||||
case 'agent_end':
|
||||
break
|
||||
|
||||
default: {
|
||||
// Fallback: try to extract text (aligned with frontend extractFallbackText)
|
||||
if (typeof event.delta === 'string') {
|
||||
process.stdout.write(event.delta)
|
||||
_printedText = true
|
||||
} else if (typeof (event as Record<string, unknown>).text === 'string') {
|
||||
process.stdout.write((event as Record<string, unknown>).text as string)
|
||||
_printedText = true
|
||||
} else if (typeof (event as Record<string, unknown>).content === 'string') {
|
||||
process.stdout.write((event as Record<string, unknown>).content as string)
|
||||
_printedText = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stream chat
|
||||
// Strictly follows frontend streamMessage() in useChat.ts:
|
||||
// client.post({ url: '/bots/{bot_id}/messages/stream', path: { bot_id }, ... })
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const streamChat = async (query: string, botId: string, sessionId: string, token: TokenInfo) => {
|
||||
const config = readConfig()
|
||||
const baseURL = getBaseURL(config)
|
||||
const resp = await fetch(`${baseURL}/bots/${botId}/chat/stream?session_id=${encodeURIComponent(sessionId)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token.access_token}`,
|
||||
},
|
||||
body: JSON.stringify({ query }),
|
||||
}).catch(() => null)
|
||||
if (!resp || !resp.ok || !resp.body) return false
|
||||
export const streamChat = async (query: string, botId: string) => {
|
||||
_printedText = false
|
||||
|
||||
const stream = resp.body
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
let printedText = false
|
||||
try {
|
||||
// Exactly matches frontend: client.post() with parseAs: 'stream'
|
||||
const { data: body } = await client.post({
|
||||
url: '/bots/{bot_id}/messages/stream',
|
||||
path: { bot_id: botId },
|
||||
body: { query, current_channel: 'cli', channels: ['cli'] },
|
||||
parseAs: 'stream',
|
||||
throwOnError: true,
|
||||
}) as { data: ReadableStream<Uint8Array> }
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read()
|
||||
if (done) break
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
let idx
|
||||
while ((idx = buffer.indexOf('\n')) >= 0) {
|
||||
const line = buffer.slice(0, idx).trim()
|
||||
buffer = buffer.slice(idx + 1)
|
||||
if (!line.startsWith('data:')) continue
|
||||
const payload = line.slice(5).trim()
|
||||
if (!payload || payload === '[DONE]') continue
|
||||
|
||||
let event: Record<string, unknown>
|
||||
try {
|
||||
const parsed = JSON.parse(payload)
|
||||
if (typeof parsed === 'string') {
|
||||
process.stdout.write(parsed)
|
||||
printedText = true
|
||||
continue
|
||||
}
|
||||
event = parsed
|
||||
} catch {
|
||||
process.stdout.write(payload)
|
||||
printedText = true
|
||||
continue
|
||||
}
|
||||
|
||||
const eventType = event.type as string | undefined
|
||||
|
||||
switch (eventType) {
|
||||
case 'text_start':
|
||||
break
|
||||
|
||||
case 'text_delta':
|
||||
if (typeof event.delta === 'string') {
|
||||
process.stdout.write(event.delta)
|
||||
printedText = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'text_end':
|
||||
if (printedText) {
|
||||
process.stdout.write('\n')
|
||||
printedText = false
|
||||
}
|
||||
break
|
||||
|
||||
case 'tool_call_start': {
|
||||
if (printedText) {
|
||||
process.stdout.write('\n')
|
||||
printedText = false
|
||||
}
|
||||
const toolName = event.toolName as string
|
||||
const toolInput = event.input
|
||||
if (toolName === 'exec') {
|
||||
console.log(formatExecCall(toolInput))
|
||||
} else if (toolName === 'edit') {
|
||||
console.log(formatEditCall(toolInput))
|
||||
} else {
|
||||
const displayConfig = getToolDisplay(toolName)
|
||||
if (displayConfig.mode === 'expanded') {
|
||||
console.log(formatToolCallExpanded(displayConfig, toolName, toolInput))
|
||||
} else {
|
||||
console.log(formatToolCallInline(toolName, toolInput))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tool_call_end': {
|
||||
const toolName = event.toolName as string
|
||||
const result = event.result
|
||||
const resultLine = formatToolResult(toolName, result)
|
||||
if (resultLine) {
|
||||
console.log(resultLine)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'reasoning_start':
|
||||
if (printedText) {
|
||||
process.stdout.write('\n')
|
||||
printedText = false
|
||||
}
|
||||
process.stdout.write(chalk.dim(' 💭 '))
|
||||
break
|
||||
|
||||
case 'reasoning_delta':
|
||||
if (typeof event.delta === 'string') {
|
||||
process.stdout.write(chalk.dim(event.delta))
|
||||
printedText = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'reasoning_end':
|
||||
if (printedText) {
|
||||
process.stdout.write('\n')
|
||||
printedText = false
|
||||
}
|
||||
break
|
||||
|
||||
case 'agent_start':
|
||||
case 'agent_end':
|
||||
break
|
||||
|
||||
default: {
|
||||
const text = extractTextFromEvent(payload)
|
||||
if (text) {
|
||||
process.stdout.write(text)
|
||||
printedText = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!body) {
|
||||
console.log(chalk.red('No response body'))
|
||||
return false
|
||||
}
|
||||
|
||||
// Use the same readSSEStream + parseStreamPayload as frontend
|
||||
await readSSEStream(body, (payload) => {
|
||||
const event = parseStreamPayload(payload)
|
||||
if (event) handleStreamEvent(event)
|
||||
})
|
||||
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
if (_printedText) {
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
console.log(chalk.red(`Stream error: ${msg}`))
|
||||
return false
|
||||
}
|
||||
if (printedText) {
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { readConfig, readToken, getBaseURL, TokenInfo } from '../utils/store'
|
||||
|
||||
export type ApiError = {
|
||||
status: number
|
||||
message: string
|
||||
}
|
||||
|
||||
export const apiRequest = async <T>(
|
||||
path: string,
|
||||
options: RequestInit = {},
|
||||
tokenOverride?: TokenInfo | null
|
||||
): Promise<T> => {
|
||||
const config = readConfig()
|
||||
const baseURL = getBaseURL(config)
|
||||
const token = tokenOverride ?? readToken()
|
||||
|
||||
const headers = new Headers(options.headers || {})
|
||||
headers.set('Content-Type', 'application/json')
|
||||
if (token?.access_token) {
|
||||
headers.set('Authorization', `Bearer ${token.access_token}`)
|
||||
}
|
||||
|
||||
const resp = await fetch(`${baseURL}${path}`, {
|
||||
...options,
|
||||
headers,
|
||||
})
|
||||
if (!resp.ok) {
|
||||
let message = resp.statusText
|
||||
try {
|
||||
const data = await resp.json()
|
||||
if (data?.message) message = data.message
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const err: ApiError = { status: resp.status, message }
|
||||
throw err
|
||||
}
|
||||
if (resp.status === 204) {
|
||||
return null as T
|
||||
}
|
||||
return (await resp.json()) as T
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { client } from '@memoh/sdk/client'
|
||||
import { readConfig, readToken, getBaseURL } from '../utils/store'
|
||||
|
||||
/**
|
||||
* Configure the SDK client with base URL and auth interceptor.
|
||||
* Call this once at CLI startup (before any API calls).
|
||||
*/
|
||||
export function setupClient() {
|
||||
const config = readConfig()
|
||||
client.setConfig({ baseUrl: getBaseURL(config) })
|
||||
|
||||
// Add auth token to every request (read lazily from store)
|
||||
client.interceptors.request.use((request) => {
|
||||
const token = readToken()
|
||||
if (token?.access_token) {
|
||||
request.headers.set('Authorization', `Bearer ${token.access_token}`)
|
||||
}
|
||||
return request
|
||||
})
|
||||
}
|
||||
|
||||
export { client }
|
||||
@@ -1,2 +1 @@
|
||||
export * from './api'
|
||||
|
||||
export * from './client'
|
||||
|
||||
+19
-171
@@ -1,173 +1,21 @@
|
||||
export type { CliConfig, TokenInfo } from '../utils/store'
|
||||
// API response type definitions
|
||||
|
||||
export interface ApiResponse<T = unknown> {
|
||||
success?: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
username: string
|
||||
role: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
id: string
|
||||
name: string
|
||||
modelId: string
|
||||
baseUrl: string
|
||||
apiKey?: string
|
||||
clientType: string
|
||||
type?: 'chat' | 'embedding'
|
||||
dimensions?: number
|
||||
createdAt: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export interface Memory {
|
||||
content: string
|
||||
timestamp: string
|
||||
similarity?: number
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface MessageListResponse {
|
||||
messages: Message[]
|
||||
pagination: {
|
||||
page: number
|
||||
limit: number
|
||||
total: number
|
||||
totalPages: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
userId: string
|
||||
language?: string
|
||||
maxContextLoadTime?: number
|
||||
defaultChatModel?: string
|
||||
defaultSummaryModel?: string
|
||||
defaultEmbeddingModel?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface Schedule {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
cronExpression: string
|
||||
enabled: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface Platform {
|
||||
id: string
|
||||
name: string
|
||||
config: Record<string, unknown>
|
||||
active: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// Platform configuration definitions
|
||||
export interface PlatformConfigField {
|
||||
name: string
|
||||
message: string
|
||||
type?: 'input' | 'password' | 'number'
|
||||
required?: boolean
|
||||
default?: string | number
|
||||
validate?: (value: string) => boolean | string
|
||||
}
|
||||
|
||||
export interface PlatformDefinition {
|
||||
name: string
|
||||
displayName: string
|
||||
description: string
|
||||
configFields: PlatformConfigField[]
|
||||
}
|
||||
|
||||
// Platform configurations
|
||||
export const PLATFORM_DEFINITIONS: PlatformDefinition[] = [
|
||||
{
|
||||
name: 'telegram',
|
||||
displayName: 'Telegram',
|
||||
description: 'Telegram Bot Platform',
|
||||
configFields: [
|
||||
{
|
||||
name: 'botToken',
|
||||
message: 'Bot Token:',
|
||||
type: 'password',
|
||||
required: true,
|
||||
validate: (value: string) => {
|
||||
if (!value.trim()) return 'Bot token is required'
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Future platforms can be added here
|
||||
// {
|
||||
// name: 'discord',
|
||||
// displayName: 'Discord',
|
||||
// description: 'Discord Bot Platform',
|
||||
// configFields: [
|
||||
// {
|
||||
// name: 'botToken',
|
||||
// message: 'Bot Token:',
|
||||
// type: 'password',
|
||||
// required: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'clientId',
|
||||
// message: 'Client ID:',
|
||||
// type: 'input',
|
||||
// required: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
]
|
||||
|
||||
export interface MCPConnection {
|
||||
id: string
|
||||
type: string
|
||||
name: string
|
||||
config: MCPConnectionConfig
|
||||
active: boolean
|
||||
user: string
|
||||
}
|
||||
|
||||
export type MCPConnectionConfig =
|
||||
| StdioMCPConnection
|
||||
| HTTPMCPConnection
|
||||
| SSEMCPConnection
|
||||
|
||||
export interface StdioMCPConnection {
|
||||
type: 'stdio'
|
||||
command: string
|
||||
args: string[]
|
||||
env: Record<string, string>
|
||||
cwd: string
|
||||
}
|
||||
|
||||
export interface HTTPMCPConnection {
|
||||
type: 'http'
|
||||
url: string
|
||||
headers: Record<string, string>
|
||||
}
|
||||
|
||||
export interface SSEMCPConnection {
|
||||
type: 'sse'
|
||||
url: string
|
||||
headers: Record<string, string>
|
||||
}
|
||||
|
||||
// Re-export commonly used SDK types for convenience
|
||||
export type {
|
||||
BotsBot,
|
||||
BotsCreateBotRequest,
|
||||
BotsListBotsResponse,
|
||||
BotsUpdateBotRequest,
|
||||
HandlersLoginResponse,
|
||||
HandlersChannelMeta,
|
||||
ChannelChannelConfig,
|
||||
ChannelChannelIdentityBinding,
|
||||
ModelsGetResponse,
|
||||
ModelsModelType,
|
||||
ProvidersClientType,
|
||||
ProvidersGetResponse,
|
||||
ScheduleListResponse,
|
||||
ScheduleSchedule,
|
||||
SettingsSettings,
|
||||
AccountsAccount,
|
||||
} from '@memoh/sdk'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: { cli: 'src/cli/index.ts' },
|
||||
format: ['esm'],
|
||||
target: 'node20',
|
||||
platform: 'node',
|
||||
bundle: true,
|
||||
splitting: false,
|
||||
clean: true,
|
||||
// @memoh/sdk exports raw .ts, must be bundled
|
||||
noExternal: [/^@memoh\/sdk/],
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node',
|
||||
},
|
||||
})
|
||||
Generated
+294
-64
@@ -121,24 +121,18 @@ importers:
|
||||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
'@elysiajs/eden':
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(@types/bun@1.3.8)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
'@memoh/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../sdk
|
||||
chalk:
|
||||
specifier: ^5.4.1
|
||||
version: 5.6.2
|
||||
commander:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
elysia:
|
||||
specifier: latest
|
||||
version: 1.4.22(@sinclair/typebox@0.34.47)(@types/bun@1.3.8)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
inquirer:
|
||||
specifier: ^12.3.0
|
||||
version: 12.11.1(@types/node@22.19.5)
|
||||
node-fetch:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
ora:
|
||||
specifier: ^8.1.1
|
||||
version: 8.2.0
|
||||
@@ -158,6 +152,9 @@ importers:
|
||||
bun-types:
|
||||
specifier: latest
|
||||
version: 1.3.8
|
||||
tsup:
|
||||
specifier: ^8.4.0
|
||||
version: 8.5.1(@microsoft/api-extractor@7.55.2(@types/node@22.19.5))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)
|
||||
|
||||
packages/config:
|
||||
dependencies:
|
||||
@@ -810,11 +807,6 @@ packages:
|
||||
peerDependencies:
|
||||
elysia: '>= 1.4.0'
|
||||
|
||||
'@elysiajs/eden@1.4.6':
|
||||
resolution: {integrity: sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q==}
|
||||
peerDependencies:
|
||||
elysia: '>=1.4.19'
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
@@ -2659,6 +2651,9 @@ packages:
|
||||
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
@@ -2727,6 +2722,12 @@ packages:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
esbuild: '>=0.18'
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2739,6 +2740,10 @@ packages:
|
||||
magicast:
|
||||
optional: true
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2786,6 +2791,10 @@ packages:
|
||||
chevrotain@11.0.3:
|
||||
resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
@@ -2841,6 +2850,10 @@ packages:
|
||||
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
commander@7.2.0:
|
||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -3078,10 +3091,6 @@ packages:
|
||||
dagre-d3-es@7.0.13:
|
||||
resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
data-urls@6.0.1:
|
||||
resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -3394,10 +3403,6 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -3414,6 +3419,9 @@ packages:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
|
||||
|
||||
flat-cache@4.0.1:
|
||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -3437,10 +3445,6 @@ packages:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -3677,6 +3681,10 @@ packages:
|
||||
jose@6.1.3:
|
||||
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -3829,9 +3837,20 @@ packages:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lilconfig@3.1.3:
|
||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
|
||||
load-tsconfig@0.2.5:
|
||||
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
local-pkg@1.1.2:
|
||||
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -4047,6 +4066,9 @@ packages:
|
||||
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -4064,18 +4086,9 @@ packages:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
|
||||
@@ -4238,6 +4251,10 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pkce-challenge@5.0.1:
|
||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||
engines: {node: '>=16.20.0'}
|
||||
@@ -4254,6 +4271,24 @@ packages:
|
||||
points-on-path@0.2.1:
|
||||
resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
jiti: '>=1.21.0'
|
||||
postcss: '>=8.0.9'
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
jiti:
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4313,6 +4348,10 @@ packages:
|
||||
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
readdirp@5.0.0:
|
||||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
@@ -4339,6 +4378,10 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
@@ -4478,6 +4521,10 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
@@ -4550,6 +4597,11 @@ packages:
|
||||
stylis@4.3.6:
|
||||
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
||||
|
||||
sucrase@3.35.1:
|
||||
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
superjson@2.2.6:
|
||||
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -4586,9 +4638,19 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyexec@1.0.2:
|
||||
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4631,6 +4693,10 @@ packages:
|
||||
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@@ -4644,9 +4710,31 @@ packages:
|
||||
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
|
||||
engines: {node: '>=6.10'}
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsup@8.5.1:
|
||||
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7.36.0
|
||||
'@swc/core': ^1
|
||||
postcss: ^8.4.12
|
||||
typescript: '>=4.5.0'
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
'@swc/core':
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
tsx@4.21.0:
|
||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -5026,10 +5114,6 @@ packages:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
webidl-conversions@8.0.1:
|
||||
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -5670,10 +5754,6 @@ snapshots:
|
||||
dependencies:
|
||||
elysia: 1.4.22(@sinclair/typebox@0.34.47)(@types/bun@1.3.8)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
|
||||
'@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(@types/bun@1.3.8)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
elysia: 1.4.22(@sinclair/typebox@0.34.47)(@types/bun@1.3.8)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
@@ -6316,6 +6396,15 @@ snapshots:
|
||||
dependencies:
|
||||
langium: 3.3.1
|
||||
|
||||
'@microsoft/api-extractor-model@7.32.2(@types/node@22.19.5)':
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.16.0
|
||||
'@microsoft/tsdoc-config': 0.18.0
|
||||
'@rushstack/node-core-library': 5.19.1(@types/node@22.19.5)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
optional: true
|
||||
|
||||
'@microsoft/api-extractor-model@7.32.2(@types/node@24.10.4)':
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.16.0
|
||||
@@ -6324,6 +6413,26 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
||||
'@microsoft/api-extractor@7.55.2(@types/node@22.19.5)':
|
||||
dependencies:
|
||||
'@microsoft/api-extractor-model': 7.32.2(@types/node@22.19.5)
|
||||
'@microsoft/tsdoc': 0.16.0
|
||||
'@microsoft/tsdoc-config': 0.18.0
|
||||
'@rushstack/node-core-library': 5.19.1(@types/node@22.19.5)
|
||||
'@rushstack/rig-package': 0.6.0
|
||||
'@rushstack/terminal': 0.19.5(@types/node@22.19.5)
|
||||
'@rushstack/ts-command-line': 5.1.5(@types/node@22.19.5)
|
||||
diff: 8.0.2
|
||||
lodash: 4.17.21
|
||||
minimatch: 10.0.3
|
||||
resolve: 1.22.11
|
||||
semver: 7.5.4
|
||||
source-map: 0.6.1
|
||||
typescript: 5.8.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
optional: true
|
||||
|
||||
'@microsoft/api-extractor@7.55.2(@types/node@24.10.4)':
|
||||
dependencies:
|
||||
'@microsoft/api-extractor-model': 7.32.2(@types/node@24.10.4)
|
||||
@@ -6465,6 +6574,20 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.54.0':
|
||||
optional: true
|
||||
|
||||
'@rushstack/node-core-library@5.19.1(@types/node@22.19.5)':
|
||||
dependencies:
|
||||
ajv: 8.13.0
|
||||
ajv-draft-04: 1.0.0(ajv@8.13.0)
|
||||
ajv-formats: 3.0.1(ajv@8.13.0)
|
||||
fs-extra: 11.3.3
|
||||
import-lazy: 4.0.0
|
||||
jju: 1.4.0
|
||||
resolve: 1.22.11
|
||||
semver: 7.5.4
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.5
|
||||
optional: true
|
||||
|
||||
'@rushstack/node-core-library@5.19.1(@types/node@24.10.4)':
|
||||
dependencies:
|
||||
ajv: 8.13.0
|
||||
@@ -6478,6 +6601,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
|
||||
'@rushstack/problem-matcher@0.1.1(@types/node@22.19.5)':
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.5
|
||||
optional: true
|
||||
|
||||
'@rushstack/problem-matcher@0.1.1(@types/node@24.10.4)':
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
@@ -6487,6 +6615,15 @@ snapshots:
|
||||
resolve: 1.22.11
|
||||
strip-json-comments: 3.1.1
|
||||
|
||||
'@rushstack/terminal@0.19.5(@types/node@22.19.5)':
|
||||
dependencies:
|
||||
'@rushstack/node-core-library': 5.19.1(@types/node@22.19.5)
|
||||
'@rushstack/problem-matcher': 0.1.1(@types/node@22.19.5)
|
||||
supports-color: 8.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.5
|
||||
optional: true
|
||||
|
||||
'@rushstack/terminal@0.19.5(@types/node@24.10.4)':
|
||||
dependencies:
|
||||
'@rushstack/node-core-library': 5.19.1(@types/node@24.10.4)
|
||||
@@ -6495,6 +6632,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
|
||||
'@rushstack/ts-command-line@5.1.5(@types/node@22.19.5)':
|
||||
dependencies:
|
||||
'@rushstack/terminal': 0.19.5(@types/node@22.19.5)
|
||||
'@types/argparse': 1.0.38
|
||||
argparse: 1.0.10
|
||||
string-argv: 0.3.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
optional: true
|
||||
|
||||
'@rushstack/ts-command-line@5.1.5(@types/node@24.10.4)':
|
||||
dependencies:
|
||||
'@rushstack/terminal': 0.19.5(@types/node@24.10.4)
|
||||
@@ -7361,6 +7508,8 @@ snapshots:
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
@@ -7442,6 +7591,11 @@ snapshots:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
|
||||
bundle-require@5.1.0(esbuild@0.27.2):
|
||||
dependencies:
|
||||
esbuild: 0.27.2
|
||||
load-tsconfig: 0.2.5
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
c12@3.3.3:
|
||||
@@ -7459,6 +7613,8 @@ snapshots:
|
||||
pkg-types: 2.3.0
|
||||
rc9: 2.1.2
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -7504,6 +7660,10 @@ snapshots:
|
||||
'@chevrotain/utils': 11.0.3
|
||||
lodash-es: 4.17.21
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
@@ -7547,6 +7707,8 @@ snapshots:
|
||||
|
||||
commander@14.0.3: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
commander@8.3.0: {}
|
||||
@@ -7796,8 +7958,6 @@ snapshots:
|
||||
d3: 7.9.0
|
||||
lodash-es: 4.17.23
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-urls@6.0.1:
|
||||
dependencies:
|
||||
whatwg-mimetype: 5.0.0
|
||||
@@ -8198,11 +8358,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
@@ -8232,6 +8387,12 @@ snapshots:
|
||||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
mlly: 1.8.0
|
||||
rollup: 4.54.0
|
||||
|
||||
flat-cache@4.0.1:
|
||||
dependencies:
|
||||
flatted: 3.3.3
|
||||
@@ -8255,10 +8416,6 @@ snapshots:
|
||||
mime-types: 2.1.35
|
||||
optional: true
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
@@ -8475,6 +8632,8 @@ snapshots:
|
||||
|
||||
jose@6.1.3: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
@@ -8611,10 +8770,16 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
|
||||
load-tsconfig@0.2.5: {}
|
||||
|
||||
local-pkg@1.1.2:
|
||||
dependencies:
|
||||
mlly: 1.8.0
|
||||
@@ -8816,6 +8981,12 @@ snapshots:
|
||||
|
||||
mute-stream@2.0.0: {}
|
||||
|
||||
mz@2.7.0:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
object-assign: 4.1.1
|
||||
thenify-all: 1.6.0
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
@@ -8824,16 +8995,8 @@ snapshots:
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
npm-normalize-package-bin@4.0.0: {}
|
||||
@@ -8993,6 +9156,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkce-challenge@5.0.1: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
@@ -9014,6 +9179,14 @@ snapshots:
|
||||
path-data-parser: 0.1.0
|
||||
points-on-curve: 0.2.0
|
||||
|
||||
postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
optionalDependencies:
|
||||
jiti: 2.6.1
|
||||
postcss: 8.5.6
|
||||
tsx: 4.21.0
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
@@ -9070,6 +9243,8 @@ snapshots:
|
||||
json-parse-even-better-errors: 4.0.0
|
||||
npm-normalize-package-bin: 4.0.0
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
@@ -9103,6 +9278,8 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.11:
|
||||
@@ -9301,6 +9478,8 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
@@ -9372,6 +9551,16 @@ snapshots:
|
||||
|
||||
stylis@4.3.6: {}
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
commander: 4.1.1
|
||||
lines-and-columns: 1.2.4
|
||||
mz: 2.7.0
|
||||
pirates: 4.0.7
|
||||
tinyglobby: 0.2.15
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
superjson@2.2.6:
|
||||
dependencies:
|
||||
copy-anything: 4.0.5
|
||||
@@ -9404,8 +9593,18 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
|
||||
thenify@3.3.1:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyexec@1.0.2: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
@@ -9441,6 +9640,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
ts-api-utils@2.4.0(typescript@5.9.3):
|
||||
@@ -9449,8 +9650,39 @@ snapshots:
|
||||
|
||||
ts-dedent@2.2.0: {}
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsup@8.5.1(@microsoft/api-extractor@7.55.2(@types/node@22.19.5))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.27.2)
|
||||
cac: 6.7.14
|
||||
chokidar: 4.0.3
|
||||
consola: 3.4.2
|
||||
debug: 4.4.3
|
||||
esbuild: 0.27.2
|
||||
fix-dts-default-cjs-exports: 1.0.1
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.54.0
|
||||
source-map: 0.7.6
|
||||
sucrase: 3.35.1
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
'@microsoft/api-extractor': 7.55.2(@types/node@22.19.5)
|
||||
postcss: 8.5.6
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- supports-color
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
tsx@4.21.0:
|
||||
dependencies:
|
||||
esbuild: 0.27.2
|
||||
@@ -9843,8 +10075,6 @@ snapshots:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
webidl-conversions@8.0.1: {}
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
Reference in New Issue
Block a user