skillfree 0.1.9 → 0.1.11
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.
- package/SKILL.md +12 -2
- package/bin/skillfree.js +58 -0
- package/package.json +1 -1
- package/scripts/commands/pilot.js +59 -1
package/SKILL.md
CHANGED
|
@@ -108,10 +108,20 @@ skillfree pilot --type tts --text "你好世界" --output hello.mp3
|
|
|
108
108
|
|
|
109
109
|
## 🎬 视频生成(video)
|
|
110
110
|
|
|
111
|
-
>
|
|
111
|
+
> 视频为异步任务:提交后自动轮询,完成后返回下载链接或自动保存本地。
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
|
|
114
|
+
# 文生视频(自动轮询,完成后显示下载链接)
|
|
115
|
+
skillfree pilot --type video --model wan2.6-t2v --prompt "波浪翻涌的海面,黄昏时分"
|
|
116
|
+
|
|
117
|
+
# 自动下载到本地
|
|
118
|
+
skillfree pilot --type video --model wan2.6-t2v --prompt "波浪翻涌的海面" --output ./video.mp4
|
|
119
|
+
|
|
120
|
+
# 超时后手动查询进度(使用提交时返回的 task_id)
|
|
121
|
+
skillfree task <task_id>
|
|
122
|
+
|
|
123
|
+
# 查询并下载
|
|
124
|
+
skillfree task <task_id> --output ./video.mp4
|
|
115
125
|
```
|
|
116
126
|
|
|
117
127
|
| 模型ID | 名称 | 积分 | 最适合的场景 |
|
package/bin/skillfree.js
CHANGED
|
@@ -63,4 +63,62 @@ program
|
|
|
63
63
|
await authStatus()
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
// ── task(查询异步任务状态)────────────────────────────────────────────────────
|
|
67
|
+
program
|
|
68
|
+
.command('task <taskId>')
|
|
69
|
+
.description('查询异步任务状态(视频/音乐等)')
|
|
70
|
+
.option('--output <path>', '任务完成后自动下载到本地')
|
|
71
|
+
.action(async (taskId, flags) => {
|
|
72
|
+
const { request } = require('../scripts/lib/client')
|
|
73
|
+
const fs = require('fs')
|
|
74
|
+
|
|
75
|
+
// Suno 任务用 suno:xxx 格式区分
|
|
76
|
+
if (taskId.startsWith('suno:')) {
|
|
77
|
+
const sunoId = taskId.slice(5)
|
|
78
|
+
const poll = await request('/suno/fetch/' + sunoId, { method: 'GET' })
|
|
79
|
+
const result = await poll.json()
|
|
80
|
+
if (result.code !== 'success') throw new Error(result.message || JSON.stringify(result))
|
|
81
|
+
const status = result.data?.status
|
|
82
|
+
const songs = result.data?.data || []
|
|
83
|
+
console.log(`\n📋 Suno 任务 ${sunoId}`)
|
|
84
|
+
console.log(` 状态:${status}`)
|
|
85
|
+
if (status === 'SUCCESS') {
|
|
86
|
+
songs.forEach((s, i) => {
|
|
87
|
+
console.log(` 歌曲${i+1}:${s.title} ${s.audio_url}`)
|
|
88
|
+
})
|
|
89
|
+
if (flags.output && songs[0]?.audio_url) {
|
|
90
|
+
const resp = await fetch(songs[0].audio_url)
|
|
91
|
+
fs.writeFileSync(flags.output, Buffer.from(await resp.arrayBuffer()))
|
|
92
|
+
console.log(`✅ 已下载到 ${flags.output}`)
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`\n💡 下载:skillfree task ${taskId} --output ./music.mp3`)
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
console.log(`\n⏳ 仍在生成中,稍后再查...`)
|
|
98
|
+
}
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 视频任务
|
|
103
|
+
const res = await request(`/v1/tasks/${taskId}`, { method: 'GET' })
|
|
104
|
+
const task = await res.json()
|
|
105
|
+
console.log(`\n📋 任务 ${taskId}`)
|
|
106
|
+
console.log(` 状态:${task.status}`)
|
|
107
|
+
console.log(` 模型:${task.model}`)
|
|
108
|
+
if (task.status === 'completed' && task.output_url) {
|
|
109
|
+
console.log(` 链接:${task.output_url}`)
|
|
110
|
+
if (flags.output) {
|
|
111
|
+
const resp = await fetch(task.output_url)
|
|
112
|
+
fs.writeFileSync(flags.output, Buffer.from(await resp.arrayBuffer()))
|
|
113
|
+
console.log(`✅ 已下载到 ${flags.output}`)
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`\n💡 下载:skillfree task ${taskId} --output ./video.mp4`)
|
|
116
|
+
}
|
|
117
|
+
} else if (task.status === 'failed') {
|
|
118
|
+
console.log(` 错误:${task.error || '未知错误'}`)
|
|
119
|
+
} else {
|
|
120
|
+
console.log(`\n⏳ 仍在生成中,稍后再查...`)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
66
124
|
program.parse(process.argv)
|
package/package.json
CHANGED
|
@@ -231,7 +231,7 @@ async function pilot(flags) {
|
|
|
231
231
|
}
|
|
232
232
|
if (status === 'FAILED') throw new Error('Suno 任务失败: ' + JSON.stringify(result.data))
|
|
233
233
|
}
|
|
234
|
-
throw new Error(
|
|
234
|
+
throw new Error(`Suno 生成超时(150s),任务仍在后台运行\n📋 稍后手动查询:skillfree task suno:${taskId}`)
|
|
235
235
|
|
|
236
236
|
} else {
|
|
237
237
|
// music-2.5(MiniMax),走 /v1/responses,需要 lyrics
|
|
@@ -332,6 +332,64 @@ async function pilot(flags) {
|
|
|
332
332
|
return
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
// ── VIDEO ─────────────────────────────────────────────────────────────────────
|
|
336
|
+
if (type === 'video') {
|
|
337
|
+
if (!prompt) throw new Error('--prompt 是必需的(视频描述)')
|
|
338
|
+
const videoModel = model || 'wan2.6-t2v'
|
|
339
|
+
|
|
340
|
+
// 1. 提交任务
|
|
341
|
+
process.stdout.write(`🎬 提交视频任务(${videoModel})...`)
|
|
342
|
+
const res = await request('/v1/video/generations', {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
body: JSON.stringify({ model: videoModel, prompt }),
|
|
345
|
+
})
|
|
346
|
+
const submitData = await res.json()
|
|
347
|
+
if (!submitData.task_id) throw new Error('提交失败: ' + JSON.stringify(submitData).slice(0, 200))
|
|
348
|
+
|
|
349
|
+
const taskId = submitData.task_id
|
|
350
|
+
console.log(` ✅\n📋 task_id: ${taskId}`)
|
|
351
|
+
console.log('⏳ 视频生成中,预计需要 1-3 分钟...\n')
|
|
352
|
+
|
|
353
|
+
// 2. 轮询状态(最多等 5 分钟)
|
|
354
|
+
const maxWait = 300000
|
|
355
|
+
const interval = 5000
|
|
356
|
+
const start = Date.now()
|
|
357
|
+
|
|
358
|
+
while (Date.now() - start < maxWait) {
|
|
359
|
+
await new Promise(r => setTimeout(r, interval))
|
|
360
|
+
const pollRes = await request(`/v1/tasks/${taskId}`, { method: 'GET' })
|
|
361
|
+
const task = await pollRes.json()
|
|
362
|
+
const status = task.status
|
|
363
|
+
|
|
364
|
+
process.stdout.write(`\r状态: ${status} 已等待: ${Math.round((Date.now() - start) / 1000)}s `)
|
|
365
|
+
|
|
366
|
+
if (status === 'completed') {
|
|
367
|
+
console.log('\n')
|
|
368
|
+
const videoUrl = task.output_url
|
|
369
|
+
if (!videoUrl) throw new Error('任务完成但未返回视频 URL: ' + JSON.stringify(task))
|
|
370
|
+
|
|
371
|
+
if (output) {
|
|
372
|
+
await downloadAndSave(videoUrl, output)
|
|
373
|
+
} else {
|
|
374
|
+
console.log(`✅ 视频生成完成!`)
|
|
375
|
+
console.log(`🔗 下载链接:${videoUrl}`)
|
|
376
|
+
console.log(`\n💡 加 --output ./video.mp4 可自动下载到本地`)
|
|
377
|
+
}
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (status === 'failed') {
|
|
382
|
+
throw new Error('视频生成失败: ' + (task.error || JSON.stringify(task)))
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 超时,给出手动查询命令
|
|
387
|
+
console.log(`\n⏰ 等待超时(5分钟),任务仍在后台运行`)
|
|
388
|
+
console.log(`📋 稍后可手动查询进度:`)
|
|
389
|
+
console.log(` skillfree task ${taskId}`)
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
335
393
|
throw new Error(`不支持的类型: ${type},可选: chat | image | tts | stt | music | ocr | video | embedding`)
|
|
336
394
|
}
|
|
337
395
|
|