#!/usr/bin/env bun import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import os from 'os'; // Dynamically import ZAI, supporting global installation (Node.js and Bun) async function getZAI() { try { // Try importing from package name (when globally installed) const packageName = 'z-ai-web-dev-sdk'; const { default: ZAI } = await import(packageName); return ZAI; } catch (error1) { try { // Try importing directly from global node_modules (container environment fix) const globalPackagePath = '/usr/lib/node_modules/z-ai-web-dev-sdk/dist/index.js'; const { default: ZAI } = await import(globalPackagePath); return ZAI; } catch (error2) { try { // Dynamically get global path (supporting Node.js and Bun) const { execSync } = await import('child_process'); let globalRoot = ''; // Detect runtime environment: check Bun global object or process.versions.bun const isBun = (typeof globalThis.Bun !== 'undefined') || (typeof process !== 'undefined' && process.versions.bun); if (isBun) { // Bun environment: try multiple ways to find global package path const homeDir = os.homedir(); const possiblePaths = [ path.join(homeDir, '.bun', 'install', 'global'), path.join(homeDir, '.bun', 'bin', 'node_modules'), '/usr/local/lib/bun/install/global', ]; // Try using bun pm cache command to get cache path try { const bunCache = execSync('bun pm cache', { encoding: 'utf-8' }).trim(); possiblePaths.unshift(path.join(bunCache, 'global')); } catch { // Ignore error, continue with other paths } // Try each possible path let found = false; for (const possiblePath of possiblePaths) { try { const testPath = path.join(possiblePath, 'z-ai-web-dev-sdk', 'dist', 'index.js'); await fs.access(testPath); globalRoot = possiblePath; found = true; break; } catch { // Continue trying next path } } // If all paths failed, use the first default path if (!found) { globalRoot = possiblePaths[0]; } } else { // Node.js environment: use npm root -g globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim(); } // Ensure globalRoot is assigned if (!globalRoot) { throw new Error('Unable to determine global package path'); } const globalPackagePath = path.join(globalRoot, 'z-ai-web-dev-sdk', 'dist', 'index.js'); const { default: ZAI } = await import(globalPackagePath); return ZAI; } catch (error3) { // Finally fallback to relative path import (for development) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const indexPath = path.resolve(__dirname, './index.js'); const { default: ZAI } = await import(indexPath); return ZAI; } } } } // Generic parameter parsing function function parseKeyValueArgs(args) { const result = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const key = arg.slice(2); const nextArg = args[i + 1]; if (nextArg && !nextArg.startsWith('--')) { result[key] = nextArg; i++; } else { result[key] = 'true'; } } else if (arg.startsWith('-') && arg.length === 2) { const key = arg.slice(1); const nextArg = args[i + 1]; if (nextArg && !nextArg.startsWith('-')) { result[key] = nextArg; i++; } else { result[key] = 'true'; } } } return result; } // Show main help information function showMainHelp() { console.log(` Z-AI SDK CLI - Multi-purpose AI Tool Usage: z-ai [options] z-ai-generate [options] # Backward compatible: image generation Commands: chat Chat completion vision Vision model chat tts Text to speech asr Speech to text image Image generation image-edit Image editing image-search Image search (ZAI in-house, returns OSS-hosted URLs) video Video generation async-result Query async result function Function invocation Use "z-ai --help" to view help for specific commands `); } // Show chat completion help function showChatHelp() { console.log(` Z-AI SDK CLI - Chat Completion Usage: z-ai chat [options] Options: --prompt, -p Required: User message content --system, -s Optional: System prompt --thinking, -t Optional: Enable thinking chain (default: disabled) --output, -o Optional: Output file path (JSON format) --stream Optional: Stream output --help, -h Show help information Examples: z-ai chat --prompt "Hello" --output response.json z-ai chat -p "Explain quantum computing" --system "You are a professional physicist" --thinking `); } // Show vision chat help function showVisionHelp() { console.log(` Z-AI SDK CLI - Vision Model Chat Usage: z-ai vision [options] Options: --prompt, -p Required: User message content --image, -i Optional: Image URL or local file path (can be used multiple times) --thinking, -t Optional: Enable thinking chain (default: disabled) --output, -o Optional: Output file path (JSON format) --stream Optional: Stream output --help, -h Show help information Examples: z-ai vision --prompt "Describe this image" --image "https://example.com/image.jpg" z-ai vision -p "What is this?" -i "./photo.jpg" --thinking -o response.json `); } // Show TTS help function showTTSHelp() { console.log(` Z-AI SDK CLI - Text to Speech Usage: z-ai tts [options] Options: --input, -i Required: Text to convert --output, -o Required: Output audio file path --voice, -v Optional: Voice type (default: tongtong) --speed, -s Optional: Speech rate (0.5-2.0, default: 1.0) --format, -f Optional: Output format (wav, mp3, pcm, default: wav) --stream Optional: Stream output --help, -h Show help information Examples: z-ai tts --input "Hello, world" --output "./output.wav" z-ai tts -i "Hello World" -o "./hello.mp3" -v "tongtong" -s 1.2 `); } // Show ASR help function showASRHelp() { console.log(` Z-AI SDK CLI - Speech to Text Usage: z-ai asr [options] Options: --file, -f Required: Audio file path (or use --base64) --base64, -b Optional: Base64 encoded audio file --output, -o Optional: Output file path (JSON format) --stream Optional: Stream output --help, -h Show help information Examples: z-ai asr --file "./audio.wav" --output transcript.json z-ai asr -f "./recording.mp3" -o result.json `); } // Show image generation help function showImageHelp() { console.log(` Z-AI SDK CLI - Image Generation Usage: z-ai image [options] z-ai-generate [options] # Backward compatible Options: --prompt, -p Required: Image description text --output, -o Required: Output image file path (png format) --size, -s Optional: Image size (default: 1024x1024) Supported sizes: 1024x1024, 768x1344, 864x1152, 1344x768, 1152x864, 1440x720, 720x1440 --help, -h Show help information Examples: z-ai image --prompt "A cute kitten" --output "./cat.png" z-ai-generate -p "Beautiful landscape" -o "./landscape.png" -s 1344x768 `); } // Show image edit help function showImageEditHelp() { console.log(` Z-AI SDK CLI - Image Editing Usage: z-ai image-edit [options] Options: --prompt, -p Required: Image editing description --image, -i Required: Original image URL or local file path --output, -o Required: Output image file path (png format) --size, -s Optional: Image size (default: 1024x1024) Supported sizes: 1024x1024, 768x1344, 864x1152, 1344x768, 1152x864, 1440x720, 720x1440 --help, -h Show help information Examples: z-ai image-edit --prompt "把背景改成蓝天白云" --image "./photo.png" --output "./edited.png" z-ai image-edit -p "Add a hat to the person" -i "https://example.com/image.png" -o "./result.png" `); } // Show image search help function showImageSearchHelp() { console.log(` Z-AI SDK CLI - Image Search (ZAI in-house service) Usage: z-ai image-search [options] Searches public web images for a natural-language query, re-hosts each hit on OSS so the URL is embeddable, and (by default) attaches a short Gemini caption. Options: --query, -q Required: natural-language description of the image --count, -c Optional: number of results, default 10, max ~20 --gl Optional: region code, default cn (cn|us|jp|kr|...) --no-rank Optional: disable Gemini captioning (faster) --output, -o Optional: write full JSON response to file --help, -h Show help information Examples: z-ai image-search -q "a cute orange tabby kitten playing with yarn" z-ai image-search -q "vintage red sports car" --count 5 --gl us --no-rank z-ai image-search -q "中国传统水墨山水画" -c 8 -o results.json `); } // Show video generation help function showVideoHelp() { console.log(` Z-AI SDK CLI - Video Generation Usage: z-ai video [options] Options: --prompt, -p Optional: Video description text --image-url, -i Optional: Image URL (single image or first/last frame array) --quality, -q Optional: Output mode speed or quality (default: speed) --with-audio Optional: Generate AI audio (default: false) --size, -s Optional: Video resolution, e.g. "1920x1080" --fps Optional: Video frame rate 30 or 60 (default: 30) --duration, -d Optional: Video duration in seconds 5 or 10 (default: 5) --model, -m Optional: Model name --poll Optional: Auto-poll until task completes --poll-interval Optional: Polling interval in seconds (default: 5) --max-polls Optional: Maximum polling count (default: 60) --output, -o Optional: Output result file path (JSON format) --help, -h Show help information Examples: z-ai video --prompt "A cat is playing with a ball" --poll z-ai video -p "Beautiful landscape" -q quality --size "1920x1080" --fps 60 --poll z-ai video --image-url "https://example.com/image.png" --prompt "Make the scene move" --poll `); } // Show async result query help function showAsyncResultHelp() { console.log(` Z-AI SDK CLI - Query Async Result Usage: z-ai async-result [options] Options: --id, -i Required: Task ID --poll Optional: Auto-poll until task completes --poll-interval Optional: Polling interval in seconds (default: 5) --max-polls Optional: Maximum polling count (default: 60) --output, -o Optional: Output result file path (JSON format) --help, -h Show help information Examples: z-ai async-result --id "16591731777601843-8059626559669415615" z-ai async-result -i "task-id-123" --poll z-ai async-result --id "task-id-123" --poll --poll-interval 10 --max-polls 30 `); } // Show function invocation help function showFunctionHelp() { console.log(` Z-AI SDK CLI - Function Invocation Usage: z-ai function [options] Options: --name, -n Required: Function name (e.g.: web_search) --args, -a Required: Function arguments (JSON format) --output, -o Optional: Output file path (JSON format) --help, -h Show help information Examples: z-ai function --name "web_search" --args '{"query": "AI", "num": 5}' z-ai function -n web_search -a '{"query": "TypeScript", "num": 3}' -o result.json `); } // Utility functions function base64ToBuffer(base64Data) { return Buffer.from(base64Data, 'base64'); } async function saveFile(content, outputPath) { try { const outputDir = path.dirname(outputPath); await fs.mkdir(outputDir, { recursive: true }); if (Buffer.isBuffer(content)) { await fs.writeFile(outputPath, content); } else { await fs.writeFile(outputPath, content, 'utf-8'); } console.log(`✅ File saved to: ${outputPath}`); } catch (error) { throw new Error(`Failed to save file: ${error}`); } } async function readFileAsBase64(filePath) { try { const buffer = await fs.readFile(filePath); return buffer.toString('base64'); } catch (error) { throw new Error(`Failed to read file: ${error}`); } } // Handle streaming response (SSE format) async function handleStreamResponse(stream, outputPath) { const reader = stream.getReader(); const decoder = new TextDecoder(); let buffer = ''; let fullContent = ''; const outputLines = []; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6).trim(); if (data === '' || data === '[DONE]') continue; try { const json = JSON.parse(data); // Process SSE data if (json.choices && json.choices[0]) { const choice = json.choices[0]; if (choice.delta && choice.delta.content) { const content = choice.delta.content; process.stdout.write(content); fullContent += content; outputLines.push(JSON.stringify(json, null, 2)); } } else { // Output JSON directly outputLines.push(JSON.stringify(json, null, 2)); console.log(JSON.stringify(json, null, 2)); } } catch (e) { // Ignore JSON parsing errors } } } } // Output newline console.log(''); // If output file is specified, save full content if (outputPath) { if (fullContent) { await saveFile(fullContent, outputPath); } else if (outputLines.length > 0) { await saveFile(outputLines.join('\n'), outputPath); } } } catch (error) { console.error('❌ Failed to process streaming response:', error); throw error; } finally { reader.releaseLock(); } } // Handle chat completion command async function handleChat(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showChatHelp(); return; } const prompt = params.prompt || params.p; if (!prompt) { console.error('❌ Error: Missing required parameter --prompt'); showChatHelp(); process.exit(1); } const messages = []; if (params.system || params.s) { messages.push({ role: 'system', content: params.system || params.s, }); } messages.push({ role: 'user', content: prompt, }); const body = { messages, }; // Handle thinking parameter (boolean flag, enabled if provided, default disabled) if (params.thinking || params.t) { body.thinking = { type: 'enabled' }; } else { body.thinking = { type: 'disabled' }; } if (params.stream) { body.stream = true; } console.log('🚀 Sending chat request...'); const response = await client.chat.completions.create(body); // Handle streaming response if (body.stream && response && typeof response.getReader === 'function') { await handleStreamResponse(response, params.output || params.o); return; } const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(response, null, 2), output); } else { console.log(JSON.stringify(response, null, 2)); } } // Handle vision chat command async function handleVision(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showVisionHelp(); return; } const prompt = params.prompt || params.p; if (!prompt) { console.error('❌ Error: Missing required parameter --prompt'); showVisionHelp(); process.exit(1); } const content = [{ type: 'text', text: prompt }]; // Handle image parameters (possibly multiple) - extract from original args for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--image' || arg === '-i') { const imageArg = args[i + 1]; if (imageArg && !imageArg.startsWith('-')) { let imageUrl = imageArg; // If it's a local file path, need to convert to base64 if (!imageArg.startsWith('http://') && !imageArg.startsWith('https://') && !imageArg.startsWith('data:')) { // Local file, read as base64 try { const base64 = await readFileAsBase64(imageArg); // Determine MIME type based on file extension const ext = path.extname(imageArg).toLowerCase(); const mimeTypes = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', }; const mimeType = mimeTypes[ext] || 'image/jpeg'; imageUrl = `data:${mimeType};base64,${base64}`; } catch (error) { console.error(`❌ Unable to read image file: ${imageArg}`); process.exit(1); } } content.push({ type: 'image_url', image_url: { url: imageUrl }, }); i++; // Skip processed image parameter value } } } const body = { messages: [ { role: 'user', content, }, ], }; // Handle thinking parameter (boolean flag, enabled if provided, default disabled) if (params.thinking || params.t) { body.thinking = { type: 'enabled' }; } else { body.thinking = { type: 'disabled' }; } if (params.stream) { body.stream = true; } console.log('🚀 Sending vision chat request...'); const response = await client.chat.completions.createVision(body); // Handle streaming response if (body.stream && response && typeof response.getReader === 'function') { await handleStreamResponse(response, params.output || params.o); return; } const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(response, null, 2), output); } else { console.log(JSON.stringify(response, null, 2)); } } // Handle TTS command async function handleTTS(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showTTSHelp(); return; } const input = params.input || params.i; if (!input) { console.error('❌ Error: Missing required parameter --input'); showTTSHelp(); process.exit(1); } const output = params.output || params.o; if (!output) { console.error('❌ Error: Missing required parameter --output'); showTTSHelp(); process.exit(1); } const body = { input, }; if (params.voice || params.v) { body.voice = params.voice || params.v; } else { body.voice = 'tongtong'; } if (params.speed || params.s) { body.speed = parseFloat(params.speed || params.s); } const format = params.format || params.f || 'wav'; body.response_format = format; if (params.stream) { body.stream = true; } console.log('🚀 Generating speech...'); const response = await client.audio.tts.create(body); // Handle audio response const contentType = response.headers.get('content-type') || ''; // Check if it's a streaming response if (body.stream && (contentType.includes('text/event-stream') || contentType.includes('text/plain'))) { console.error('❌ Streaming response requires special handling, please use streaming processing code'); process.exit(1); } // Non-streaming response, get audio data const arrayBuffer = await response.arrayBuffer(); const buffer = Buffer.from(new Uint8Array(arrayBuffer)); await saveFile(buffer, output); console.log('🎉 Speech generation completed!'); } // Handle ASR command async function handleASR(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showASRHelp(); return; } const file = params.file || params.f; const base64 = params.base64 || params.b; if (!file && !base64) { console.error('❌ Error: Missing required parameter --file or --base64'); showASRHelp(); process.exit(1); } const body = {}; if (file) { // Read file and convert to base64 try { const fileBase64 = await readFileAsBase64(file); body.file_base64 = fileBase64; } catch (error) { console.error(`❌ Unable to read audio file: ${file}`); process.exit(1); } } else { body.file_base64 = base64; } if (params.stream) { body.stream = true; } console.log('🚀 Recognizing speech...'); const response = await client.audio.asr.create(body); const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(response, null, 2), output); } else { console.log(JSON.stringify(response, null, 2)); } console.log('🎉 Speech recognition completed!'); } // Handle image generation command async function handleImage(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showImageHelp(); return; } const prompt = params.prompt || params.p; if (!prompt) { console.error('❌ Error: Missing required parameter --prompt'); showImageHelp(); process.exit(1); } const output = params.output || params.o; if (!output) { console.error('❌ Error: Missing required parameter --output'); showImageHelp(); process.exit(1); } const body = { prompt, size: params.size || params.s || '1024x1024', }; const validSizes = ['1024x1024', '768x1344', '864x1152', '1344x768', '1152x864', '1440x720', '720x1440']; if (!validSizes.includes(body.size)) { throw new Error(`--size must be one of: ${validSizes.join(', ')}`); } console.log(`🎨 Generating image: "${prompt}"`); console.log(`📐 Size: ${body.size}`); const response = await client.images.generations.create(body); if (!response.data || response.data.length === 0) { throw new Error('API did not return image data'); } const imageBase64 = response.data[0].base64; await saveFile(base64ToBuffer(imageBase64), output); console.log('🎉 Image generation completed!'); } // Handle image edit command async function handleImageEdit(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showImageEditHelp(); return; } const prompt = params.prompt || params.p; if (!prompt) { console.error('❌ Error: Missing required parameter --prompt'); showImageEditHelp(); process.exit(1); } const image = params.image || params.i; if (!image) { console.error('❌ Error: Missing required parameter --image'); showImageEditHelp(); process.exit(1); } const output = params.output || params.o; if (!output) { console.error('❌ Error: Missing required parameter --output'); showImageEditHelp(); process.exit(1); } // Process image: convert local file to base64 data URL let imageUrl = image; if (!image.startsWith('http://') && !image.startsWith('https://') && !image.startsWith('data:')) { try { const base64 = await readFileAsBase64(image); const ext = path.extname(image).toLowerCase(); const mimeTypes = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', }; const mimeType = mimeTypes[ext] || 'image/png'; imageUrl = `data:${mimeType};base64,${base64}`; } catch (error) { console.error(`❌ Unable to read image file: ${image}`); process.exit(1); } } const body = { prompt, image: imageUrl, size: params.size || params.s || '1024x1024', }; const validSizes = ['1024x1024', '768x1344', '864x1152', '1344x768', '1152x864', '1440x720', '720x1440']; if (!validSizes.includes(body.size)) { throw new Error(`--size must be one of: ${validSizes.join(', ')}`); } console.log(`🎨 Editing image: "${prompt}"`); console.log(`📐 Size: ${body.size}`); const response = await client.images.generations.edit(body); if (!response.data || response.data.length === 0) { throw new Error('API did not return image data'); } const imageBase64 = response.data[0].base64; await saveFile(base64ToBuffer(imageBase64), output); console.log('🎉 Image editing completed!'); } // Handle image search command async function handleImageSearch(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showImageSearchHelp(); return; } const query = params.query || params.q; if (!query) { console.error('❌ Error: Missing required parameter --query'); showImageSearchHelp(); process.exit(1); } const body = { query }; const countRaw = params.count || params.c; if (countRaw !== undefined) { const n = parseInt(countRaw, 10); if (Number.isNaN(n) || n <= 0) { console.error('❌ Error: --count must be a positive integer'); process.exit(1); } body.count = n; } if (params.gl) { body.gl = params.gl; } // Captioning is on by default; --no-rank disables it. if (params['no-rank']) { body.rank = false; } else if (params.rank) { body.rank = true; } console.log(`🔎 Searching images: "${query}"`); const response = await client.images.search.create(body); if (response && response.success === false) { console.error(`❌ Image search failed: ${response.error || 'unknown error'}`); } else { const n = response?.results?.length ?? 0; console.log(`✅ Got ${n} image${n === 1 ? '' : 's'}`); } const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(response, null, 2), output); } else { console.log(JSON.stringify(response, null, 2)); } } // Handle video generation command async function handleVideo(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showVideoHelp(); return; } const body = {}; if (params.prompt || params.p) { body.prompt = params.prompt || params.p; } if (params['image-url'] || params.i) { const imageUrl = params['image-url'] || params.i; // Support array format (comma-separated) if (imageUrl.includes(',')) { body.image_url = imageUrl.split(',').map((url) => url.trim()); } else { body.image_url = imageUrl; } } if (params.quality || params.q) { body.quality = params.quality || params.q; } if (params['with-audio']) { body.with_audio = params['with-audio'] === 'true' || params['with-audio'] === '1'; } if (params.size || params.s) { body.size = params.size || params.s; } if (params.fps) { const fps = parseInt(params.fps); if (!isNaN(fps)) { body.fps = fps; } } if (params.duration || params.d) { const duration = parseInt(params.duration || params.d); if (!isNaN(duration)) { body.duration = duration; } } if (params.model || params.m) { body.model = params.model || params.m; } console.log('🎬 Creating video generation task...'); const task = await client.video.generations.create(body); console.log(`✅ Task created!`); console.log(`📋 Task ID: ${task.id}`); console.log(`📊 Task Status: ${task.task_status}`); if (task.model) { console.log(`🤖 Model: ${task.model}`); } const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(task, null, 2), output); } // If polling is needed, automatically query results if (params.poll) { const pollInterval = parseInt(params['poll-interval'] || '5') * 1000; const maxPolls = parseInt(params['max-polls'] || '60'); console.log(`\n🔄 Starting to poll results (interval ${pollInterval / 1000}s, max ${maxPolls} times)...`); let result = await client.async.result.query(task.id); let pollCount = 0; while (result.task_status === 'PROCESSING' && pollCount < maxPolls) { pollCount++; console.log(`⏳ Poll ${pollCount}/${maxPolls}: Status ${result.task_status}, waiting...`); await new Promise(resolve => setTimeout(resolve, pollInterval)); result = await client.async.result.query(task.id); } await displayAsyncResult(result, output); } else { console.log(`\n💡 Tip: Use --poll parameter to automatically poll until task completes`); console.log(` Or use: z-ai async-result --id "${task.id}" --poll`); } } // Handle async result query command async function handleAsyncResult(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showAsyncResultHelp(); return; } const taskId = params.id || params.i; if (!taskId) { console.error('❌ Error: Missing required parameter --id'); showAsyncResultHelp(); process.exit(1); } const pollInterval = parseInt(params['poll-interval'] || '5') * 1000; const maxPolls = parseInt(params['max-polls'] || '60'); console.log(`🔍 Querying task result: ${taskId}`); let result = await client.async.result.query(taskId); // If polling is needed and status is processing if (params.poll && result.task_status === 'PROCESSING') { console.log(`\n🔄 Starting to poll results (interval ${pollInterval / 1000}s, max ${maxPolls} times)...`); let pollCount = 0; while (result.task_status === 'PROCESSING' && pollCount < maxPolls) { pollCount++; console.log(`⏳ Poll ${pollCount}/${maxPolls}: Status ${result.task_status}, waiting...`); await new Promise(resolve => setTimeout(resolve, pollInterval)); result = await client.async.result.query(taskId); } } const output = params.output || params.o; await displayAsyncResult(result, output); } // Display async result async function displayAsyncResult(result, output) { console.log(`\n=== Query Result ===`); console.log(`📊 Task Status: ${result.task_status}`); if (result.model) { console.log(`🤖 Model: ${result.model}`); } if (result.request_id) { console.log(`🆔 Request ID: ${result.request_id}`); } if (result.task_status === 'SUCCESS') { // Try to get video URL from multiple possible fields const videoUrl = result.video_result?.[0]?.url || result.video_url || result.url || result.video; if (videoUrl) { console.log(`\n✅ Video generated successfully!`); console.log(`🎬 Video URL: ${videoUrl}`); console.log(`\n💡 Tip: You can open this URL in your browser to view or download the video`); } else { console.log(`\n⚠️ Task completed but video URL not found`); console.log(`Full response:`, JSON.stringify(result, null, 2)); } } else if (result.task_status === 'PROCESSING') { console.log(`\n⏳ Task is still processing, please query again later`); if (result.id) { console.log(`Task ID: ${result.id}`); } } else if (result.task_status === 'FAIL') { console.log(`\n❌ Task processing failed`); console.log(`Full response:`, JSON.stringify(result, null, 2)); } if (output) { await saveFile(JSON.stringify(result, null, 2), output); } else { console.log(`\nFull response:`, JSON.stringify(result, null, 2)); } } // Handle function invocation command async function handleFunction(args, client) { const params = parseKeyValueArgs(args); if (params.help || params.h) { showFunctionHelp(); return; } const name = params.name || params.n; if (!name) { console.error('❌ Error: Missing required parameter --name'); showFunctionHelp(); process.exit(1); } const argsJson = params.args || params.a; if (!argsJson) { console.error('❌ Error: Missing required parameter --args'); showFunctionHelp(); process.exit(1); } let functionArgs; try { functionArgs = JSON.parse(argsJson); } catch (error) { console.error('❌ Error: --args must be valid JSON format'); process.exit(1); } console.log(`🚀 Invoking function: ${name}...`); const response = await client.functions.invoke(name, functionArgs); const output = params.output || params.o; if (output) { await saveFile(JSON.stringify(response, null, 2), output); } else { console.log(JSON.stringify(response, null, 2)); } console.log('🎉 Function invocation completed!'); } // Check if it's legacy image generation format (backward compatible) function isLegacyImageFormat(args) { return args.some(arg => arg === '--prompt' || arg === '-p') && args.some(arg => arg === '--output' || arg === '-o') && !['chat', 'vision', 'tts', 'asr', 'image', 'image-edit', 'image-search', 'video', 'async-result', 'function'].includes(args[0]); } async function main() { try { const args = process.argv.slice(2); // If no arguments or first argument is --help, show main help if (args.length === 0 || args[0] === '--help' || args[0] === '-h') { showMainHelp(); return; } const command = args[0]; const commandArgs = args.slice(1); // Backward compatible: if it's legacy image generation format, handle directly if (isLegacyImageFormat(args)) { console.log('🚀 Initializing Z-AI SDK...'); const ZAI = await getZAI(); const client = await ZAI.create(); await handleImage(args, client); return; } // Check if it's a valid command const validCommands = ['chat', 'vision', 'tts', 'asr', 'image', 'image-edit', 'image-search', 'video', 'async-result', 'function']; if (!validCommands.includes(command)) { console.error(`❌ Error: Unknown command "${command}"`); showMainHelp(); process.exit(1); } console.log('🚀 Initializing Z-AI SDK...'); const ZAI = await getZAI(); const client = await ZAI.create(); // Execute corresponding handler function based on command switch (command) { case 'chat': await handleChat(commandArgs, client); break; case 'vision': await handleVision(commandArgs, client); break; case 'tts': await handleTTS(commandArgs, client); break; case 'asr': await handleASR(commandArgs, client); break; case 'image': await handleImage(commandArgs, client); break; case 'image-edit': await handleImageEdit(commandArgs, client); break; case 'image-search': await handleImageSearch(commandArgs, client); break; case 'video': await handleVideo(commandArgs, client); break; case 'async-result': await handleAsyncResult(commandArgs, client); break; case 'function': await handleFunction(commandArgs, client); break; default: console.error(`❌ Error: Unknown command "${command}"`); showMainHelp(); process.exit(1); } } catch (error) { console.error('❌ Error:', error instanceof Error ? error.message : error); process.exit(1); } } // Run main function directly main();