UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

742 lines 33.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.registerAccountCommands = registerAccountCommands; const account_config_1 = require("../config/account-config"); const session_state_1 = require("../config/session-state"); const config_1 = require("../types/config"); const auth_adapter_1 = require("../config/auth-adapter"); const manager_1 = require("../config/manager"); const account_api_handler_1 = require("../handlers/account-api.handler"); const errors_1 = require("../utils/errors"); const readline_1 = require("readline"); const global_1 = require("../config/global"); const fs_1 = require("fs"); const DEFAULT_TIMEOUT_MS = 30000; function createReadlineInterface() { return (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout }); } function promptConfirmation(rl, question) { return new Promise((resolve) => { rl.question(`${question} (y/N): `, (answer) => { const trimmed = answer.trim().toLowerCase(); resolve(trimmed === 'y' || trimmed === 'yes'); }); }); } async function loadAccountApiConfig(parentOptions) { const authResult = auth_adapter_1.authAdapter.tryGetAuthConfig(parentOptions); if (!authResult) { throw new Error(auth_adapter_1.authAdapter.getStatusMessage(parentOptions)); } let configResult; try { configResult = await manager_1.configManager.load({ cliOptions: parentOptions }); } catch (error) { if (error instanceof Error && error.message.includes('Auth configuration is incomplete')) { configResult = { config: { baseUrl: 'https://api.polyv.net', timeout: DEFAULT_TIMEOUT_MS, debug: false, }, }; } else { throw error; } } return { authConfig: authResult.config, serviceConfig: { baseUrl: configResult.config.baseUrl, timeout: configResult.config.timeout, debug: configResult.config.debug, }, }; } function parsePositiveInteger(value) { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed <= 0) { throw new Error('Value must be a positive integer'); } return parsed; } function validateCallbackType(value) { if (!['stream', 'record', 'playback'].includes(value)) { throw new Error('type must be one of: stream, record, playback'); } return value; } function validateAccountApiUrl(value) { if (!value.startsWith('http://') && !value.startsWith('https://')) { throw new Error('url must start with http:// or https://'); } return value; } function withAccountApiHandler(program, run) { return (async () => { try { const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAccountApiConfig(parentOptions); const handler = new account_api_handler_1.AccountApiHandler(authConfig, serviceConfig); await run(handler); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } })(); } function formatAccountTable(accounts) { if (accounts.length === 0) { console.log('📝 No accounts configured.'); console.log(''); console.log('Use "polyv-live-cli account add <name>" to add your first account.'); return; } console.log('📋 Configured Accounts:'); console.log(''); const nameWidth = Math.max(4, ...accounts.map(a => a.name.length)); const appIdWidth = Math.max(6, ...accounts.map(a => a.appId.length)); const userIdWidth = Math.max(7, ...accounts.map(a => (a.userId || '').length)); const envWidth = Math.max(11, ...accounts.map(a => (a.environment || '-').length)); const header = `${'Name'.padEnd(nameWidth)} | ${'App ID'.padEnd(appIdWidth)} | ${'User ID'.padEnd(userIdWidth)} | ${'Environment'.padEnd(envWidth)} | Created`; console.log(header); console.log('-'.repeat(header.length)); accounts.forEach(account => { const createdDate = new Date(account.createdAt).toLocaleDateString(); const userId = account.userId || '-'; const environment = account.environment || '-'; console.log(`${account.name.padEnd(nameWidth)} | ${account.appId.padEnd(appIdWidth)} | ${userId.padEnd(userIdWidth)} | ${environment.padEnd(envWidth)} | ${createdDate}`); }); console.log(''); console.log(`Total: ${accounts.length} account${accounts.length === 1 ? '' : 's'}`); } async function handleAccountAdd(name, options) { try { const { appId, appSecret, userId, env: environment, baseUrl } = options; if (!appId) { console.error('❌ Error: --app-id is required'); console.log(''); console.log('Usage: polyv-live-cli account add <name> --app-id <id> --app-secret <secret> [options]'); console.log('Options: --user-id <uid> --env <env> --base-url <url>'); process.exit(1); } if (!appSecret) { console.error('❌ Error: --app-secret is required'); console.log(''); console.log('Usage: polyv-live-cli account add <name> --app-id <id> --app-secret <secret> [options]'); console.log('Options: --user-id <uid> --env <env> --base-url <url>'); process.exit(1); } if (environment && !['development', 'production', 'test', 'custom'].includes(environment)) { console.error('❌ Error: --env must be one of: development, production, test, custom'); process.exit(1); } if (baseUrl && !baseUrl.match(/^https?:\/\/.+/)) { console.error('❌ Error: --base-url must be a valid HTTP(S) URL'); process.exit(1); } const effectiveEnvironment = baseUrl ? 'custom' : environment; const manager = new account_config_1.AccountConfigManager(); const result = manager.addAccount(name, appId, appSecret, userId, effectiveEnvironment, baseUrl); if (result.success) { console.log('✅ ' + result.message); console.log(''); console.log('Account details:'); console.log(` Name: ${name}`); console.log(` App ID: ${appId}`); if (userId) { console.log(` User ID: ${userId}`); } if (effectiveEnvironment) { console.log(` Environment: ${effectiveEnvironment}`); } if (baseUrl) { console.log(` Base URL: ${baseUrl}`); } else if (effectiveEnvironment && effectiveEnvironment !== 'custom') { const { ENVIRONMENT_BASE_URLS } = await Promise.resolve().then(() => __importStar(require('../types/account.types'))); console.log(` Base URL: ${ENVIRONMENT_BASE_URLS[effectiveEnvironment] || 'https://api.polyv.net'}`); } console.log(` Config file: ${manager.getConfigPath()}`); } else { console.error('❌ ' + result.message); process.exit(1); } } catch (error) { console.error('❌ Failed to add account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountRemove(name, options) { try { const manager = new account_config_1.AccountConfigManager(); if (!manager.accountExists(name)) { console.error(`❌ Account '${name}' not found.`); console.log(''); console.log('Use "polyv-live-cli account list" to see available accounts.'); process.exit(1); } if (!options.force) { const rl = createReadlineInterface(); const confirmed = await promptConfirmation(rl, `Are you sure you want to remove account '${name}'?`); rl.close(); if (!confirmed) { console.log('❌ Account removal cancelled.'); return; } } const result = manager.removeAccount(name); if (result.success) { console.log('✅ ' + result.message); } else { console.error('❌ ' + result.message); process.exit(1); } } catch (error) { console.error('❌ Failed to remove account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountList(options) { try { const manager = new account_config_1.AccountConfigManager(); const accounts = manager.listAccounts(); if (options.output === 'json') { console.log(JSON.stringify(accounts, null, 2)); } else { formatAccountTable(accounts); } } catch (error) { console.error('❌ Failed to list accounts:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountMigrate(options) { try { const accountManager = new account_config_1.AccountConfigManager(); if (!global_1.globalConfig.exists()) { console.log('📋 No legacy configuration found.'); console.log(''); console.log('Legacy config file: ' + global_1.globalConfig.getConfigPath()); console.log('Use "polyv-live-cli account add <name>" to add new accounts.'); return; } const oldConfig = global_1.globalConfig.load(); const validation = global_1.globalConfig.validate(oldConfig); if (!validation.isValid) { console.error('❌ Legacy configuration is incomplete.'); console.error('Missing required fields:', validation.missingKeys.join(', ')); console.log(''); console.log('Please fix the legacy configuration first or remove it:'); console.log(` Legacy config file: ${global_1.globalConfig.getConfigPath()}`); process.exit(1); } const accountName = options.name || `legacy_${oldConfig.appId}`; if (accountManager.accountExists(accountName)) { console.error(`❌ Account '${accountName}' already exists.`); console.log(''); console.log('Use a different name with --name option or remove the existing account first:'); console.log(` polyv-live-cli account remove ${accountName}`); process.exit(1); } if (!options.force) { const rl = createReadlineInterface(); console.log('🔄 Legacy Configuration Migration'); console.log(''); console.log('Found legacy configuration:'); console.log(` App ID: ${oldConfig.appId}`); console.log(` User ID: ${oldConfig.userId || '(not set)'}`); console.log(` Base URL: ${oldConfig.baseUrl || '(default)'}`); console.log(''); console.log(`Will create new account: ${accountName}`); console.log(''); const confirmed = await promptConfirmation(rl, 'Do you want to migrate this configuration?'); rl.close(); if (!confirmed) { console.log('❌ Migration cancelled.'); return; } } const result = accountManager.addAccount(accountName, oldConfig.appId, oldConfig.appSecret, oldConfig.userId); if (!result.success) { console.error('❌ Failed to migrate account:', result.message); process.exit(1); } console.log('✅ ' + result.message); console.log(''); console.log('Migration details:'); console.log(` New account name: ${accountName}`); console.log(` App ID: ${oldConfig.appId}`); if (oldConfig.userId) { console.log(` User ID: ${oldConfig.userId}`); } console.log(` Config file: ${accountManager.getConfigPath()}`); console.log(''); if (!options.keepLegacy) { const rl = createReadlineInterface(); const shouldRemove = await promptConfirmation(rl, 'Remove legacy configuration file?'); rl.close(); if (shouldRemove) { const configPath = global_1.globalConfig.getConfigPath(); if ((0, fs_1.existsSync)(configPath)) { (0, fs_1.unlinkSync)(configPath); console.log('✅ Legacy configuration file removed.'); } else { console.log('✅ Legacy configuration already removed.'); } console.log(''); } } console.log('Migration completed successfully!'); console.log(''); console.log('Next steps:'); console.log(` 1. Use: polyv-live-cli use ${accountName}`); console.log(' 2. Test: polyv-live-cli account current'); } catch (error) { console.error('❌ Failed to migrate account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountSetDefault(accountName) { try { const accountManager = new account_config_1.AccountConfigManager(); const result = accountManager.setDefaultAccount(accountName); if (result.success) { console.log('✅ ' + result.message); } else { console.error('❌ ' + result.message); process.exit(1); } } catch (error) { console.error('❌ Failed to set default account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountUnsetDefault() { try { const accountManager = new account_config_1.AccountConfigManager(); const result = accountManager.unsetDefaultAccount(); if (result.success) { console.log('✅ ' + result.message); } else { console.error('❌ ' + result.message); process.exit(1); } } catch (error) { console.error('❌ Failed to unset default account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleAccountCurrent() { try { const accountManager = new account_config_1.AccountConfigManager(); const sessionManager = new session_state_1.SessionStateManager(accountManager); console.log('📋 Current Account Status:'); console.log(''); const sessionAccount = sessionManager.getCurrentSessionAccount(); const sessionState = sessionManager.getSessionState(); if (sessionAccount) { console.log('🎯 Current Session Account:'); console.log(` 账号名称: ${sessionAccount}`); if (sessionState) { const setTime = sessionState.setAt.toLocaleString('zh-CN'); console.log(` 设置时间: ${setTime}`); if (sessionState.expiresAt) { const expireTime = sessionState.expiresAt.toLocaleString('zh-CN'); console.log(` 过期时间: ${expireTime}`); } console.log(` 终端ID: ${sessionState.terminalId}`); console.log(` 进程ID: ${sessionState.processId}`); } if (!accountManager.accountExists(sessionAccount)) { console.log(' ⚠️ 警告: 当前会话账号在配置中不存在'); } console.log(''); } const authSource = sessionManager.getAuthSource(); console.log('🔐 Authentication Source:'); const sourceDesc = authSource.accountName ? `${authSource.description} (${authSource.accountName})` : authSource.description; console.log(` 来源: ${sourceDesc}`); console.log(` 优先级: ${authSource.priority}`); console.log(''); const defaultAccount = accountManager.getDefaultAccount(); if (defaultAccount) { console.log('🌟 Default Account:'); console.log(` 账号名称: ${defaultAccount}`); if (!accountManager.accountExists(defaultAccount)) { console.log(' ⚠️ 警告: 默认账号在配置中不存在'); } console.log(''); } const accounts = accountManager.listAccounts(); console.log('📝 Available Accounts:'); if (accounts.length === 0) { console.log(' 没有配置任何账号。'); console.log(''); console.log('使用 "polyv-live-cli account add <name>" 添加账号。'); } else { accounts.forEach(account => { const isCurrent = account.name === sessionAccount; const isDefault = account.name === defaultAccount; let marker = ''; if (isCurrent && isDefault) { marker = ' (当前会话, 默认)'; } else if (isCurrent) { marker = ' (当前会话)'; } else if (isDefault) { marker = ' (默认)'; } const createdDate = new Date(account.createdAt).toLocaleDateString('zh-CN'); console.log(` • ${account.name}${marker}`); console.log(` App ID: ${account.appId}`); if (account.userId) { console.log(` User ID: ${account.userId}`); } console.log(` 创建时间: ${createdDate}`); }); console.log(''); if (!sessionAccount && !defaultAccount) { console.log('使用 "polyv-live-cli use <account-name>" 切换到指定账号。'); console.log('或使用 "polyv-live-cli account set-default <account-name>" 设置默认账号。'); } else if (!sessionAccount && defaultAccount) { console.log('使用 "polyv-live-cli use <account-name>" 切换到指定账号。'); } } console.log('📁 Session Information:'); console.log(` 会话目录: ${sessionManager.getSessionDir()}`); console.log(` 环境变量: ${sessionManager.getEnvVarName()}`); } catch (error) { console.error('❌ Failed to get current account:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } function registerAccountCommands(program) { const accountCmd = program .command('account') .description('Manage PolyV account configurations'); accountCmd .command('add <name>') .description('Add a new account configuration') .requiredOption('--app-id <id>', 'PolyV application ID') .requiredOption('--app-secret <secret>', 'PolyV application secret') .option('--user-id <uid>', 'Optional user ID') .option('--env <env>', 'Environment type (development|production|test|custom)') .option('--base-url <url>', 'Custom base URL (sets environment to custom)') .action(handleAccountAdd); accountCmd .command('remove <name>') .description('Remove an account configuration') .option('--force', 'Skip confirmation prompt') .action(handleAccountRemove); accountCmd .command('list') .description('List all configured accounts') .option('--output <format>', 'Output format (table|json)', config_1.validateOutputFormat, 'table') .action(handleAccountList); accountCmd .command('current') .description('Show current account information') .action(handleAccountCurrent); accountCmd .command('migrate') .description('Migrate legacy configuration to new account system') .option('--name <name>', 'Name for the migrated account (default: legacy_<appId>)') .option('--force', 'Skip confirmation prompts') .option('--keep-legacy', 'Keep legacy configuration file after migration') .action(handleAccountMigrate); accountCmd .command('set-default <name>') .description('Set an account as the default account') .action(handleAccountSetDefault); accountCmd .command('unset-default') .description('Remove the current default account setting') .action(handleAccountUnsetDefault); const apiCmd = accountCmd .command('api') .description('Manage server-side account APIs'); apiCmd .command('channels') .description('List channel IDs under the current account') .option('--category-id <id>', 'category ID') .option('--keyword <keyword>', 'channel name keyword') .option('--label-id <id>', 'label ID') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listChannels({ categoryId: options.categoryId, keyword: options.keyword, labelId: options.labelId, output: options.output, }))); const apiPlaybackCmd = apiCmd .command('playback') .description('Account playback APIs'); apiPlaybackCmd .command('list') .description('List playback records under the current account') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('--start-date <date>', 'start date, yyyy-MM-dd') .option('--end-date <date>', 'end date, yyyy-MM-dd') .option('--keyword <keyword>', 'playback keyword') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listPlayback({ page: options.page, pageSize: options.pageSize, startDate: options.startDate, endDate: options.endDate, keyword: options.keyword, output: options.output, }))); const apiChannelCmd = apiCmd .command('channel') .description('Account channel list APIs'); apiChannelCmd .command('basic-list') .description('List account channel basic data') .option('--category-ids <ids>', 'category IDs, comma-separated') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('--keyword <keyword>', 'keyword') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listChannelBasic({ categoryIds: options.categoryIds, page: options.page, pageSize: options.pageSize, keyword: options.keyword, output: options.output, }))); apiChannelCmd .command('list') .description('List account channels with management fields') .option('--category-id <id>', 'category ID') .option('--watch-status <status>', 'watch status filter') .option('--keyword <keyword>', 'keyword') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listSimpleChannels({ categoryId: options.categoryId, watchStatus: options.watchStatus, keyword: options.keyword, page: options.page, pageSize: options.pageSize, output: options.output, }))); apiChannelCmd .command('detail-list') .description('List account channel details') .option('--category-id <id>', 'category ID') .option('--watch-status <status>', 'watch status filter') .option('--keyword <keyword>', 'keyword') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listChannelDetails({ categoryId: options.categoryId, watchStatus: options.watchStatus, keyword: options.keyword, page: options.page, pageSize: options.pageSize, output: options.output, }))); apiCmd .command('durations') .description('Get available account live minutes') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.getUserDurations({ output: options.output, }))); apiCmd .command('mic-duration') .description('Get account link-mic minute usage') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.getMicDuration({ output: options.output, }))); apiCmd .command('income-list') .description('List income details') .requiredOption('--user-id <id>', 'account user ID') .requiredOption('--start-date <date>', 'start date, yyyy-MM-dd') .requiredOption('--end-date <date>', 'end date, yyyy-MM-dd') .option('--channel-id <id>', 'channel ID') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listIncome({ userId: options.userId, startDate: options.startDate, endDate: options.endDate, channelId: options.channelId, page: options.page, pageSize: options.pageSize, output: options.output, }))); apiCmd .command('receive-list') .description('List channels receiving another channel stream') .requiredOption('--channel-id <id>', 'source channel ID') .option('--keyword <keyword>', 'keyword') .option('--page <page>', 'page number', parsePositiveInteger) .option('--page-size <size>', 'page size', parsePositiveInteger) .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listReceiveChannels({ channelId: options.channelId, keyword: options.keyword, page: options.page, pageSize: options.pageSize, output: options.output, }))); const apiCategoryCmd = apiCmd .command('category') .description('Live category management APIs'); apiCategoryCmd .command('list') .description('List live categories') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.listCategories({ output: options.output, }))); apiCategoryCmd .command('create') .description('Create live category') .requiredOption('--name <name>', 'category name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.createCategory({ name: options.name, force: options.force, output: options.output, }))); apiCategoryCmd .command('delete') .description('Delete live category') .requiredOption('--category-id <id>', 'category ID', parsePositiveInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.deleteCategory({ categoryId: options.categoryId, force: options.force, output: options.output, }))); apiCategoryCmd .command('update-name') .description('Update live category name') .requiredOption('--category-id <id>', 'category ID', parsePositiveInteger) .requiredOption('--name <name>', 'new category name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.updateCategoryName({ categoryId: options.categoryId, name: options.name, force: options.force, output: options.output, }))); apiCategoryCmd .command('update-rank') .description('Move a live category to after another category (更新分类排序:移动到指定分类之后)') .requiredOption('--category-id <id>', 'category ID to move', parsePositiveInteger) .requiredOption('--after-category-id <id>', 'target category ID to move after', parsePositiveInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.updateCategoryRank({ categoryId: options.categoryId, afterCategoryId: options.afterCategoryId, force: options.force, output: options.output, }))); const apiSsoCmd = apiCmd .command('sso') .description('SSO token APIs'); apiSsoCmd .command('set') .description('Set account or child account SSO token') .requiredOption('--token <token>', 'SSO token') .option('--child-email <email>', 'child account email') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.setSsoToken({ token: options.token, childEmail: options.childEmail, force: options.force, output: options.output, }))); const apiCallbackCmd = apiCmd .command('callback') .description('Account callback APIs'); apiCallbackCmd .command('set') .description('Set stream, record, or playback callback URL') .requiredOption('--type <type>', 'callback type (stream|record|playback)', validateCallbackType) .requiredOption('--user-id <id>', 'account user ID') .option('--url <url>', 'callback URL', validateAccountApiUrl) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', config_1.validateOutputFormat, 'table') .action((options) => withAccountApiHandler(program, handler => handler.setCallback({ type: options.type, userId: options.userId, url: options.url, force: options.force, output: options.output, }))); } //# sourceMappingURL=account.commands.js.map