UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

804 lines (790 loc) β€’ 41.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseInteger = parseInteger; exports.validateSize = validateSize; exports.validateOutputFormat = validateOutputFormat; exports.validateProductType = validateProductType; exports.validateProductStatus = validateProductStatus; exports.validateProductSortType = validateProductSortType; exports.validatePushCardType = validatePushCardType; exports.validateLinkType = validateLinkType; exports.validateUserProductLinkType = validateUserProductLinkType; exports.validatePrice = validatePrice; exports.registerProductCommands = registerProductCommands; const product_handler_1 = require("../handlers/product.handler"); const manager_1 = require("../config/manager"); const auth_adapter_1 = require("../config/auth-adapter"); const errors_1 = require("../utils/errors"); const api_command_1 = require("../utils/api-command"); async function loadAuthAndServiceConfig(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: 30000, debug: false } }; } else { throw error; } } const serviceConfig = { baseUrl: configResult.config.baseUrl, timeout: configResult.config.timeout, debug: configResult.config.debug }; const isVerbose = !!parentOptions.verbose; if (isVerbose) { console.log(`πŸ” Authentication Source: ${authResult.source}`); if (authResult.accountName) { console.log(`πŸ‘€ Account: ${authResult.accountName}`); } console.log(''); } const result = { authConfig: authResult.config, serviceConfig, isVerbose, }; if (authResult.source) { result.authSource = authResult.source; } if (authResult.accountName) { result.accountName = authResult.accountName; } return result; } function parseInteger(value) { const parsed = parseInt(value, 10); if (isNaN(parsed)) { throw new Error(`"${value}" is not a valid integer`); } return parsed; } function validateSize(value) { const parsed = parseInteger(value); if (parsed < 1 || parsed > 100) { throw new Error('Size must be between 1 and 100'); } return parsed; } function validateOutputFormat(value) { if (!['table', 'json'].includes(value)) { throw new Error('Output format must be either "table" or "json"'); } return value; } function validateProductType(value) { const validTypes = ['normal', 'finance', 'position']; if (!validTypes.includes(value)) { throw new Error('Product type must be one of: normal, finance, position'); } return value; } function validateProductStatus(value) { const parsed = parseInt(value, 10); if (parsed !== 1 && parsed !== 2) { throw new Error('Product status must be 1 (上枢) or 2 (δΈ‹ζžΆ)'); } return parsed; } function validateProductSortType(value) { const parsed = parseInt(value, 10); if (parsed !== 10 && parsed !== 20 && parsed !== 50) { throw new Error('Product sort type must be 10 (up), 20 (down), or 50 (rank)'); } return parsed; } function validatePushCardType(value) { if (value !== 'smallCard' && value !== 'bigCard') { throw new Error('Push card type must be smallCard or bigCard'); } return value; } function validateLinkType(value) { const parsed = parseInt(value, 10); if (parsed !== 10 && parsed !== 11) { throw new Error('Link type must be 10 (ι€šη”¨ι“ΎζŽ₯) or 11 (ε€šεΉ³ε°ι“ΎζŽ₯)'); } return parsed; } function validateUserProductLinkType(value) { const parsed = parseInt(value, 10); if (parsed !== 10 && parsed !== 11 && parsed !== 12) { throw new Error('Link type must be 10 (ι€šη”¨ι“ΎζŽ₯), 11 (ε€šεΉ³ε°ι“ΎζŽ₯), or 12 (εŽŸη”Ÿζ–Ήζ³•)'); } return parsed; } function validatePrice(value) { const parsed = parseFloat(value); if (isNaN(parsed)) { throw new Error(`"${value}" is not a valid price`); } if (parsed < 0) { throw new Error('Price cannot be negative'); } return parsed; } function withProductHandler(program, run) { return (async () => { try { const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); await run(new product_handler_1.ProductHandler(authConfig, serviceConfig)); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } })(); } function addPaging(command) { return command .option('-P, --page <number>', 'page number', parseInteger, 1) .option('-s, --size <number>', 'items per page', validateSize, 20) .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table'); } function addUserProductOptions(command, update = false) { if (update) { command.requiredOption('-p, --product-id <productId>', 'user product ID'); } command .requiredOption('-n, --name <name>', 'product name') .requiredOption('--link-type <type>', 'link type: 10 | 11 | 12', validateUserProductLinkType) .requiredOption('-l, --link <url>', 'product link') .option('-t, --product-type <type>', 'product type: normal | finance | position', validateProductType) .option('--cover <url>', 'product cover URL') .option('--pc-link <url>', 'PC link') .option('--mobile-link <url>', 'mobile web link') .option('--wx-miniprogram-link <url>', 'WeChat miniprogram link') .option('--wx-miniprogram-original-id <id>', 'WeChat miniprogram original ID') .option('--mobile-app-link <url>', 'mobile app link') .option('--android-link <url>', 'Android native link') .option('--ios-link <url>', 'iOS native link') .option('--other-link <url>', 'other platform link') .option('--features <json>', 'features/tags as JSON array string') .option('--tag-ids <ids>', 'comma-separated product tag IDs') .option('--btn-show <text>', 'button text') .option('--product-desc <desc>', 'product description') .option('--product-detail <detail>', 'product detail') .option('--ext <json>', 'extended product info JSON string') .option('--price-type <type>', 'price type: AMOUNT | CUSTOM') .option('--real-price <amount>', 'real price amount', validatePrice) .option('--custom-price <text>', 'custom price text') .option('--original-price-type <type>', 'original price type: AMOUNT | CUSTOM') .option('--price <amount>', 'original price amount', validatePrice) .option('--custom-original-price <text>', 'custom original price text') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table'); return command; } function toUserProductOptions(options) { return { ...(options.productId ? { productId: options.productId } : {}), name: options.name, linkType: options.linkType, link: options.link, productType: options.productType, cover: options.cover, pcLink: options.pcLink, mobileLink: options.mobileLink, wxMiniprogramLink: options.wxMiniprogramLink, wxMiniprogramOriginalId: options.wxMiniprogramOriginalId, mobileAppLink: options.mobileAppLink, androidLink: options.androidLink, iosLink: options.iosLink, otherLink: options.otherLink, features: options.features, tagIds: options.tagIds, btnShow: options.btnShow, productDesc: options.productDesc, productDetail: options.productDetail, ext: options.ext, priceType: options.priceType, realPrice: options.realPrice, customPrice: options.customPrice, originalPriceType: options.originalPriceType, price: options.price, customOrignalPrice: options.customOriginalPrice, force: options.force, output: options.output, }; } function registerProductCommands(program) { const productCmd = program.command('product'); productCmd.description('Manage live streaming channel products'); const listCmd = productCmd .command('list') .description('List products with pagination') .option('-c, --channel-id <channelId>', 'channel ID (required unless --platform is specified)') .option('--platform', 'list platform products (user-level product library) instead of channel products') .option('-P, --page <number>', 'page number (optional, minimum 1, default 1)', parseInteger, 1) .option('-s, --size <number>', 'items per page (optional, 1-100, default 20)', validateSize, 20) .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action(async (options) => { try { if (!options.platform && !options.channelId) { console.error('Error: --channel-id is required unless --platform is specified'); process.exit(1); } const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const productHandler = new product_handler_1.ProductHandler(authConfig, serviceConfig); const listOptions = { page: options.page, size: options.size, channelId: options.channelId, platform: options.platform, output: options.output }; await productHandler.listProducts(listOptions); } catch (error) { if (error instanceof Error && error.message.includes('Authentication')) { const diagnostics = auth_adapter_1.authAdapter.getDiagnostics(program.opts()); console.error('\nπŸ” Authentication Diagnostics:'); diagnostics.availableSources.forEach(source => { const status = source.appId && source.appSecret ? 'βœ…' : '❌'; console.error(` ${status} ${source.metadata.source}: ${source.type}`); }); if (diagnostics.errors.length > 0) { console.error('\n❌ Errors:'); diagnostics.errors.forEach(error => console.error(` - ${error}`)); } } (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); listCmd.addHelpText('after', ` Examples: # List channel products (requires channel ID) $ polyv-live-cli product list -c "3151318" $ polyv-live-cli product list -c "3151318" -P 2 -s 10 $ polyv-live-cli product list -c "3151318" -o json # List platform products (user-level product library) $ polyv-live-cli product list --platform $ polyv-live-cli product list --platform -P 2 -s 10 Alternative (full parameter names): $ polyv-live-cli product list --channel-id "3151318" --page 2 --size 10 --output json $ polyv-live-cli product list --platform --page 2 --size 10 Modes: --channel-id, -c List products for a specific channel (V3 API, includes channelId in response) --platform List platform products from user-level product library (V4 API, no channelId) Pagination: --page, -P Page number (minimum 1, default 1) --size, -s Items per page (1-100, default 20) Output Formats: table - Formatted table output (default) json - JSON format for programmatic use Notes: - Use --channel-id for channel-specific products - Use --platform for platform-level product library - Channel products include channelId in the response - All product prices are shown in Chinese Yuan (Β₯) `); const addCmd = productCmd .command('add') .description('Add a new product to channel') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-n, --name <name>', 'product name') .requiredOption('--status <status>', 'product status: 1 (on-shelf) | 2 (off-shelf)', validateProductStatus) .requiredOption('--link-type <type>', 'link type: 10 (universal) | 11 (multi-platform)', validateLinkType) .option('-t, --product-type <type>', 'product type: normal | finance | position (default: normal)', validateProductType, 'normal') .option('--cover <url>', 'product cover URL') .option('-l, --link <url>', 'universal link (required when link-type=10)') .option('--pc-link <url>', 'PC link') .option('--mobile-link <url>', 'mobile web link') .option('--wx-miniprogram-link <url>', 'WeChat miniprogram link') .option('--wx-miniprogram-original-id <id>', 'WeChat miniprogram original ID') .option('--mobile-app-link <url>', 'mobile app link') .option('--android-link <url>', 'Android native link') .option('--ios-link <url>', 'iOS native link') .option('--params <json>', 'custom params as JSON string') .option('--product-desc <desc>', 'product description') .option('--features <json>', 'features/tags as JSON array string') .option('--btn-show <text>', 'button text') .option('--yield <value>', 'yield/rate for finance products') .option('--price-type <type>', 'price type: AMOUNT | CUSTOM') .option('--real-price <amount>', 'real price amount', validatePrice) .option('--custom-price <text>', 'custom price text') .option('--original-price-type <type>', 'original price type: AMOUNT | CUSTOM') .option('--price <amount>', 'original price amount', validatePrice) .option('--custom-original-price <text>', 'custom original price text') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action(async (options) => { try { const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const productHandler = new product_handler_1.ProductHandler(authConfig, serviceConfig); const addOptions = { channelId: options.channelId, name: options.name, status: options.status, linkType: options.linkType, productType: options.productType, cover: options.cover, link: options.link, pcLink: options.pcLink, mobileLink: options.mobileLink, wxMiniprogramLink: options.wxMiniprogramLink, wxMiniprogramOriginalId: options.wxMiniprogramOriginalId, mobileAppLink: options.mobileAppLink, androidLink: options.androidLink, iosLink: options.iosLink, params: options.params, productDesc: options.productDesc, features: options.features, btnShow: options.btnShow, yield: options.yield, priceType: options.priceType, realPrice: options.realPrice, customPrice: options.customPrice, originalPriceType: options.originalPriceType, price: options.price, customOrignalPrice: options.customOriginalPrice, output: options.output }; await productHandler.addProduct(addOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); addCmd.addHelpText('after', ` Examples: # Add a normal product $ polyv-live-cli product add -c "3151318" -n "桋试商品" --status 1 --link-type 10 \\ -l "https://example.com/product" --cover "https://example.com/cover.jpg" \\ --real-price 99.9 --price 199.9 # Add a finance product $ polyv-live-cli product add -c "3151318" -n "理贒产品" --status 1 --link-type 10 \\ -l "https://example.com/finance" --yield "5.5%" -t finance Product Types: normal - Regular product (default) finance - Financial product position - Job position Status Values: 1 - On shelf (上枢) 2 - Off shelf (δΈ‹ζžΆ) Link Types: 10 - Universal link (requires --link) 11 - Multi-platform link (use --pc-link, --mobile-link, etc.) Price Types: AMOUNT - Numeric price (use --real-price, --price) CUSTOM - Custom text price (use --custom-price) `); const updateCmd = productCmd .command('update') .description('Update an existing product') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('-n, --name <name>', 'product name') .option('--status <status>', 'product status: 1 | 2', validateProductStatus) .option('--link-type <type>', 'link type: 10 | 11', validateLinkType) .option('-t, --product-type <type>', 'product type: normal | finance | position', validateProductType) .option('--cover <url>', 'product cover URL') .option('-l, --link <url>', 'universal link') .option('--pc-link <url>', 'PC link') .option('--mobile-link <url>', 'mobile web link') .option('--wx-miniprogram-link <url>', 'WeChat miniprogram link') .option('--wx-miniprogram-original-id <id>', 'WeChat miniprogram original ID') .option('--mobile-app-link <url>', 'mobile app link') .option('--android-link <url>', 'Android native link') .option('--ios-link <url>', 'iOS native link') .option('--params <json>', 'custom params as JSON string') .option('--product-desc <desc>', 'product description') .option('--features <json>', 'features/tags as JSON array string') .option('--btn-show <text>', 'button text') .option('--yield <value>', 'yield/rate for finance products') .option('--price-type <type>', 'price type: AMOUNT | CUSTOM') .option('--real-price <amount>', 'real price amount', validatePrice) .option('--custom-price <text>', 'custom price text') .option('--original-price-type <type>', 'original price type: AMOUNT | CUSTOM') .option('--price <amount>', 'original price amount', validatePrice) .option('--custom-original-price <text>', 'custom original price text') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action(async (options) => { try { const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const productHandler = new product_handler_1.ProductHandler(authConfig, serviceConfig); const updateOptions = { channelId: options.channelId, productId: options.productId, name: options.name, status: options.status, linkType: options.linkType, cover: options.cover, link: options.link, pcLink: options.pcLink, mobileLink: options.mobileLink, wxMiniprogramLink: options.wxMiniprogramLink, wxMiniprogramOriginalId: options.wxMiniprogramOriginalId, mobileAppLink: options.mobileAppLink, androidLink: options.androidLink, iosLink: options.iosLink, params: options.params, productDesc: options.productDesc, features: options.features, btnShow: options.btnShow, yield: options.yield, priceType: options.priceType, realPrice: options.realPrice, customPrice: options.customPrice, originalPriceType: options.originalPriceType, price: options.price, customOrignalPrice: options.customOriginalPrice, output: options.output }; await productHandler.updateProduct(updateOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); updateCmd.addHelpText('after', ` Examples: # Update product name $ polyv-live-cli product update -c "3151318" -p 12345 -n "新商品名" --status 1 --link-type 10 # Change product status to off-shelf $ polyv-live-cli product update -c "3151318" -p 12345 -n "商品名" --status 2 --link-type 10 `); const deleteCmd = productCmd .command('delete') .description('Delete a product from channel') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('-f, --force', 'force delete without confirmation') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action(async (options) => { try { const parentOptions = program.opts(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const productHandler = new product_handler_1.ProductHandler(authConfig, serviceConfig); const deleteOptions = { channelId: options.channelId, productId: options.productId, force: options.force || false, output: options.output }; await productHandler.deleteProduct(deleteOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); deleteCmd.addHelpText('after', ` Examples: # Delete with confirmation $ polyv-live-cli product delete -c "3151318" -p 12345 # Force delete without confirmation $ polyv-live-cli product delete -c "3151318" -p 12345 --force Note: Deletion is permanent and cannot be undone. Use --force to skip the confirmation prompt. `); productCmd.command('enabled') .description('Get channel product library enabled status') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.getChannelProductEnabled(options))); productCmd.command('update-enabled') .description('Update channel product library enabled status') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--enabled <yn>', 'enabled flag (Y|N)', api_command_1.validateYn) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.updateChannelProductEnabled(options))); productCmd.command('batch-add') .description('Batch add products to a channel') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--products-json <json>', 'products JSON array', api_command_1.parseJsonArray) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.batchAddChannelProducts({ ...options, products: options.productsJson }))); productCmd.command('batch-delete') .description('Batch delete channel products') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--product-ids <ids>', 'product IDs, comma-separated', api_command_1.parseNumberList) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.batchDeleteChannelProducts(options))); productCmd.command('batch-shelf') .description('Batch update channel product shelf status') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--product-ids <ids>', 'product IDs, comma-separated', api_command_1.parseNumberList) .requiredOption('--shelf <status>', 'shelf status: 1 (on shelf) | 2 (off shelf)', validateProductStatus) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.batchShelfChannelProducts(options))); productCmd.command('shelf') .description('Update one channel product shelf status') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .requiredOption('--shelf <status>', 'shelf status: 1 (on shelf) | 2 (off shelf)', validateProductStatus) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.shelfChannelProduct(options))); productCmd.command('sort') .description('Sort a channel product') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .requiredOption('--type <type>', '10 up, 20 down, 50 explicit rank', validateProductSortType) .option('--sort <rank>', 'target rank when type=50', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.sortChannelProduct(options))); productCmd.command('push') .description('Push a channel product to viewers') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('--push-card-type <type>', 'push card type: smallCard | bigCard', validatePushCardType) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.pushChannelProduct(options))); productCmd.command('cancel-push') .description('Cancel a pushed channel product') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.cancelPushChannelProduct(options))); productCmd.command('reference') .description('Reference a platform product into a channel product library') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--origin-id <id>', 'platform product origin ID') .requiredOption('--status <status>', 'shelf status: 1 (on shelf) | 2 (off shelf)', validateProductStatus) .option('--with-tags', 'sync platform tags') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.referenceProduct(options))); const pushRuleCmd = productCmd.command('push-rule').description('Manage channel product push rule'); pushRuleCmd.command('get') .description('Get channel product push rule') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.getProductPushRule(options))); pushRuleCmd.command('update') .description('Update channel product push rule') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('--product-explain-enabled <yn>', 'product explain enabled (Y|N)', api_command_1.validateYn) .option('--product-explaining-auto-push-and-sticky <yn>', 'auto push and sticky explaining product (Y|N)', api_command_1.validateYn) .option('--product-list-sort-type <type>', 'product list sort type') .option('--product-tag-sort-type <type>', 'product tag sort type') .option('--product-push-rule <rule>', 'product push rule: smallCard | bigCard | chooseCard') .option('--product-hot-effect-enabled <yn>', 'product hot effect enabled (Y|N)', api_command_1.validateYn) .option('--normal-product-hot-effect-tips <text>', 'normal product hot effect tips') .option('--job-product-hot-effect-tips <text>', 'job product hot effect tips') .option('--finance-product-hot-effect-tips <text>', 'finance product hot effect tips') .option('--out-link-product-redirect-enabled <yn>', 'out-link redirect enabled (Y|N)', api_command_1.validateYn) .option('--product-tag-sort-order-ids <ids>', 'product tag sort order IDs, comma-separated', api_command_1.parseNumberList) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.updateProductPushRule(options))); const channelTagCmd = productCmd.command('channel-tag').description('Manage channel product tags'); addPaging(channelTagCmd.command('list').description('List channel product tags')) .requiredOption('-c, --channel-id <channelId>', 'channel ID') .action((options) => withProductHandler(program, handler => handler.listChannelProductTags({ channelId: options.channelId, page: options.page, size: options.size, output: options.output, }))); channelTagCmd.command('create') .description('Create a channel product tag') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-n, --name <name>', 'tag name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.createChannelProductTag({ channelId: options.channelId, name: options.name, force: options.force, output: options.output, }))); channelTagCmd.command('update') .description('Update a channel product tag') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--id <id>', 'tag ID', parseInteger) .requiredOption('-n, --name <name>', 'tag name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.updateChannelProductTag({ channelId: options.channelId, id: options.id, name: options.name, force: options.force, output: options.output, }))); channelTagCmd.command('delete') .description('Delete a channel product tag') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('--id <id>', 'tag ID', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.deleteChannelProductTag({ channelId: options.channelId, id: options.id, force: options.force, output: options.output, }))); const statsCmd = productCmd.command('stats').description('Query channel product statistics'); addPaging(statsCmd.command('list').description('List channel product statistics')) .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('--product-id <productId>', 'product ID') .option('--product-name <productName>', 'product name filter') .option('--session-id <sessionId>', 'session ID') .action((options) => withProductHandler(program, handler => handler.listProductStats({ channelId: options.channelId, productId: options.productId, productName: options.productName, sessionId: options.sessionId, page: options.page, size: options.size, output: options.output, }))); statsCmd.command('summary') .description('Get channel product statistics summary') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('--session-id <sessionId>', 'session ID') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.getProductStatsSummary(options))); productCmd.command('rank') .description('Set channel product rank') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .requiredOption('--rank <rank>', 'target rank', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.sortChannelProductRank(options))); productCmd.command('topping') .description('Top a channel product') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.toppingChannelProduct(options))); productCmd.command('untopping') .description('Cancel topping for a channel product') .requiredOption('-c, --channel-id <channelId>', 'channel ID') .requiredOption('-p, --product-id <productId>', 'product ID', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.untoppingChannelProduct(options))); const libraryCmd = productCmd.command('library').description('Manage user-level product library'); addPaging(libraryCmd.command('list').description('List user-level product library')) .option('-k, --keyword <keyword>', 'keyword search') .option('-t, --product-type <type>', 'product type filter') .action((options) => withProductHandler(program, handler => handler.listUserProducts({ page: options.page, size: options.size, keyword: options.keyword, productType: options.productType, output: options.output, }))); addUserProductOptions(libraryCmd.command('create').description('Create a user-level product')) .action((options) => withProductHandler(program, handler => handler.createUserProduct(toUserProductOptions(options)))); addUserProductOptions(libraryCmd.command('update').description('Update a user-level product'), true) .action((options) => withProductHandler(program, handler => handler.updateUserProduct(toUserProductOptions(options)))); libraryCmd.command('delete') .description('Delete a user-level product') .requiredOption('-p, --product-id <productId>', 'user product ID') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.deleteUserProduct({ productId: options.productId, force: options.force, output: options.output, }))); const tagCmd = productCmd.command('tag').description('Manage user-level product tags'); addPaging(tagCmd.command('list').description('List user-level product tags')) .requiredOption('-c, --channel-id <channelId>', 'channel ID') .option('-k, --keyword <keyword>', 'keyword search') .action((options) => withProductHandler(program, handler => handler.listProductTags({ channelId: options.channelId, page: options.page, size: options.size, keyword: options.keyword, output: options.output, }))); tagCmd.command('create') .description('Create a user-level product tag') .requiredOption('-n, --name <name>', 'tag name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.createProductTag({ name: options.name, force: options.force, output: options.output, }))); tagCmd.command('update') .description('Update a user-level product tag') .requiredOption('--id <id>', 'tag ID', parseInteger) .requiredOption('-n, --name <name>', 'tag name') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.updateProductTag({ id: options.id, name: options.name, force: options.force, output: options.output, }))); tagCmd.command('delete') .description('Delete a user-level product tag') .requiredOption('--id <id>', 'tag ID', parseInteger) .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.deleteProductTag({ id: options.id, force: options.force, output: options.output, }))); const orderCmd = productCmd.command('order').description('Manage user-level product orders'); addPaging(orderCmd.command('list').description('List user-level product orders')) .action((options) => withProductHandler(program, handler => handler.listProductOrders({ page: options.page, size: options.size, output: options.output, }))); orderCmd.command('get') .description('Get a user-level product order') .requiredOption('--order-no <orderNo>', 'order number') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.getProductOrder({ orderNo: options.orderNo, output: options.output, }))); orderCmd.command('batch-status') .description('Batch update user-level product order status') .requiredOption('--order-nos <orderNos>', 'comma-separated order numbers') .requiredOption('--status <status>', 'new order status') .option('-f, --force', 'skip confirmation prompt') .option('-o, --output <format>', 'output format (table|json)', validateOutputFormat, 'table') .action((options) => withProductHandler(program, handler => handler.batchUpdateProductOrderStatus({ orderNos: options.orderNos, status: options.status, force: options.force, output: options.output, }))); } //# sourceMappingURL=product.commands.js.map