skillfree 0.1.25 โ†’ 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,27 +10,35 @@ async function listModels(type) {
10
10
  const t = m.type || 'chat'
11
11
  if (type && t !== type) continue
12
12
  if (!groups[t]) groups[t] = []
13
- groups[t].push(m.id)
13
+ groups[t].push(m)
14
14
  }
15
15
 
16
16
  const typeLabel = {
17
- chat: '๐Ÿ’ฌ ๅฏน่ฏ',
18
- image: '๐ŸŽจ ๅ›พๅƒ',
19
- tts: '๐Ÿ”Š ่ฏญ้Ÿณๅˆๆˆ',
20
- video: '๐ŸŽฌ ่ง†้ข‘',
21
- music: '๐ŸŽต ้Ÿณไน',
22
- ocr: '๐Ÿ“„ OCR',
23
- embedding: '๐Ÿ”ข Embedding',
17
+ chat: '๐Ÿ’ฌ ๅฏน่ฏ',
18
+ image: '๐ŸŽจ ๅ›พๅƒ',
19
+ video: '๐ŸŽฌ ่ง†้ข‘',
20
+ ocr: '๐Ÿ“„ OCR ่งฃๆž',
21
+ search: '๐ŸŒ ๆœ็ดข',
24
22
  }
25
23
 
26
- console.log('\n๐Ÿฆž SkillFree ๅฏ็”จๆจกๅž‹\n')
27
- for (const [t, ids] of Object.entries(groups)) {
24
+ console.log('\n๐Ÿฆž SkillFree ไธฅ้€‰ๆจกๅž‹\n')
25
+ for (const [t, models] of Object.entries(groups)) {
28
26
  console.log(`${typeLabel[t] || t}`)
29
- ids.forEach(id => console.log(` ${id}`))
27
+ for (const m of models) {
28
+ let price = ''
29
+ if (m.tokenBased) {
30
+ price = `่พ“ๅ…ฅ ยฅ${((m.inputPrice || 0) / 100).toFixed(0)}/1M ยท ่พ“ๅ‡บ ยฅ${((m.outputPrice || 0) / 100).toFixed(0)}/1M`
31
+ } else if (m.pricePerSec) {
32
+ price = `720p ยฅ${((m.pricePerSec['1280x720'] || 0) / 100).toFixed(1)}/s ยท 1080p ยฅ${((m.pricePerSec['1920x1080'] || 0) / 100).toFixed(1)}/s ยท 4K ยฅ${((m.pricePerSec['3840x2160'] || 0) / 100).toFixed(1)}/s`
33
+ } else if (m.estimatedCredits) {
34
+ price = `ยฅ${(m.estimatedCredits / 100).toFixed(2)}/ๆฌก`
35
+ }
36
+ const label = m.label ? ` ${m.label}` : ''
37
+ console.log(` ${m.id}${label ? ' (' + m.label + ')' : ''} ${price}`)
38
+ }
30
39
  console.log()
31
40
  }
32
- console.log(`ๅ…ฑ ${all.length} ไธชๆจกๅž‹`)
33
- console.log(`\n็”จๆณ•็คบไพ‹๏ผšskillfree pilot --type chat --model DeepSeek-V3.2-Fast --prompt "ไฝ ๅฅฝ"`)
41
+ console.log(`\n็”จๆณ•๏ผšskillfree pilot --type chat --model claude-sonnet-4-6 --prompt "ไฝ ๅฅฝ"`)
34
42
  }
35
43
 
36
44
  module.exports = { listModels }
@@ -1,4 +1,4 @@
1
- const { post, postStream, get, request, getApiKey, BASE_URL, checkCredits } = require('../lib/client')
1
+ const { post, postStream, request, getApiKey, BASE_URL, checkCredits } = require('../lib/client')
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
 
@@ -10,35 +10,23 @@ async function downloadAndSave(url, output) {
10
10
  console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
11
11
  }
12
12
 
13
- // โ”€โ”€โ”€ ๅทฅๅ…ท๏ผšๅฐ่ฃ… PCM ไธบ WAV โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
- function pcmToWav(pcmBytes, sampleRate = 24000, channels = 1, bitsPerSample = 16) {
15
- const dataSize = pcmBytes.length
16
- const wav = Buffer.alloc(44 + dataSize)
17
- wav.write('RIFF', 0); wav.writeUInt32LE(36 + dataSize, 4); wav.write('WAVE', 8)
18
- wav.write('fmt ', 12); wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20)
19
- wav.writeUInt16LE(channels, 22); wav.writeUInt32LE(sampleRate, 24)
20
- wav.writeUInt32LE(sampleRate * channels * bitsPerSample / 8, 28)
21
- wav.writeUInt16LE(channels * bitsPerSample / 8, 32); wav.writeUInt16LE(bitsPerSample, 34)
22
- wav.write('data', 36); wav.writeUInt32LE(dataSize, 40)
23
- pcmBytes.copy(wav, 44)
24
- return wav
25
- }
26
-
27
13
  async function pilot(flags) {
28
- const type = flags.type || 'chat'
14
+ const type = flags.type || 'chat'
29
15
  const prompt = flags.prompt || flags.text || ''
30
16
  const output = flags.output || null
31
- const model = flags.model || null
17
+ const model = flags.model || null
32
18
 
33
- // โ”€โ”€ ็งฏๅˆ†้ข„ๆฃ€๏ผˆ< 100 ็งฏๅˆ†ๆ—ถๆ‹ฆๆˆชๅนถๆ็คบๅ……ๅ€ผ๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
+ // โ”€โ”€ ไฝ™้ข้ข„ๆฃ€๏ผˆ< 100 ็งฏๅˆ†ๆ—ถๆ‹ฆๆˆชๅนถๆ็คบๅ……ๅ€ผ๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
20
  await checkCredits()
35
21
 
36
22
  // โ”€โ”€ CHAT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
37
23
  if (type === 'chat') {
24
+ const chatModel = model || 'claude-sonnet-4-6'
25
+
38
26
  if (!output) {
39
27
  // ๆตๅผ่พ“ๅ‡บ
40
28
  const res = await postStream('/chat/completions', {
41
- model: model || 'DeepSeek-V3.2-Fast',
29
+ model: chatModel,
42
30
  messages: [{ role: 'user', content: prompt }],
43
31
  stream: true,
44
32
  })
@@ -66,13 +54,14 @@ async function pilot(flags) {
66
54
  }
67
55
  return
68
56
  }
57
+
69
58
  const result = await post('/chat/completions', {
70
- model: model || 'DeepSeek-V3.2-Fast',
59
+ model: chatModel,
71
60
  messages: [{ role: 'user', content: prompt }],
72
61
  })
73
62
  const text = result.choices?.[0]?.message?.content || JSON.stringify(result, null, 2)
74
- if (output) { fs.writeFileSync(output, text); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
75
- else console.log(text)
63
+ fs.writeFileSync(output, text)
64
+ console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
76
65
  return
77
66
  }
78
67
 
@@ -80,26 +69,6 @@ async function pilot(flags) {
80
69
  if (type === 'image') {
81
70
  const imageModel = model || 'gemini-3.1-flash-image-preview'
82
71
 
83
- // qwen-image-edit-plus๏ผšๅ›พ็‰‡็ผ–่พ‘๏ผŒ่ตฐ /v1/images/edits๏ผŒmultipart/form-data
84
- if (imageModel.startsWith('qwen-image')) {
85
- if (!flags.file) throw new Error('qwen-image-edit ้œ€่ฆ --file ๆŒ‡ๅฎšๅŽŸๅง‹ๅ›พ็‰‡่ทฏๅพ„')
86
- const imgBuf = fs.readFileSync(path.resolve(flags.file))
87
- const form = new FormData()
88
- form.append('model', imageModel)
89
- form.append('prompt', prompt)
90
- form.append('size', flags.size || '1024x1024')
91
- form.append('image', new Blob([imgBuf], { type: 'image/png' }), path.basename(flags.file))
92
- const res = await request('/images/edits', { method: 'POST', body: form })
93
- const data = await res.json()
94
- if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
95
- const url = data.data?.[0]?.url
96
- if (!url) throw new Error('ๆœช่ฟ”ๅ›žๅ›พๅƒURL: ' + JSON.stringify(data).slice(0, 200))
97
- if (output) await downloadAndSave(url, output)
98
- else console.log('ๅ›พๅƒ URL:', url)
99
- return
100
- }
101
-
102
- // doubao-seedream-5.0-lite / ๅ…ถไป–ๆ ‡ๅ‡†ๅ›พๅƒๆจกๅž‹
103
72
  const res = await post('/images/generations', {
104
73
  model: imageModel,
105
74
  prompt,
@@ -107,8 +76,10 @@ async function pilot(flags) {
107
76
  size: flags.size || '1024x1024',
108
77
  })
109
78
  if (res.error) throw new Error(res.error.message || JSON.stringify(res.error))
79
+
110
80
  const url = res.data?.[0]?.url || res.data?.[0]?.b64_json
111
81
  if (!url) throw new Error('ๆœช่ฟ”ๅ›žๅ›พๅƒๆ•ฐๆฎ: ' + JSON.stringify(res).slice(0, 200))
82
+
112
83
  if (output) {
113
84
  if (url.startsWith('http')) await downloadAndSave(url, output)
114
85
  else { fs.writeFileSync(output, Buffer.from(url, 'base64')); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
@@ -118,156 +89,48 @@ async function pilot(flags) {
118
89
  return
119
90
  }
120
91
 
121
- // โ”€โ”€ TTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
122
- if (type === 'tts') {
123
- const ttsModel = model || 'speech-2.6-hd'
124
- const text = prompt
92
+ // โ”€โ”€ VIDEO๏ผˆVeo ๅผ‚ๆญฅ่ฝฎ่ฏข๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
93
+ if (type === 'video') {
94
+ if (!prompt) throw new Error('--prompt ๆ˜ฏๅฟ…้œ€็š„๏ผˆ่ง†้ข‘ๆ่ฟฐ๏ผ‰')
95
+ const videoModel = model || 'veo-3.1-fast-generate-preview'
96
+ const seconds = flags.seconds || '8'
97
+ const size = flags.size || '1920x1080'
125
98
 
126
- if (ttsModel === 'speech-2.8-hd' || ttsModel === 'minimax-clone-lastversion') {
127
- // ่ตฐ /v1/responses๏ผŒ่ฟ”ๅ›ž hex ้Ÿณ้ข‘
128
- const res = await request('/responses', {
129
- method: 'POST',
130
- body: JSON.stringify({
131
- model: ttsModel,
132
- input: text,
133
- stream: false,
134
- voice_setting: {
135
- voice_id: flags.voice || 'female-shaonv',
136
- speed: 1, vol: 1, pitch: 0, emotion: 'fluent',
137
- },
138
- audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1 },
139
- output_format: 'hex',
140
- }),
141
- })
142
- const data = await res.json()
143
- if (!data.data?.audio) throw new Error(JSON.stringify(data).slice(0, 200))
144
- const audioBuf = Buffer.from(data.data.audio, 'hex')
145
- if (output) { fs.writeFileSync(output, audioBuf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${audioBuf.length} bytes๏ผ‰`) }
146
- else console.log(`โœ… TTS ๆˆๅŠŸ๏ผŒๆ—ถ้•ฟ็บฆ ${(data.extra_info?.audio_length / 1000).toFixed(1)} ็ง’`)
99
+ console.log(`๐ŸŽฌ ๆไบค Veo ่ง†้ข‘ไปปๅŠก๏ผˆ${videoModel}๏ผŒ${size}๏ผŒ${seconds}s๏ผ‰...`)
100
+ console.log('โณ ่ง†้ข‘็”Ÿๆˆไธญ๏ผŒ้ข„่ฎก้œ€่ฆ 1-3 ๅˆ†้’Ÿ...\n')
147
101
 
148
- } else if (ttsModel === 'gemini-2.5-pro-preview-tts' || ttsModel === 'gemini-2.5-flash-preview-tts') {
149
- // Gemini TTS๏ผš่ตฐ skillfree.tech ๅŽ็ซฏ /v1beta ่ทฏ็”ฑ
150
- // ๆณจๆ„๏ผš็”จ fetch+BASE_URL ่€Œ้ž request()๏ผŒๅ› ไธบ request() ไผš่‡ชๅŠจๅŠ  /v1 ๅ‰็ผ€
151
- const apiKey = getApiKey()
152
- const res = await fetch(BASE_URL + '/v1beta/models/' + ttsModel + ':generateContent', {
153
- method: 'POST',
154
- headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
155
- body: JSON.stringify({
156
- contents: [{ parts: [{ text }] }],
157
- generationConfig: {
158
- responseModalities: ['AUDIO'],
159
- speechConfig: {
160
- voiceConfig: { prebuiltVoiceConfig: { voiceName: flags.voice || 'Kore' } }
161
- }
162
- }
163
- }),
164
- })
165
- const data = await res.json()
166
- if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
167
- const pcm = Buffer.from(data.candidates[0].content.parts[0].inlineData.data, 'base64')
168
- const ext = (output || '').endsWith('.mp3') ? 'mp3' : 'wav'
169
- const finalBuf = ext === 'wav' ? pcmToWav(pcm) : pcm
170
- if (output) { fs.writeFileSync(output, finalBuf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${finalBuf.length} bytes๏ผ‰`) }
171
- else console.log(`โœ… Gemini TTS ๆˆๅŠŸ๏ผŒๆ—ถ้•ฟ็บฆ ${(pcm.length / (24000 * 2)).toFixed(1)} ็ง’`)
102
+ const apiKey = getApiKey()
103
+ const res = await fetch(`${BASE_URL}/v1/video/veo`, {
104
+ method: 'POST',
105
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
106
+ body: JSON.stringify({ model: videoModel, input: prompt, seconds, size }),
107
+ })
172
108
 
173
- } else {
174
- // ๆ ‡ๅ‡† OpenAI ๅ…ผๅฎน๏ผˆspeech-2.6-hd ็ญ‰๏ผ‰
175
- const res = await request('/audio/speech', {
176
- method: 'POST',
177
- body: JSON.stringify({
178
- model: ttsModel,
179
- input: text,
180
- voice: flags.voice || 'female-shaonv',
181
- }),
182
- })
183
- if (res.status !== 200) {
184
- const err = await res.json()
185
- throw new Error(err.error?.message || JSON.stringify(err))
186
- }
187
- const buf = Buffer.from(await res.arrayBuffer())
188
- if (output) { fs.writeFileSync(output, buf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${buf.length} bytes๏ผ‰`) }
189
- else console.log(`โœ… TTS ๆˆๅŠŸ๏ผŒ${buf.length} bytes`)
109
+ if (!res.ok) {
110
+ const err = await res.json().catch(() => ({}))
111
+ throw new Error(err.error || `HTTP ${res.status}`)
190
112
  }
191
- return
192
- }
193
-
194
- // โ”€โ”€ MUSIC โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
195
- if (type === 'music') {
196
- const musicModel = model || 'chirp-v5'
197
-
198
- if (musicModel === 'chirp-v5') {
199
- // Suno ๅผ‚ๆญฅๆŽฅๅฃ๏ผˆ่ทฏ็”ฑๅœจ /suno/๏ผŒไธๅœจ /v1/ ไธ‹๏ผŒ้œ€็”จ BASE_URL ็›ดๆŽฅ่ฏทๆฑ‚๏ผ‰
200
- const apiKey = getApiKey()
201
- const res = await fetch(`${BASE_URL}/suno/submit/music`, {
202
- method: 'POST',
203
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${apiKey}` },
204
- body: JSON.stringify({
205
- gpt_description_prompt: prompt,
206
- make_instrumental: true,
207
- mv: 'chirp-v5',
208
- notify_hook: '',
209
- }),
210
- })
211
- const submitData = await res.json()
212
- if (submitData.code !== 'success') throw new Error(submitData.message || JSON.stringify(submitData))
213
- const taskId = submitData.data
214
- console.log(`๐ŸŽต Suno ไปปๅŠกๅทฒๆไบค๏ผŒtask_id: ${taskId}๏ผŒ็ญ‰ๅพ…็”Ÿๆˆ๏ผˆ็บฆ 60-90 ็ง’๏ผ‰...`)
215
113
 
216
- // ่ฝฎ่ฏข็ป“ๆžœ
217
- for (let i = 0; i < 15; i++) {
218
- await new Promise(r => setTimeout(r, 10000))
219
- const poll = await fetch(`${BASE_URL}/suno/fetch/${taskId}`, {
220
- headers: { 'Authorization': `Bearer ${apiKey}` }
221
- })
222
- const result = await poll.json()
223
- if (result.code !== 'success') throw new Error(result.message || JSON.stringify(result))
224
- const status = result.data?.status
225
- const progress = result.data?.progress || '0%'
226
- process.stdout.write(`\r็Šถๆ€: ${status} ่ฟ›ๅบฆ: ${progress} `)
227
- if (status === 'SUCCESS') {
228
- const songs = result.data?.data || []
229
- process.stdout.write('\n')
230
- console.log(`โœ… ็”ŸๆˆๆˆๅŠŸ๏ผๅ…ฑ ${songs.length} ้ฆ–`)
231
- songs.forEach((s, i) => {
232
- console.log(` ๆญŒๆ›ฒ${i+1}: ${s.title} (${Math.floor(s.duration/60)}m${Math.floor(s.duration%60)}s)`)
233
- console.log(` ้Ÿณ้ข‘: ${s.audio_url}`)
234
- })
235
- // ๅฆ‚ๆžœๆŒ‡ๅฎš output๏ผŒไธ‹่ฝฝ็ฌฌไธ€้ฆ–
236
- if (output && songs[0]?.audio_url) await downloadAndSave(songs[0].audio_url, output)
237
- return
238
- }
239
- if (status === 'FAILED') throw new Error('Suno ไปปๅŠกๅคฑ่ดฅ: ' + JSON.stringify(result.data))
240
- }
241
- throw new Error(`Suno ็”Ÿๆˆ่ถ…ๆ—ถ๏ผˆ150s๏ผ‰๏ผŒไปปๅŠกไปๅœจๅŽๅฐ่ฟ่กŒ\n๐Ÿ“‹ ็จๅŽๆ‰‹ๅŠจๆŸฅ่ฏข๏ผšskillfree task suno:${taskId}`)
114
+ const data = await res.json()
242
115
 
116
+ // ๆœๅŠก็ซฏๅทฒ่ฝฎ่ฏขๅฎŒๆˆ๏ผŒ่ฟ”ๅ›ž video_base64 ๆˆ– url
117
+ if (data.video_base64) {
118
+ const videoBytes = Buffer.from(data.video_base64, 'base64')
119
+ const savePath = output || `./veo_${Date.now()}.mp4`
120
+ fs.writeFileSync(savePath, videoBytes)
121
+ console.log(`โœ… ่ง†้ข‘ๅทฒไฟๅญ˜ๅˆฐ ${savePath}๏ผˆ${(videoBytes.length / 1024 / 1024).toFixed(1)} MB๏ผ‰`)
122
+ } else if (data.url) {
123
+ if (output) await downloadAndSave(data.url, output)
124
+ else console.log('๐Ÿ”— ่ง†้ข‘้“พๆŽฅ:', data.url)
243
125
  } else {
244
- // music-2.5๏ผˆMiniMax๏ผ‰๏ผŒ่ตฐ /v1/responses๏ผŒ้œ€่ฆ lyrics
245
- const res = await request('/responses', {
246
- method: 'POST',
247
- body: JSON.stringify({
248
- model: 'music-2.5',
249
- input: prompt,
250
- lyrics: flags.lyrics || `[verse]\n${prompt}`,
251
- audio_setting: { sample_rate: 44100, bitrate: 256000, format: 'mp3' },
252
- output_format: 'url',
253
- stream: false,
254
- }),
255
- })
256
- const data = await res.json()
257
- if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
258
- const audioUrl = data.output?.[0]?.content?.[0]?.audio
259
- if (!audioUrl) throw new Error('ๆœช่ฟ”ๅ›ž้Ÿณ้ข‘: ' + JSON.stringify(data).slice(0, 200))
260
- const duration = ((data.extra_info?.music_duration || 0) / 1000).toFixed(1)
261
- console.log(`โœ… music-2.5 ็”ŸๆˆๆˆๅŠŸ๏ผๆ—ถ้•ฟ็บฆ ${duration} ็ง’`)
262
- console.log('้Ÿณ้ข‘ URL:', audioUrl)
263
- if (output) await downloadAndSave(audioUrl, output)
126
+ throw new Error('ๆœช่ฟ”ๅ›ž่ง†้ข‘ๆ•ฐๆฎ: ' + JSON.stringify(data).slice(0, 200))
264
127
  }
128
+ return
265
129
  }
266
130
 
267
131
  // โ”€โ”€ OCR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
268
132
  if (type === 'ocr') {
269
133
  let input = prompt
270
- // ๅฆ‚ๆžœๆ˜ฏๆœฌๅœฐๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏปๅ–ไธบ base64
271
134
  if (flags.file && fs.existsSync(path.resolve(flags.file))) {
272
135
  input = fs.readFileSync(path.resolve(flags.file)).toString('base64')
273
136
  }
@@ -293,147 +156,7 @@ async function pilot(flags) {
293
156
  return
294
157
  }
295
158
 
296
- // โ”€โ”€ STT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
297
- if (type === 'stt') {
298
- if (!flags.file) throw new Error('--file ๆ˜ฏๅฟ…้œ€็š„๏ผˆ้Ÿณ้ข‘ๆ–‡ไปถ่ทฏๅพ„๏ผ‰')
299
- const audioBase64 = fs.readFileSync(path.resolve(flags.file)).toString('base64')
300
- const result = await post('/v1/audio/transcriptions', {
301
- model: 'whisper-1',
302
- file: audioBase64,
303
- filename: path.basename(flags.file),
304
- })
305
- const text = result.text || JSON.stringify(result, null, 2)
306
- if (output) { fs.writeFileSync(output, text); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
307
- else console.log(text)
308
- return
309
- }
310
-
311
- // โ”€โ”€ EMBEDDING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
312
- if (type === 'embedding') {
313
- // doubao-embedding-vision๏ผš่ตฐ /v1/responses๏ผˆๅคšๆจกๆ€๏ผ‰๏ผŒinput ไธบๆ•ฐ็ป„
314
- // ็ฎ€ๅ•ๆ–‡ๆœฌ embedding ไนŸๅฏ็”จ
315
- const inputData = flags.file
316
- ? [{ type: 'image_url', image_url: { url: 'data:image/png;base64,' + fs.readFileSync(path.resolve(flags.file)).toString('base64') } }]
317
- : [{ type: 'text', text: prompt }]
318
- const res = await request('/embeddings', {
319
- method: 'POST',
320
- body: JSON.stringify({
321
- model: model || 'doubao-embedding-vision-251215',
322
- input: inputData,
323
- encoding_format: 'float',
324
- dimensions: 1024,
325
- sparse_embedding: { type: 'disabled' },
326
- }),
327
- })
328
- const data = await res.json()
329
- if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
330
- const embedding = data.data?.[0]?.embedding
331
- if (!embedding) throw new Error('ๆœช่ฟ”ๅ›žๅ‘้‡: ' + JSON.stringify(data).slice(0, 200))
332
- console.log(`โœ… Embedding ๆˆๅŠŸ๏ผ็ปดๅบฆ: ${embedding.length}`)
333
- if (output) {
334
- fs.writeFileSync(output, JSON.stringify(embedding))
335
- console.log(`ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
336
- } else {
337
- console.log('ๅ‰5็ปด:', embedding.slice(0, 5))
338
- }
339
- return
340
- }
341
-
342
- // โ”€โ”€ VIDEO โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
343
- if (type === 'video') {
344
- if (!prompt) throw new Error('--prompt ๆ˜ฏๅฟ…้œ€็š„๏ผˆ่ง†้ข‘ๆ่ฟฐ๏ผ‰')
345
- const videoModel = model || 'kling-v2-6-text2video'
346
-
347
- // 1. ๆไบคไปปๅŠก
348
- process.stdout.write(`๐ŸŽฌ ๆไบค่ง†้ข‘ไปปๅŠก๏ผˆ${videoModel}๏ผ‰...`)
349
- const res = await request('/video/generations', {
350
- method: 'POST',
351
- body: JSON.stringify({ model: videoModel, prompt }),
352
- })
353
- const submitData = await res.json()
354
- if (!submitData.task_id) throw new Error('ๆไบคๅคฑ่ดฅ: ' + JSON.stringify(submitData).slice(0, 200))
355
-
356
- const taskId = submitData.task_id
357
- console.log(` โœ…\n๐Ÿ“‹ task_id: ${taskId}`)
358
- console.log('โณ ่ง†้ข‘็”Ÿๆˆไธญ๏ผŒ้ข„่ฎก้œ€่ฆ 1-3 ๅˆ†้’Ÿ...\n')
359
-
360
- // 2. ่ฝฎ่ฏข็Šถๆ€๏ผˆๆœ€ๅคš็ญ‰ 5 ๅˆ†้’Ÿ๏ผ‰
361
- const maxWait = 300000
362
- const interval = 5000
363
- const start = Date.now()
364
-
365
- while (Date.now() - start < maxWait) {
366
- await new Promise(r => setTimeout(r, interval))
367
- const pollRes = await request(`/tasks/${taskId}`, { method: 'GET' })
368
- const task = await pollRes.json()
369
- const status = task.status
370
-
371
- process.stdout.write(`\r็Šถๆ€: ${status} ๅทฒ็ญ‰ๅพ…: ${Math.round((Date.now() - start) / 1000)}s `)
372
-
373
- if (status === 'completed') {
374
- console.log('\n')
375
- const videoUrl = task.result_url || task.output_url
376
- if (!videoUrl) throw new Error('ไปปๅŠกๅฎŒๆˆไฝ†ๆœช่ฟ”ๅ›ž่ง†้ข‘ URL: ' + JSON.stringify(task))
377
-
378
- if (output) {
379
- await downloadAndSave(videoUrl, output)
380
- } else {
381
- console.log(`โœ… ่ง†้ข‘็”ŸๆˆๅฎŒๆˆ๏ผ`)
382
- console.log(`๐Ÿ”— ไธ‹่ฝฝ้“พๆŽฅ๏ผš${videoUrl}`)
383
- console.log(`\n๐Ÿ’ก ๅŠ  --output ./video.mp4 ๅฏ่‡ชๅŠจไธ‹่ฝฝๅˆฐๆœฌๅœฐ`)
384
- }
385
- return
386
- }
387
-
388
- if (status === 'failed') {
389
- throw new Error('่ง†้ข‘็”Ÿๆˆๅคฑ่ดฅ: ' + (task.error || JSON.stringify(task)))
390
- }
391
- }
392
-
393
- // ่ถ…ๆ—ถ๏ผŒ็ป™ๅ‡บๆ‰‹ๅŠจๆŸฅ่ฏขๅ‘ฝไปค
394
- console.log(`\nโฐ ็ญ‰ๅพ…่ถ…ๆ—ถ๏ผˆ5ๅˆ†้’Ÿ๏ผ‰๏ผŒไปปๅŠกไปๅœจๅŽๅฐ่ฟ่กŒ`)
395
- console.log(`๐Ÿ“‹ ็จๅŽๅฏๆ‰‹ๅŠจๆŸฅ่ฏข่ฟ›ๅบฆ๏ผš`)
396
- console.log(` skillfree task ${taskId}`)
397
- return
398
- }
399
-
400
- // โ”€โ”€ SEARCH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
401
- if (type === 'search') {
402
- if (!prompt) throw new Error('--prompt ๆ˜ฏๅฟ…้œ€็š„๏ผˆๆœ็ดข่ฏ๏ผ‰')
403
- const searchModel = model || 'tavily-search'
404
- const res = await request('/search', {
405
- method: 'POST',
406
- body: JSON.stringify({
407
- model: searchModel,
408
- query: prompt,
409
- include_answer: true,
410
- max_results: flags.maxResults || 5,
411
- }),
412
- })
413
- const data = await res.json()
414
- if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
415
-
416
- // ่พ“ๅ‡บๆ ผๅผๅŒ–็ป“ๆžœ
417
- if (data.answer) {
418
- console.log('\n๐Ÿ“ ๆ‘˜่ฆ็ญ”ๆกˆ๏ผš')
419
- console.log(data.answer)
420
- }
421
- if (data.results?.length) {
422
- console.log(`\n๐Ÿ”— ๆœ็ดข็ป“ๆžœ๏ผˆๅ…ฑ ${data.results.length} ๆก๏ผ‰๏ผš`)
423
- data.results.forEach((r, i) => {
424
- console.log(`\n${i+1}. ${r.title}`)
425
- console.log(` ${r.url}`)
426
- if (r.content) console.log(` ${r.content.slice(0, 150)}...`)
427
- })
428
- }
429
- if (output) {
430
- fs.writeFileSync(output, JSON.stringify(data, null, 2))
431
- console.log(`\nโœ… ๅฎŒๆ•ด็ป“ๆžœๅทฒไฟๅญ˜ๅˆฐ ${output}`)
432
- }
433
- return
434
- }
435
-
436
- throw new Error(`ไธๆ”ฏๆŒ็š„็ฑปๅž‹: ${type}๏ผŒๅฏ้€‰: chat | image | tts | stt | music | ocr | video | embedding | search`)
159
+ throw new Error(`ไธๆ”ฏๆŒ็š„็ฑปๅž‹: ${type}\nๅฏ้€‰: chat | image | video | ocr`)
437
160
  }
438
161
 
439
162
  module.exports = { pilot }
@@ -1,97 +1,21 @@
1
- const { run } = require('./run')
2
-
3
1
  /**
4
- * Video generation command
5
- * @param {object} params - Video parameters
6
- * @param {string} params.model - Model in "vendor/model" format
7
- * @param {string} params.prompt - Video generation prompt
8
- * @param {string} [params.output] - Output file path
9
- * @returns {Promise<object>} Video generation result
2
+ * Video generation via Veo๏ผˆๅผ‚ๆญฅ๏ผŒๆœๅŠก็ซฏ่ฝฎ่ฏขๅฎŒๆˆๅŽ่ฟ”ๅ›ž๏ผ‰
3
+ * @param {object} params
4
+ * @param {string} params.model - veo-3.1-fast-generate-preview | veo-3.1-generate-preview
5
+ * @param {string} params.prompt - ่ง†้ข‘ๆ่ฟฐๆ–‡ๆœฌ
6
+ * @param {string} [params.seconds] - ๆ—ถ้•ฟ๏ผš4 / 6 / 8๏ผˆ้ป˜่ฎค 8๏ผ‰
7
+ * @param {string} [params.size] - ๅˆ†่พจ็އ๏ผš1280x720 / 1920x1080 / 3840x2160๏ผˆ้ป˜่ฎค 1920x1080๏ผ‰
8
+ * @param {string} [params.output] - ๆœฌๅœฐไฟๅญ˜่ทฏๅพ„๏ผˆ.mp4๏ผ‰
10
9
  */
11
10
  async function video(params) {
12
- if (!params.prompt) {
13
- throw new Error('--prompt is required for video generation')
14
- }
11
+ if (!params.prompt) throw new Error('--prompt is required for video generation')
15
12
 
16
- const [vendor] = params.model.split('/')
17
- const inputs = {}
18
-
19
- if (vendor === 'vertex') {
20
- // Vertex/Veo uses instances array format
21
- inputs.instances = [{ prompt: params.prompt }]
22
- inputs.parameters = {}
23
- } else if (vendor === 'mm') {
24
- // MM video models: t2v (text-to-video), i2v (image-to-video)
25
- inputs.prompt = params.prompt
26
- if (params.size) {
27
- // Convert "1280x720" to "1280*720" if needed
28
- inputs.size = params.size.replace('x', '*')
29
- }
30
- if (params.duration) {
31
- inputs.duration = parseInt(params.duration)
32
- }
33
- if (params.image) {
34
- // i2v mode: image-to-video
35
- inputs.image_url = params.image
36
- }
37
- } else {
38
- // MiniMax and others use 'prompt'
39
- inputs.prompt = params.prompt
13
+ return {
14
+ model: params.model || 'veo-3.1-fast-generate-preview',
15
+ input: params.prompt,
16
+ seconds: params.seconds || '8',
17
+ size: params.size || '1920x1080',
40
18
  }
41
-
42
- return run({ model: params.model, inputs, output: params.output })
43
- }
44
-
45
- /**
46
- * Multimodal understanding command (video/image/audio analysis)
47
- * @param {object} params - Multimodal parameters
48
- * @param {string} params.model - Model in "vendor/model" format (e.g., mm/qwen3-vl-plus)
49
- * @param {string} params.prompt - Text prompt/question about the media
50
- * @param {string} [params.video] - Video URL to analyze
51
- * @param {string} [params.image] - Image URL to analyze
52
- * @param {string} [params.audio] - Audio URL to analyze/transcribe
53
- * @returns {Promise<object>} Multimodal analysis result
54
- */
55
- async function multimodal(params) {
56
- if (!params.prompt) {
57
- throw new Error('--prompt is required for multimodal')
58
- }
59
- if (!params.video && !params.image && !params.audio) {
60
- throw new Error('At least one of --video, --image, or --audio is required')
61
- }
62
-
63
- const [vendor] = params.model.split('/')
64
- const inputs = {}
65
-
66
- if (vendor === 'mm') {
67
- // MM multimodal models use messages format
68
- const content = []
69
- if (params.video) {
70
- content.push({ video: params.video })
71
- if (params.fps) {
72
- content[content.length - 1].fps = parseInt(params.fps)
73
- }
74
- }
75
- if (params.image) {
76
- content.push({ image: params.image })
77
- }
78
- if (params.audio) {
79
- content.push({ audio: params.audio })
80
- }
81
- content.push({ text: params.prompt })
82
-
83
- inputs.input = {
84
- messages: [{ role: 'user', content }]
85
- }
86
- } else {
87
- // Generic format
88
- inputs.prompt = params.prompt
89
- if (params.video) inputs.video_url = params.video
90
- if (params.image) inputs.image_url = params.image
91
- if (params.audio) inputs.audio_url = params.audio
92
- }
93
-
94
- return run({ model: params.model, inputs })
95
19
  }
96
20
 
97
- module.exports = { video, multimodal }
21
+ module.exports = { video }
Binary file
@@ -1,28 +0,0 @@
1
- const { run } = require('./run')
2
-
3
- /**
4
- * Music generation command
5
- * @param {object} params - Music parameters
6
- * @param {string} params.model - Model in "vendor/model" format
7
- * @param {string} params.prompt - Music generation prompt
8
- * @param {number} [params.duration] - Duration in seconds
9
- * @param {string} [params.output] - Output file path
10
- * @returns {Promise<object>} Music generation result
11
- */
12
- async function music(params) {
13
- if (!params.prompt) {
14
- throw new Error('--prompt is required for music generation')
15
- }
16
-
17
- const inputs = {
18
- prompt: params.prompt,
19
- }
20
-
21
- if (params.duration) {
22
- inputs.duration = parseInt(params.duration)
23
- }
24
-
25
- return run({ model: params.model, inputs, output: params.output })
26
- }
27
-
28
- module.exports = { music }