diff --git a/packages/cli/package.json b/packages/cli/package.json index 986bf916..8bb2d363 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/src/cli/bot.ts b/packages/cli/src/cli/bot.ts index d42c5044..87e7653b 100644 --- a/packages/cli/src/cli/bot.ts +++ b/packages/cli/src/cli/bot.ts @@ -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 - 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('/models?type=chat', {}, token), - apiRequest('/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 ', '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(`/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 = { + const body: Record = { 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('/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 = {} - 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 = {} + 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 ', '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 ', 'chat | memory | embedding') .option('--model ', '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('/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 = {} - 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 = {} + 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) => { } }) } - diff --git a/packages/cli/src/cli/channel.ts b/packages/cli/src/cli/channel.ts index 02d7d675..5fe95076 100644 --- a/packages/cli/src/cli/channel.ts +++ b/packages/cli/src/cli/channel.ts @@ -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 -} - -type ChannelMeta = { - type: string - display_name: string - configless: boolean - capabilities: Record - config_schema: ChannelConfigSchema - user_config_schema: ChannelConfigSchema -} - -type ChannelUserBinding = { - id: string - channel_type: string - user_id: string - config: Record - created_at: string - updated_at: string -} - -type ChannelConfig = { - id: string - bot_id: string - channel_type: string - credentials: Record - external_identity: string - self_identity: Record - routing: Record - capabilities: Record - 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) => { - return apiRequest('/channels', {}, token) +const fetchChannelList = async () => { + const { data } = await getChannels({ throwOnError: true }) + return data as HandlersChannelMeta[] } const resolveChannelType = async ( - token: ReturnType, 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(`/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 ', '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(`/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 ') .option('--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 ', 'Channel type') .action(async (opts) => { - const token = ensureAuth() - const channelType = await resolveChannelType(token, opts.type) - const resp = await apiRequest(`/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 ') .option('--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) => { } }) } - diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index 45c1ca08..3e45cb3e 100755 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -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_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('/models?type=chat', {}, token), - apiRequest('/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('/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('/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 ', 'Filter by provider name') .action(async (opts) => { - const token = ensureAuth() - const providers = opts.provider - ? [await apiRequest(`/providers/name/${encodeURIComponent(opts.provider)}`, {}, token)] - : await apiRequest('/providers', {}, token) - const models = await apiRequest('/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 ') .option('--api_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 ', '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(`/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('/models', {}, token), - apiRequest('/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 ') .option('--multimodal', 'Is multimodal') .action(async (opts) => { - const token = ensureAuth() - const providers = await apiRequest('/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 ') .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 ', '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('/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('') .action(async (id) => { - const token = ensureAuth() - const resp = await apiRequest(`/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('') .action(async (id) => { - const token = ensureAuth() - const current = await apiRequest(`/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('') .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 ', '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 ', '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() } - diff --git a/packages/cli/src/cli/shared.ts b/packages/cli/src/cli/shared.ts index 48510115..94e1756e 100644 --- a/packages/cli/src/cli/shared.ts +++ b/packages/cli/src/cli/shared.ts @@ -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('/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) } } - diff --git a/packages/cli/src/cli/stream.ts b/packages/cli/src/cli/stream.ts index 99c26b3f..e9e244be 100644 --- a/packages/cli/src/cli/stream.ts +++ b/packages/cli/src/cli/stream.ts @@ -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, + onData: (payload: string) => void, +): Promise { + 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 = { 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 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 | null => { if (!result) return null - - // Helper to extract from MCP content blocks array const extractFromContentBlocks = (arr: unknown[]): Record | null => { for (const block of arr) { if (block && typeof block === 'object') { @@ -141,25 +211,15 @@ const unwrapToolResult = (result: unknown): Record | 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 - // 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 | 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 - 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).text === 'string') { + process.stdout.write((event as Record).text as string) + _printedText = true + } else if (typeof (event as Record).content === 'string') { + process.stdout.write((event as Record).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 } - 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 - 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 } diff --git a/packages/cli/src/core/api.ts b/packages/cli/src/core/api.ts deleted file mode 100644 index 3f80a147..00000000 --- a/packages/cli/src/core/api.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { readConfig, readToken, getBaseURL, TokenInfo } from '../utils/store' - -export type ApiError = { - status: number - message: string -} - -export const apiRequest = async ( - path: string, - options: RequestInit = {}, - tokenOverride?: TokenInfo | null -): Promise => { - 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 -} - diff --git a/packages/cli/src/core/client.ts b/packages/cli/src/core/client.ts new file mode 100644 index 00000000..f20bf510 --- /dev/null +++ b/packages/cli/src/core/client.ts @@ -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 } diff --git a/packages/cli/src/core/index.ts b/packages/cli/src/core/index.ts index 0bf83f78..83dae763 100644 --- a/packages/cli/src/core/index.ts +++ b/packages/cli/src/core/index.ts @@ -1,2 +1 @@ -export * from './api' - +export * from './client' diff --git a/packages/cli/src/types/index.ts b/packages/cli/src/types/index.ts index 34394946..e564c596 100644 --- a/packages/cli/src/types/index.ts +++ b/packages/cli/src/types/index.ts @@ -1,173 +1,21 @@ export type { CliConfig, TokenInfo } from '../utils/store' -// API response type definitions - -export interface ApiResponse { - 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 - 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 - cwd: string -} - -export interface HTTPMCPConnection { - type: 'http' - url: string - headers: Record -} - -export interface SSEMCPConnection { - type: 'sse' - url: string - headers: Record -} +// 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' diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts new file mode 100644 index 00000000..412f9fe3 --- /dev/null +++ b/packages/cli/tsup.config.ts @@ -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', + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 564a537e..8b88a7ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {}