@uzooai/anime-ai-cli 0.0.0-stage → 0.6.0

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.
Files changed (89) hide show
  1. package/.agents/skills/uzoo-anime-cli/SKILL.md +89 -0
  2. package/.agents/skills/uzoo-anime-cli/references/agreement-workflow.md +41 -0
  3. package/.agents/skills/uzoo-anime-cli/references/canvas-workflow.md +53 -0
  4. package/.agents/skills/uzoo-anime-cli/references/commands.md +242 -0
  5. package/.agents/skills/uzoo-anime-cli/references/generation-workflow.md +142 -0
  6. package/.agents/skills/uzoo-anime-cli/references/image-tools-workflow.md +153 -0
  7. package/.agents/skills/uzoo-anime-cli/references/interaction-workflow.md +67 -0
  8. package/.agents/skills/uzoo-anime-cli/references/material-graph-workflow.md +113 -0
  9. package/.agents/skills/uzoo-anime-cli/references/node-capabilities.md +64 -0
  10. package/.agents/skills/uzoo-anime-cli/references/node-workflow.md +90 -0
  11. package/.agents/skills/uzoo-anime-cli/references/remake-workflow.md +54 -0
  12. package/.agents/skills/uzoo-anime-cli/references/repaint-workflow.md +13 -0
  13. package/.agents/skills/uzoo-anime-cli/references/seedance-review-workflow.md +37 -0
  14. package/.agents/skills/uzoo-anime-cli/references/video-tools-workflow.md +67 -0
  15. package/CHANGELOG.md +13 -0
  16. package/LICENSE +21 -0
  17. package/README.md +46 -3
  18. package/assets/favicon-uzoo.png +0 -0
  19. package/assets/logo-uzoo.svg +7 -0
  20. package/docs/Agent/346/216/245/345/205/245/344/270/216/347/211/210/346/234/254/346/233/264/346/226/260.md +43 -0
  21. package/examples/matting.json +5 -0
  22. package/extensions/uzoo-cli-login/manifest.json +9 -0
  23. package/extensions/uzoo-cli-login/popup.css +1 -0
  24. package/extensions/uzoo-cli-login/popup.html +14 -0
  25. package/extensions/uzoo-cli-login/popup.js +65 -0
  26. package/package.json +53 -6
  27. package/src/agreement-page.js +62 -0
  28. package/src/agreement.js +419 -0
  29. package/src/annotation-media.js +386 -0
  30. package/src/api.js +168 -0
  31. package/src/browser-login.js +13 -0
  32. package/src/callback-page.js +44 -0
  33. package/src/canvas.js +105 -0
  34. package/src/cli-authorization.js +122 -0
  35. package/src/cli.js +464 -0
  36. package/src/download-name.js +17 -0
  37. package/src/draft-finalize.js +229 -0
  38. package/src/generation-graph.js +124 -0
  39. package/src/generation-input.js +472 -0
  40. package/src/graph-disconnect.js +89 -0
  41. package/src/graph.js +431 -0
  42. package/src/group-edit.js +53 -0
  43. package/src/group-layout.js +113 -0
  44. package/src/group-membership.js +125 -0
  45. package/src/history-add.js +119 -0
  46. package/src/image-annotation.js +246 -0
  47. package/src/image-crop.js +283 -0
  48. package/src/image-matting.js +82 -0
  49. package/src/image-media.js +89 -0
  50. package/src/image-output.js +385 -0
  51. package/src/install.js +237 -0
  52. package/src/local-output.js +23 -0
  53. package/src/material-download.js +55 -0
  54. package/src/material-save.js +254 -0
  55. package/src/materials.js +147 -0
  56. package/src/matting-background.js +73 -0
  57. package/src/matting-jobs.js +63 -0
  58. package/src/matting-worker.js +49 -0
  59. package/src/matting.js +170 -0
  60. package/src/media-copy.js +53 -0
  61. package/src/media.js +343 -0
  62. package/src/model-options.js +76 -0
  63. package/src/multipart.js +28 -0
  64. package/src/node-copy.js +95 -0
  65. package/src/node-delete.js +49 -0
  66. package/src/node-history.js +231 -0
  67. package/src/normal-login.js +118 -0
  68. package/src/package-info.js +8 -0
  69. package/src/png-alpha.js +60 -0
  70. package/src/protocol.js +83 -0
  71. package/src/remake.js +738 -0
  72. package/src/repaint-archive.js +53 -0
  73. package/src/repaint-delivery.js +783 -0
  74. package/src/repaint-export.js +63 -0
  75. package/src/repaint-graph.js +214 -0
  76. package/src/repaint-history.js +267 -0
  77. package/src/repaint-materials.js +601 -0
  78. package/src/repaint-reverse.js +614 -0
  79. package/src/repaint-sequence.js +132 -0
  80. package/src/repaint-settings.js +576 -0
  81. package/src/repaint-video.js +798 -0
  82. package/src/repaint.js +326 -0
  83. package/src/resource.js +110 -0
  84. package/src/review.js +435 -0
  85. package/src/service.js +85 -0
  86. package/src/store.js +100 -0
  87. package/src/video-clip.js +381 -0
  88. package/src/video-frame.js +296 -0
  89. package/src/workflows.js +214 -0
package/src/cli.js ADDED
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env node
2
+ import {editGraphGroup} from './group-edit.js'
3
+ import { deleteNodes } from './node-delete.js'
4
+ import { copyTextNode, copyTextNodes } from './node-copy.js'
5
+ import { disconnectGraph } from './graph-disconnect.js'
6
+ import fs from 'node:fs/promises'
7
+ import path from 'node:path'
8
+ import {packageInfo,skillEntry,skillDirectory} from './package-info.js'
9
+ import { Store, json, root, atomic } from './store.js'
10
+ import { ApiClient, baseUrl, cancellation } from './api.js'
11
+ import { segment, validateGeneration, submit, recover, wait, download, generationProtocol } from './workflows.js'
12
+ import { loginTarget } from './browser-login.js'
13
+ import { browserAuthorization, CLIENT_ID, e1Request } from './cli-authorization.js'
14
+ import { createCanvas, writeNode, spaceContext } from './canvas.js'
15
+ import { startRepaint, repaintGet, confirmRepaint, updateRepaintPlan, recoverRepaint, waitRepaint, listRepaints } from './repaint.js'
16
+ import { materialFolders, materialList, materialGet, materialUpload, materialUse } from './materials.js'
17
+ import { downloadMaterial } from './material-download.js'
18
+ import { graphWrite, prepareGeneration, renameGraphGroup, ungroupGraph } from './graph.js'
19
+ import { uploadAndBind, bindUpload } from './resource.js'
20
+ import {syncGeneration} from './generation-graph.js'
21
+ import { prepareDraftFinalize, submitDraftFinalize, getDraftFinalize, waitDraftFinalize } from './draft-finalize.js'
22
+ import { listNodeHistory, selectNodeHistory, recoverNodeHistory } from './node-history.js'
23
+ import { addNodeHistory, recoverHistoryAdd } from './history-add.js'
24
+ import { modelOptions } from './model-options.js'
25
+ import {browserAgreement} from './agreement.js'
26
+ import { startReview, getReview, recoverReview, waitReview } from './review.js'
27
+ import { startMaterialSave, getMaterialSave, recoverMaterialSave } from './material-save.js'
28
+ import { serviceStatus, requireServiceReady, diagnoseServiceError } from './service.js'
29
+ import { startClip, getClip, recoverClip, listClips } from './video-clip.js'
30
+ import { startFrames, getFrames, recoverFrames } from './video-frame.js'
31
+ import { mediaTools } from './media.js'
32
+ import { startCrop, getCrop, recoverCrop } from './image-crop.js'
33
+ import { startAnnotation, getAnnotation, recoverAnnotation } from './image-annotation.js'
34
+ import { checkMatting } from './image-matting.js'
35
+ import { startMatting, recoverMatting } from './matting.js'
36
+ import { launchMattingBackground, mattingBackgroundStatus } from './matting-background.js'
37
+ import { mattingPath } from './matting.js'
38
+ import { submitMattingJob } from './matting-jobs.js'
39
+ import { prepareRemake, submitRemake, getRemake, recoverRemake, waitRemake } from './remake.js'
40
+ import { startReverse, getReverse, recoverReverse, waitReverse } from './repaint-reverse.js'
41
+ import { startDelivery, getDelivery, recoverDelivery, waitDelivery } from './repaint-delivery.js'
42
+ import { repaintVideoOptions, startRepaintVideo, getRepaintVideo, recoverRepaintVideo, waitRepaintVideo, downloadRepaintVideo } from './repaint-video.js'
43
+ import { startRepaintUpload, getRepaintUpload, recoverRepaintUpload, listRepaintMaterials } from './repaint-materials.js'
44
+ import { getRepaintHistory } from './repaint-history.js'
45
+ import { exportRepaintResult } from './repaint-export.js'
46
+ import { prepareRepaintSequence, inspectRepaintSequence, nextRepaintSequence } from './repaint-sequence.js'
47
+ import { getRepaintSettings, startRepaintSettings, repaintSettingsOperation, recoverRepaintSettings } from './repaint-settings.js'
48
+
49
+ const help = `Uzoo Anime CLI — 浏览器授权与独立 CLI 会话
50
+ 命令:
51
+ --version | version [--format json](本地版本,不读取登录态)
52
+ install [--agent NAME ...] [--yes | --dry-run](可选:安装 CLI 和所选 Agent 的 Skill)
53
+ skill path [--format json](定位随包skill,不自动注册或覆盖)
54
+ service status [--timeout MS](先检查后端连接及数据库/Redis就绪)
55
+ video clip --input clip.json --operation NAME [--yes](视频剪辑,保留原视频)
56
+ video clips [--limit N --offset N] | clip-get NAME | clip-recover NAME [--clip-id ID]
57
+ video tools [--ffmpeg PATH --ffprobe PATH](本地解码工具检查,不下载)
58
+ video frame --input frames.json --operation NAME [--yes --ffmpeg PATH --ffprobe PATH]
59
+ video frame-get NAME | frame-recover NAME [--yes --ffmpeg PATH --ffprobe PATH]
60
+ image tools [--ffmpeg PATH --ffprobe PATH](本机依赖检查)
61
+ image crop --input crop.json --operation NAME [--yes --ffmpeg PATH --ffprobe PATH]
62
+ image crop-get NAME | crop-recover NAME [--yes --ffmpeg PATH --ffprobe PATH]
63
+ image annotate --input annotations.json --operation NAME [--yes --ffmpeg PATH --ffprobe PATH]
64
+ image annotate-get NAME | annotate-recover NAME [--yes --ffmpeg PATH --ffprobe PATH]
65
+ image matting-check --input matting.json(只读预检)
66
+ image matting --input matting.json --operation NAME [--yes]
67
+ image matting-get|matting-recover OPERATION [--yes]
68
+ image matting-start OPERATION [--yes](独立后台仅缓存)
69
+ image matting-status OPERATION(本地只读,可离线查询)
70
+ image matting-submit OPERATION [--yes](服务端异步入队)
71
+ draft prepare --input FILE --operation NAME(只读预估样片转正片并保存本地计划)
72
+ draft submit NAME [--yes --expected-cost OO] | get NAME | recover NAME | wait NAME
73
+ remake prepare --input remake.json --operation NAME [--yes](创建真实源片绑定的重拍配置,不生成)
74
+ remake submit NAME [--yes --expected-cost OO](先预估/当次确认,再单次提交)
75
+ remake get NAME | recover NAME [--yes] | wait NAME [--interval MS --timeout MS]
76
+ config set --base-url URL
77
+ auth login [--web-url URL] [--browser default|msedge|chrome] [--timeout MS]
78
+ auth import --input FILE(JSON: accessToken, refreshToken/cookie, deviceId)
79
+ auth import --access-token TOKEN [--refresh-token TOKEN] [--cookie COOKIE]
80
+ auth login-url [--web-url URL] | status | logout
81
+ account info
82
+ model options --type text|image|video --space personal|team [--team-uid UID](只读交互选项,不选择或生成)
83
+ model list --space personal|team [--team-uid UID] [--type image|video|text]
84
+ canvas list --space personal|team [--team-uid UID] [--project-id ID] | get ID
85
+ canvas create --name NAME --space personal|team [--team-uid UID] [--project-id ID]
86
+ node add --doc ID --type text|image|video|audio [--node-id ID] [--input FILE]
87
+ 其他普通节点类型:image_upload|video_upload|audio_upload|video-repaint;派生节点由业务流程创建
88
+ node update --doc ID --node-id ID --input FILE
89
+ node delete --input delete.json [--yes](默认预览普通节点删除影响)
90
+ graph move-group/resize-group/layout-group --input group.json [--yes]
91
+ node copy-batch --input copies.json [--yes](普通节点批量复制及批内引用映射,最多100项)
92
+ node copy --input copy.json [--yes](普通文本/媒体节点,默认预览)
93
+ graph apply --input graph.json(整理、分组、单节点/整组输出连线)
94
+ graph disconnect --input disconnect.json [--yes](默认预览普通断线影响)
95
+ graph ungroup --input ungroup.json [--yes](默认预览,保留节点及连线)
96
+ graph rename-group --input rename.json [--yes](默认预览,执行须expected_version)
97
+ material folders|list --space personal|team [--team-uid UID] [--folder-id ID --keyword TEXT]
98
+ material get ID --space personal|team [--team-uid UID]
99
+ material download ID --space personal|team --output FILE [--team-uid UID --sha256 HEX](原文件,不转换)
100
+ material upload --file FILE --mime MIME --folder-id ID --space personal|team [--team-uid UID --name NAME]
101
+ material use ID --doc ID --node-id ID --space personal|team [--team-uid UID --input position.json]
102
+ material save --input save.json --operation NAME [--yes](原节点结果直接存素材库)
103
+ material save-get NAME | save-recover NAME [--yes](恢复原保存,不生成)
104
+ generation prepare --doc ID --node ID --space personal|team [--team-uid UID --output request.json]
105
+ generation list --doc ID [--node ID] [--status STATUS] [--cursor ID] [--limit N]
106
+ generation estimate --input request.json
107
+ generation submit --input request.json --operation NAME [--yes --expected-cost OO]
108
+ generation recover NAME [--yes] | operations
109
+ generation get ID | logs ID | wait ID [--interval MS] [--timeout MS]
110
+ generation history --doc ID --node ID --space personal|team [--team-uid UID --status STATUS --limit N --offset N]
111
+ generation history-select --input FILE --operation NAME [--yes]
112
+ generation history-add --input FILE --operation NAME [--yes]
113
+ generation history-add-get|history-add-recover OPERATION
114
+ generation history-get NAME | history-recover NAME(查询原历史选择,不重发写入)
115
+ generation sync ID(将原任务真实结果保存至普通生成节点)
116
+ generation protocol-status(只读查询视频《统一协议》确认状态)
117
+ generation protocol-open [--browser default|msedge|chrome] [--timeout MS]
118
+ generation download ID [--output FILE] [--sha256 HEX]
119
+ review start --input review.json --operation NAME [--yes](仅素材审核,不提交生成)
120
+ review get NAME | recover NAME | wait NAME [--interval MS] [--timeout MS]
121
+ repaint start --input repaint.json --operation NAME [--yes]
122
+ repaint get NAME | recover NAME [--restore-detail --yes] | operations
123
+ repaint plan NAME --input plan.json [--yes]
124
+ repaint confirm NAME --plan-version N [--yes]
125
+ repaint wait NAME [--interval MS] [--timeout MS]
126
+ repaint reverse PARENT --segment-index N --operation NAME [--yes --expected-cost OO]
127
+ repaint reverse-get NAME | reverse-recover NAME | reverse-wait NAME [--interval MS --timeout MS]
128
+ repaint delivery PARENT --segment-index N --operation NAME [--input rules.json --yes --expected-cost OO]
129
+ repaint delivery-get NAME | delivery-recover NAME | delivery-wait NAME [--interval MS --timeout MS]
130
+ repaint video-options PARENT --segment-index N(只读当前单段无额外参考的实际模型选项)
131
+ repaint video PARENT --segment-index N --input video.json --operation NAME [--yes --expected-cost OO]
132
+ repaint video-get NAME | video-recover NAME | video-wait NAME [--interval MS --timeout MS]
133
+ repaint video-download NAME --output FILE
134
+ repaint export NAME --type reverse|delivery|video --output FILE [--source remote|archive]
135
+ repaint sequence PARENT --input FILE --operation NAME
136
+ repaint sequence-get NAME
137
+ repaint sequence-next NAME [--yes --step CHILD_OPERATION --expected-cost OO]
138
+ repaint sequence-recover NAME --step CHILD_OPERATION
139
+ repaint history NAME --type reverse|delivery|video(配置改变后只读核验原任务,不写回/下载)
140
+ repaint materials PARENT(只读任务素材池)
141
+ repaint reference-upload PARENT --input reference.json --operation NAME [--yes]
142
+ repaint attachment-upload PARENT --input attachment.json --operation NAME [--yes]
143
+ repaint upload-get NAME | upload-recover NAME [--yes](不重复未知注册)
144
+ repaint settings-get PARENT(只读全局文本与同步状态)
145
+ repaint settings PARENT --input settings.json --operation NAME [--yes](免费编辑,不重处理结果)
146
+ repaint settings-operation NAME | settings-recover NAME
147
+ resource upload --file FILE --doc ID --type image|video|audio --mime MIME
148
+ [--node ID | --asset-type character|scene|prop --name NAME]
149
+ resource bind --doc ID --node ID --input upload-response.json(仅恢复真实上传引用)
150
+ 全局参数:--profile NAME --format json|table --request-timeout MS
151
+ generation submit 首次 --yes 必须带 --expected-cost OO;generation submit 与 repaint 提交不带 --yes 时只预估/展示;上传、节点和graph apply会直接保存;graph rename-group默认只预览。`
152
+ const values = new Set(['profile','format','base-url','web-url','browser','access-token','refresh-token','cookie','device-id','type','id','output','interval','timeout','request-timeout','input','operation','doc','node','node-id','space','team-uid','project-id','status','cursor','limit','file','mime','asset-type','name','sha256','plan-version','folder-id','keyword','offset','expected-cost','agreement-timeout'])
153
+ for(const name of ['clip-id','ffmpeg','ffprobe','segment-index','step','source'])values.add(name)
154
+ function parse(args) {
155
+ const opts = {}, positional = []
156
+ for (let i = 0; i < args.length; i++) {
157
+ const arg = args[i]
158
+ if (!arg.startsWith('-')) { positional.push(arg); continue }
159
+ const key = arg.slice(2)
160
+ if (arg === '--yes' || arg === '--help' || arg === '--restore-detail') { opts[key] = true; continue }
161
+ if (!arg.startsWith('--') || !values.has(key) || opts[key] !== undefined) throw new Error(`未知或重复参数: ${arg}`)
162
+ if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`${arg} 缺少值`)
163
+ opts[key] = args[++i]
164
+ }
165
+ return { opts, positional }
166
+ }
167
+ const print = (data, format) => {
168
+ const rows = Array.isArray(data) ? data : data?.items
169
+ console.log(format === 'table' && Array.isArray(rows) ? rows.map(x => Object.values(x).map(v => typeof v === 'object' ? JSON.stringify(v) : String(v ?? '')).join('\t')).join('\n') : JSON.stringify(data, null, 2))
170
+ }
171
+ function number(value, fallback, max = 86400000) { const n = value === undefined ? fallback : Number(value); if (!Number.isSafeInteger(n) || n <= 0 || n > max) throw new Error(`数字参数必须在 1–${max} 范围内`); return n }
172
+ function need(value, label) { if (!value) throw new Error(`缺少 ${label}`); return value }
173
+ async function input(file) {
174
+ need(file, '--input')
175
+ try { return JSON.parse(await fs.readFile(file, 'utf8')) } catch (error) {
176
+ if (error.code === 'ENOENT') throw new Error('找不到 --input 指定的 JSON 文件;网页登录无需文件,请使用 auth login')
177
+ throw error
178
+ }
179
+ }
180
+ function query(endpoint, fields) { const q = new URLSearchParams(Object.entries(fields).filter(([, v]) => v !== undefined)); return `${endpoint}${q.size ? '?' + q : ''}` }
181
+
182
+ let diagnosticClient
183
+ async function main(args) {
184
+ if (args[0] === 'install') {
185
+ const { runInstall } = await import('./install.js')
186
+ await runInstall(args.slice(1), { signal: cancellation.signal }); return
187
+ }
188
+ if(args[0]==='--version'||args[0]==='-v'){
189
+ if(args.length!==1)throw new Error('--version仅用于查看本地版本,不能与业务参数混用')
190
+ console.log(packageInfo.version);return
191
+ }
192
+ const { opts: o, positional: p } = parse(args)
193
+ if (o.help || !p.length) { console.log(help); return }
194
+ const format = o.format || 'json'
195
+ if (!['json', 'table'].includes(format)) throw new Error('--format 必须是 json 或 table')
196
+ if (p.length > 3) throw new Error('多余的位置参数')
197
+ const [group, action, idArg] = p
198
+ if(group==='version'||group==='skill'){
199
+ if(Object.keys(o).some(k=>k!=='format')||idArg||
200
+ (group==='version'&&p.length!==1)||(group==='skill'&&(action!=='path'||p.length!==2)))
201
+ throw new Error('本地查询仅支持 version 或 skill path,以及--format')
202
+ print(group==='version'?packageInfo:{directory:skillDirectory,entry:skillEntry,registered:false},format);return
203
+ }
204
+ const routes = { config: ['set'], auth: ['login','import','status','logout','login-url'], account: ['info'], model: ['list','options'], canvas: ['list','get','create'], node: ['add','update','copy','copy-batch','delete'], graph:['apply','rename-group','ungroup','disconnect','move-group','resize-group','layout-group'], material:['folders','list','get','download','upload','use','save','save-get','save-recover'], generation: ['history','history-add','history-add-get','history-add-recover','history-select','history-get','history-recover','prepare','protocol-status','protocol-open','list','estimate','submit','recover','operations','get','logs','wait','sync','download'], resource: ['upload','bind'], repaint: ['start','get','recover','operations','plan','confirm','wait','reverse','reverse-get','reverse-recover','reverse-wait','delivery','delivery-get','delivery-recover','delivery-wait','video-options','video','video-get','video-recover','video-wait','video-download','settings','settings-get','settings-operation','settings-recover','materials','reference-upload','attachment-upload','upload-get','upload-recover','history','export','sequence','sequence-get','sequence-next','sequence-recover'], review: ['start','get','recover','wait'] }
205
+ routes.service = ['status']
206
+ routes.draft = ['prepare','submit','get','recover','wait']
207
+ routes.remake = ['prepare','submit','get','recover','wait']
208
+ routes.video = ['clip','clips','clip-get','clip-recover','tools','frame','frame-get','frame-recover']
209
+ routes.image = ['tools','crop','crop-get','crop-recover','annotate','annotate-get','annotate-recover','matting-check','matting','matting-get','matting-recover','matting-start','matting-status','matting-submit']
210
+ if (!routes[group]?.includes(action)) throw new Error('未知命令;使用 --help 查看')
211
+ const permitted = {
212
+ 'config set':['base-url'], 'auth login':['web-url','browser','timeout'], 'auth import':['input','access-token','refresh-token','cookie','device-id','base-url'], 'auth login-url':['web-url'],
213
+ 'model list':['type','space','team-uid'], 'model options':['type','space','team-uid'], 'canvas list':['space','team-uid','project-id','cursor','limit'], 'canvas get':['id'],
214
+ 'canvas create':['name','space','team-uid','project-id'], 'node add':['doc','type','node-id','input'], 'node update':['doc','node-id','input'], 'node copy':['input','yes'], 'node copy-batch':['input','yes'], 'node delete':['input','yes'],
215
+ 'generation list':['doc','node','status','cursor','limit'], 'generation estimate':['input'], 'generation submit':['input','operation','yes','expected-cost','browser','agreement-timeout'],
216
+ 'generation recover':['id','yes','expected-cost','browser','agreement-timeout'], 'generation get':['id'], 'generation logs':['id'], 'generation wait':['id','timeout','interval'],
217
+ 'generation download':['id','output','sha256'], 'resource upload':['file','doc','node','type','mime','asset-type','name'],
218
+ 'repaint start':['input','operation','yes'], 'repaint get':[], 'repaint recover':['restore-detail','yes'], 'repaint operations':[],
219
+ 'review start':['input','operation','yes'], 'review get':[], 'review recover':[], 'review wait':['interval','timeout'],
220
+ 'repaint export':['type','output','source'], 'repaint sequence':['input','operation'], 'repaint sequence-get':[], 'repaint sequence-next':['yes','step','expected-cost','browser','agreement-timeout'], 'repaint sequence-recover':['step'],
221
+ 'repaint history':['type'], 'repaint materials':[], 'repaint reference-upload':['input','operation','yes'], 'repaint attachment-upload':['input','operation','yes'], 'repaint upload-get':[], 'repaint upload-recover':['yes'],
222
+ 'repaint settings':['input','operation','yes'], 'repaint settings-get':[], 'repaint settings-operation':[], 'repaint settings-recover':[],
223
+ 'repaint video-options':['segment-index'], 'repaint video':['segment-index','input','operation','yes','expected-cost','browser','agreement-timeout'],
224
+ 'repaint video-get':[], 'repaint video-recover':[], 'repaint video-wait':['interval','timeout'], 'repaint video-download':['output'],
225
+ 'repaint delivery':['segment-index','operation','input','yes','expected-cost'], 'repaint delivery-get':[], 'repaint delivery-recover':[], 'repaint delivery-wait':['interval','timeout'],
226
+ 'repaint reverse':['segment-index','operation','yes','expected-cost'], 'repaint reverse-get':[], 'repaint reverse-recover':[], 'repaint reverse-wait':['interval','timeout'],
227
+ 'repaint plan':['input','yes'], 'repaint confirm':['plan-version','yes'], 'repaint wait':['interval','timeout'],
228
+ 'graph move-group':['input','yes'], 'graph resize-group':['input','yes'], 'graph layout-group':['input','yes'], 'graph apply':['input'], 'graph rename-group':['input','yes'], 'graph ungroup':['input','yes'], 'graph disconnect':['input','yes'], 'material folders':['space','team-uid'], 'material list':['space','team-uid','folder-id','keyword','limit','offset'],
229
+ 'material get':['space','team-uid'], 'material download':['space','team-uid','output','sha256'], 'material upload':['space','team-uid','folder-id','file','mime','name'],
230
+ 'material use':['space','team-uid','doc','node-id','input'], 'generation prepare':['doc','node','space','team-uid','output'],
231
+ 'material save':['input','operation','yes'], 'material save-get':[], 'material save-recover':['yes'],
232
+ 'resource bind':['doc','node','input'],
233
+ 'generation history':['doc','node','space','team-uid','status','limit','offset'], 'generation history-add':['input','operation','yes'], 'generation history-add-get':[], 'generation history-add-recover':[], 'generation history-select':['input','operation','yes'], 'generation history-get':[], 'generation history-recover':[],
234
+ 'generation sync':['id'],
235
+ 'generation protocol-status':[],
236
+ 'generation protocol-open':['browser','timeout'],
237
+ 'service status':['timeout'],
238
+ 'video clip':['input','operation','yes'], 'video clips':['limit','offset'], 'video clip-get':[], 'video clip-recover':['clip-id'],
239
+ 'video tools':['ffmpeg','ffprobe'], 'video frame':['input','operation','yes','ffmpeg','ffprobe'],
240
+ 'video frame-get':[], 'video frame-recover':['yes','ffmpeg','ffprobe'],
241
+ 'image tools':['ffmpeg','ffprobe'], 'image crop':['input','operation','yes','ffmpeg','ffprobe'],
242
+ 'image crop-get':[], 'image crop-recover':['yes','ffmpeg','ffprobe'],
243
+ 'image annotate':['input','operation','yes','ffmpeg','ffprobe'],
244
+ 'image annotate-get':[], 'image annotate-recover':['yes','ffmpeg','ffprobe'],
245
+ 'image matting-check':['input'], 'image matting':['input','operation','yes'], 'image matting-get':[], 'image matting-recover':['yes'], 'image matting-start':['yes'], 'image matting-status':[], 'image matting-submit':['yes'],
246
+ 'draft prepare':['input','operation'], 'draft submit':['yes','expected-cost'], 'draft get':[], 'draft recover':[], 'draft wait':['interval','timeout'],
247
+ 'remake prepare':['input','operation','yes'], 'remake submit':['yes','expected-cost','browser','agreement-timeout'],
248
+ 'remake get':[], 'remake recover':['yes'], 'remake wait':['interval','timeout'],
249
+ }
250
+ const allowed = new Set(['profile','format','request-timeout', ...(permitted[`${group} ${action}`] || [])])
251
+ for (const key of Object.keys(o)) if (!allowed.has(key)) throw new Error(`当前命令不支持 --${key}`)
252
+ if (idArg && !['get','logs','wait','download','recover', ...(group==='generation'?['sync','history-get','history-recover']:[]), ...(['remake','draft'].includes(group)?['submit']:[]), ...(group==='video'?['clip-get','clip-recover','frame-get','frame-recover']:[]), ...(group==='image'?['crop-get','crop-recover','annotate-get','annotate-recover','matting-get','matting-recover','matting-start','matting-status','matting-submit']:[]), ...(group === 'repaint' ? ['plan','confirm','reverse','reverse-get','reverse-recover','reverse-wait','delivery','delivery-get','delivery-recover','delivery-wait','video-options','video','video-get','video-recover','video-wait','video-download','settings','settings-get','settings-operation','settings-recover','materials','reference-upload','attachment-upload','upload-get','upload-recover','history','export','sequence','sequence-get','sequence-next','sequence-recover'] : []), ...(group==='material'?['use','save-get','save-recover']:[])].includes(action)) throw new Error('当前命令不接受额外 ID')
253
+ const store = new Store(o.profile || 'default')
254
+ if(group==='image'&&action==='matting-status') {print(await mattingBackgroundStatus(store,need(idArg,'抠图operation')),format);return}
255
+ if(group==='image'&&action==='matting-get') {
256
+ const record=await json(mattingPath(store.name,need(idArg,'抠图operation')),null)
257
+ if(record?.backgroundId){print(await mattingBackgroundStatus(store,idArg),format);return}
258
+ }
259
+ if(group==='image'&&action==='matting-start') {
260
+ const record=await json(mattingPath(store.name,need(idArg,'抠图operation')),null)
261
+ if(record?.backgroundId){
262
+ const status=await mattingBackgroundStatus(store,idArg)
263
+ if(record.attempted||['launching','waiting_lock','running'].includes(status.worker_status)){print(status,format);return}
264
+ }
265
+ }
266
+ const unlock = await store.lock()
267
+ try {
268
+ if(['video','image'].includes(group) && action==='tools') {await mediaTools({ffmpeg:o.ffmpeg,ffprobe:o.ffprobe});print({available:true,decoder:'ffmpeg',probe:'ffprobe',downloaded:false},format);return}
269
+ if (group === 'auth' && action === 'logout') {
270
+ const profile = await store.load()
271
+ if (profile.authMode === 'cli' && profile.refreshToken) {
272
+ const client = new ApiClient(profile, store, number(o['request-timeout'], 30000))
273
+ if (client.refreshRequestId) await client.refresh()
274
+ await e1Request(client.baseUrl, '/auth/cli/revoke', { client_id: CLIENT_ID, device_id: client.deviceId, refresh_token: client.refreshToken }, client.timeout)
275
+ }
276
+ await store.clear(); print({ loggedOut: true, remoteRevoked: profile.authMode === 'cli' }, format); return
277
+ }
278
+ if (group === 'config') {
279
+ let config = await json(store.configFile, {})
280
+ if (config.accessToken || config.cookie || config.refreshToken) { await store.load(); config = await json(store.configFile, {}) }
281
+ if (config.baseUrl && baseUrl(config.baseUrl) !== baseUrl(need(o['base-url'], '--base-url'))) throw new Error('切换服务请先 auth logout 或使用新 profile,避免凭据发往其他服务')
282
+ await atomic(store.configFile, { ...config, baseUrl: baseUrl(need(o['base-url'], '--base-url')) })
283
+ print({ profile: store.name, baseUrl: baseUrl(o['base-url']) }, format); return
284
+ }
285
+ if (group === 'auth' && action === 'login-url') {
286
+ const config = await store.config()
287
+ const target = loginTarget(config.baseUrl || need(o['web-url'], '--web-url 或 API 配置'), o['web-url'])
288
+ print({ url: target.url, note: '此命令只打印链接;需要网页登录后自动保存会话,请使用 auth login' }, format); return
289
+ }
290
+ if (group === 'auth' && action === 'login') {
291
+ const config = await store.config()
292
+ need(config.baseUrl, 'API 配置;请先 config set --base-url 网站域名')
293
+ const result = await browserAuthorization({ profile: config, store, webUrl: o['web-url'], browser: o.browser || 'default', timeout: number(o.timeout, 300000), requestTimeout: number(o['request-timeout'], 30000), onReady: url => console.error(`请在普通浏览器打开授权页面:${url}`), onOpenError: message => console.error(message) })
294
+ print(result, format); return
295
+ }
296
+ if (group === 'auth' && action === 'import') {
297
+ const config = await json(store.configFile, {})
298
+ const imported = o.input ? await input(o.input) : { accessToken: o['access-token'], refreshToken: o['refresh-token'], cookie: o.cookie, deviceId: o['device-id'] }
299
+ need(imported.accessToken, 'accessToken')
300
+ if (config.baseUrl && o['base-url'] && baseUrl(config.baseUrl) !== baseUrl(o['base-url'])) throw new Error('切换服务请先退出或使用新 profile')
301
+ await store.save({ ...imported, baseUrl: baseUrl(o['base-url'] || config.baseUrl || (await store.config()).baseUrl) })
302
+ print({ profile: store.name, imported: true }, format); return
303
+ }
304
+ if (group === 'generation' && action === 'operations') {
305
+ const dir = path.join(root(), 'operations', store.name)
306
+ const files = await fs.readdir(dir).catch(e => { if (e.code === 'ENOENT') return []; throw e })
307
+ const records = await Promise.all(files.filter(f => f.endsWith('.json')).map(f => json(path.join(dir, f))))
308
+ print(records.map(({ operation, taskId, state, createdAt, lastFailure }) => ({ operation, taskId, state:lastFailure?.apiCode===4401?'blocked_agreement':state, createdAt, ...(lastFailure?{lastFailure}:{}) })), format); return
309
+ }
310
+ if (group === 'repaint' && action === 'operations') { print(await listRepaints(store.name), format); return }
311
+ if (group === 'service') {
312
+ const config = await store.config()
313
+ need(config.baseUrl, 'API 配置;请先 config set --base-url 网站域名')
314
+ const service = await serviceStatus({baseUrl:baseUrl(config.baseUrl),timeout:number(o['request-timeout'],8000)},number(o.timeout,8000,60000))
315
+ print(service,format);if(service.status!=='ready')process.exitCode=3;return
316
+ }
317
+ const profile = await store.load()
318
+ need(profile.accessToken, '登录态;请先 auth login(或 auth import)')
319
+ const client = new ApiClient(profile, store, number(o['request-timeout'], 30000))
320
+ diagnosticClient = client
321
+ const openAgreement=(continuation,timeout=number(o['agreement-timeout'],300000,1800000))=>browserAgreement(client,{browser:o.browser || 'default',timeout,continuation,onReady:(url,info)=>console.error(JSON.stringify({status:'awaiting_agreement',api_code:4401,agreement:info.protocol.label,document_title:info.document.title,version:info.protocol.version,url,message:'请本人阅读、勾选并点击同意;CLI 将核验服务端状态后继续'})),onOpenError:message=>console.error(message)})
322
+ const id = () => need(o.id || idArg, 'ID')
323
+ const expectedCost=o['expected-cost']===undefined?undefined:Number(o['expected-cost'])
324
+ if(expectedCost!==undefined && (!Number.isFinite(expectedCost)||expectedCost<0)) throw new Error('--expected-cost 必须为非负 OO 币数字')
325
+ let data
326
+ if (group === 'draft') {
327
+ if(action==='prepare') data=await prepareDraftFinalize(client,store.name,await input(o.input),need(o.operation,'--operation'))
328
+ else if(action==='submit') data=await submitDraftFinalize(client,store.name,need(idArg,'样片 operation'),o.yes,expectedCost)
329
+ else if(action==='wait') data=await waitDraftFinalize(client,store.name,need(idArg,'样片 operation'),number(o.interval,3000,60000),number(o.timeout,600000))
330
+ else data=await getDraftFinalize(client,store.name,need(idArg,'样片 operation'),action==='recover')
331
+ }
332
+ else if (group === 'remake') {
333
+ if(o.yes || action==='wait') await requireServiceReady(client)
334
+ if(action==='prepare') data=await prepareRemake(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
335
+ else if(action==='submit') data=await submitRemake(client,store.name,need(idArg,'重拍 operation'),o.yes,expectedCost,{onAgreementRequired:({operation,body,estimate})=>openAgreement({operation,model:body.model_name,cost:estimate.estimated_credit_cost})})
336
+ else if(action==='get') data=await getRemake(client,store.name,need(idArg,'重拍 operation'))
337
+ else if(action==='wait') data=await waitRemake(client,store.name,need(idArg,'重拍 operation'),number(o.interval,6000,60000),number(o.timeout,600000))
338
+ else data=await recoverRemake(client,store.name,need(idArg,'重拍 operation'),o.yes)
339
+ }
340
+ else if (group === 'image') {
341
+ if (o.yes) await requireServiceReady(client)
342
+ const tools={ffmpeg:o.ffmpeg,ffprobe:o.ffprobe}
343
+ if(action==='matting-check') data=await checkMatting(client,await input(o.input))
344
+ else if(action==='matting-submit') data=await submitMattingJob(client,store.name,need(idArg,'抠图operation'),o.yes)
345
+ else if(action==='matting-start') data=await launchMattingBackground(client,store,need(idArg,'抠图operation'),o.yes,{requestTimeout:number(o['request-timeout'],180000,1800000)})
346
+ else if(action==='matting') data=await startMatting(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
347
+ else if(['matting-get','matting-recover'].includes(action)) data=await recoverMatting(client,store.name,need(idArg,'抠图 operation'),action==='matting-recover'&&o.yes)
348
+ else if(action==='annotate') data=await startAnnotation(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes,tools)
349
+ else if(action==='annotate-get') data=await getAnnotation(client,store.name,need(idArg,'标注 operation 名称'))
350
+ else if(action==='annotate-recover') data=await recoverAnnotation(client,store.name,need(idArg,'标注 operation 名称'),o.yes,tools)
351
+ else if(action==='crop') data=await startCrop(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes,tools)
352
+ else if(action==='crop-get') data=await getCrop(client,store.name,need(idArg,'裁剪 operation 名称'))
353
+ else data=await recoverCrop(client,store.name,need(idArg,'裁剪 operation 名称'),o.yes,tools)
354
+ }
355
+ else if (group === 'video') {
356
+ if ((['clip','frame','frame-recover'].includes(action) && o.yes) || action==='clip-recover') await requireServiceReady(client)
357
+ if(action==='frame')data=await startFrames(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes,{ffmpeg:o.ffmpeg,ffprobe:o.ffprobe})
358
+ else if(action==='frame-get')data=await getFrames(client,store.name,need(idArg,'截帧 operation 名称'))
359
+ else if(action==='frame-recover')data=await recoverFrames(client,store.name,need(idArg,'截帧 operation 名称'),o.yes,{ffmpeg:o.ffmpeg,ffprobe:o.ffprobe})
360
+ else if(action==='clip') data=await startClip(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
361
+ else if(action==='clips')data=await listClips(client,number(o.limit,50,200),o.offset===undefined?0:Number(o.offset))
362
+ else if(action==='clip-get')data=await getClip(client,store.name,need(idArg,'剪辑 operation 名称'))
363
+ else data=await recoverClip(client,store.name,need(idArg,'剪辑 operation 名称'),o['clip-id'])
364
+ }
365
+ else if (group === 'review') {
366
+ const operation = () => need(idArg, '审核 operation 名称')
367
+ if (action === 'start') data = await startReview(client, store.name, await input(o.input), need(o.operation, '--operation'), o.yes)
368
+ else if (action === 'get') data = await getReview(client, store.name, operation())
369
+ else if (action === 'recover') data = await recoverReview(client, store.name, operation())
370
+ else data = await waitReview(client, store.name, operation(), number(o.interval, 3000), number(o.timeout, 180000))
371
+ }
372
+ else if (group === 'repaint') {
373
+ const operation = () => need(idArg, '转绘 operation 名称')
374
+ if (action === 'start') data = await startRepaint(client, store.name, await input(o.input), need(o.operation, '--operation'), o.yes)
375
+ else if (action === 'export') data = await exportRepaintResult(client,store.name,operation(),need(o.type,'--type'),need(o.output,'--output'),o.source || 'remote')
376
+ else if (action === 'sequence') data = await prepareRepaintSequence(client,store.name,operation(),need(o.operation,'--operation'),await input(o.input))
377
+ else if (action === 'sequence-get') data = await inspectRepaintSequence(client,store.name,operation())
378
+ else if (['sequence-next','sequence-recover'].includes(action)) data = await nextRepaintSequence(client,store.name,operation(),{confirmed:o.yes,expectedCost,step:action==='sequence-recover'||o.yes?need(o.step,'--step'):o.step,recover:action==='sequence-recover',onAgreementRequired:({operation,body,estimate})=>openAgreement({operation,model:body.model_name,cost:estimate.estimated_credit_cost})})
379
+ else if (action === 'history') data = await getRepaintHistory(client,store.name,operation(),need(o.type,'--type'))
380
+ else if (action === 'materials') data = await listRepaintMaterials(client,store.name,operation())
381
+ else if (['reference-upload','attachment-upload'].includes(action)) data = await startRepaintUpload(client,store.name,operation(),need(o.operation,'--operation'),action==='reference-upload'?'reference':'attachment',await input(o.input),o.yes)
382
+ else if (action === 'upload-get') data = await getRepaintUpload(client,store.name,operation())
383
+ else if (action === 'upload-recover') data = await recoverRepaintUpload(client,store.name,operation(),o.yes)
384
+ else if (action === 'settings-get') data = await getRepaintSettings(client, store.name, operation())
385
+ else if (action === 'settings') data = await startRepaintSettings(client, store.name, operation(), need(o.operation,'--operation'), await input(o.input), o.yes)
386
+ else if (action === 'settings-operation') data = await repaintSettingsOperation(client, store.name, operation())
387
+ else if (action === 'settings-recover') data = await recoverRepaintSettings(client, store.name, operation())
388
+ else if (action === 'video-options') data = await repaintVideoOptions(client, store.name, operation(), number(need(o['segment-index'],'--segment-index'),undefined,Number.MAX_SAFE_INTEGER))
389
+ else if (action === 'video') data = await startRepaintVideo(client, store.name, operation(), number(need(o['segment-index'],'--segment-index'),undefined,Number.MAX_SAFE_INTEGER), need(o.operation,'--operation'), await input(o.input), o.yes, expectedCost,{onAgreementRequired:({operation,body,estimate})=>openAgreement({operation,model:body.model_name,cost:estimate.estimated_credit_cost})})
390
+ else if (action === 'video-get') data = await getRepaintVideo(client, store.name, operation())
391
+ else if (action === 'video-recover') data = await recoverRepaintVideo(client, store.name, operation())
392
+ else if (action === 'video-wait') data = await waitRepaintVideo(client, store.name, operation(), number(o.interval,3000), number(o.timeout,600000))
393
+ else if (action === 'video-download') data = await downloadRepaintVideo(client, store.name, operation(), need(o.output,'--output'))
394
+ else if (action === 'delivery') data = await startDelivery(client, store.name, operation(), number(need(o['segment-index'],'--segment-index'),undefined,Number.MAX_SAFE_INTEGER), need(o.operation,'--operation'), o.input ? await input(o.input) : {}, o.yes, expectedCost)
395
+ else if (action === 'delivery-get') data = await getDelivery(client, store.name, operation())
396
+ else if (action === 'delivery-recover') data = await recoverDelivery(client, store.name, operation())
397
+ else if (action === 'delivery-wait') data = await waitDelivery(client, store.name, operation(), number(o.interval,2000), number(o.timeout,180000))
398
+ else if (action === 'reverse') data = await startReverse(client, store.name, operation(), number(need(o['segment-index'],'--segment-index'),undefined,Number.MAX_SAFE_INTEGER), need(o.operation,'--operation'), o.yes, expectedCost)
399
+ else if (action === 'reverse-get') data = await getReverse(client, store.name, operation())
400
+ else if (action === 'reverse-recover') data = await recoverReverse(client, store.name, operation())
401
+ else if (action === 'reverse-wait') data = await waitReverse(client, store.name, operation(), number(o.interval,2000), number(o.timeout,180000))
402
+ else if (action === 'get') data = await repaintGet(client, store.name, operation())
403
+ else if (action === 'recover') data = await recoverRepaint(client, store.name, operation(), {restoreDetail:o['restore-detail'],confirmed:o.yes})
404
+ else if (action === 'plan') data = await updateRepaintPlan(client, store.name, operation(), await input(o.input), o.yes)
405
+ else if (action === 'confirm') data = await confirmRepaint(client, store.name, operation(), number(need(o['plan-version'], '--plan-version'), undefined, Number.MAX_SAFE_INTEGER), o.yes)
406
+ else data = await waitRepaint(client, store.name, operation(), number(o.interval, 3000), number(o.timeout, 600000))
407
+ }
408
+ else if (group === 'graph') data = ['move-group','resize-group','layout-group'].includes(action) ? await editGraphGroup(client,await input(o.input),action.replace('-group',''),o.yes) : action === 'disconnect' ? await disconnectGraph(client,await input(o.input),o.yes) : action === 'ungroup' ? await ungroupGraph(client,await input(o.input),o.yes) : action === 'rename-group' ? await renameGraphGroup(client,await input(o.input),o.yes) : await graphWrite(client,await input(o.input))
409
+ else if (group === 'material') {
410
+ if (o.yes && ['save','save-recover'].includes(action)) await requireServiceReady(client)
411
+ const context={space:o.space,teamUid:o['team-uid']}
412
+ if(action==='folders') data=await materialFolders(client,context)
413
+ else if(action==='list') data=await materialList(client,{...context,folderId:o['folder-id'],keyword:o.keyword,limit:number(o.limit,50,200),offset:o.offset===undefined?0:Number(o.offset)})
414
+ else if(action==='get') data=await materialGet(client,{...context,assetId:id()})
415
+ else if(action==='download') data=await downloadMaterial(client,{...context,assetId:id(),output:o.output,expectedHash:o.sha256})
416
+ else if(action==='save') data=await startMaterialSave(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
417
+ else if(action==='save-get') data=await getMaterialSave(client,store.name,need(idArg,'保存 operation 名称'))
418
+ else if(action==='save-recover') data=await recoverMaterialSave(client,store.name,need(idArg,'保存 operation 名称'),o.yes)
419
+ else if(action==='upload') data=await materialUpload(client,{...context,folderId:need(o['folder-id'],'--folder-id'),file:need(o.file,'--file'),mime:need(o.mime,'--mime'),name:o.name})
420
+ else data=await materialUse(client,{...context,assetId:id(),docId:need(o.doc,'--doc'),nodeId:need(o['node-id'],'--node-id'),position:o.input?(await input(o.input)).position:undefined})
421
+ }
422
+ else if (group === 'generation' && action === 'history') data=await listNodeHistory(client,{doc_id:need(o.doc,'--doc'),node_id:need(o.node,'--node'),space:o.space,...(o['team-uid']?{team_uid:o['team-uid']}:{})},{status:o.status,limit:number(o.limit,30,100),offset:o.offset===undefined?0:Number(o.offset)})
423
+ else if (group === 'generation' && action === 'history-add') data=await addNodeHistory(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
424
+ else if (group === 'generation' && ['history-add-get','history-add-recover'].includes(action)) data=await recoverHistoryAdd(client,store.name,need(idArg,'历史添加operation'))
425
+ else if (group === 'generation' && action === 'history-select') data=await selectNodeHistory(client,store.name,await input(o.input),need(o.operation,'--operation'),o.yes)
426
+ else if (group === 'generation' && ['history-get','history-recover'].includes(action)) data=await recoverNodeHistory(client,store.name,need(idArg,'历史选择operation'))
427
+ else if (group === 'generation' && action === 'protocol-status') data=await generationProtocol(client)
428
+ else if (group === 'generation' && action === 'protocol-open') data=await openAgreement(undefined,number(o.timeout,300000,1800000))
429
+ else if (group === 'generation' && action === 'prepare') {
430
+ data=await prepareGeneration(client,{docId:need(o.doc,'--doc'),nodeId:need(o.node,'--node'),space:o.space,teamUid:o['team-uid']})
431
+ if(o.output) {await fs.writeFile(o.output,JSON.stringify(data.request,null,2)+'\n',{encoding:'utf8',flag:'wx'});data.output=path.resolve(o.output)}
432
+ }
433
+ else if ((group === 'auth' && action === 'status') || group === 'account') data = await client.raw('/auth/me')
434
+ else if (group === 'model' && action === 'options') data = await modelOptions(client,{space:o.space,teamUid:o['team-uid'],type:need(o.type,'--type')})
435
+ else if (group === 'model') data = await client.raw(query('/canvas/models', { ...spaceContext({ space: o.space, teamUid: o['team-uid'] }), content_type: o.type }))
436
+ else if (group === 'canvas' && action === 'create') data = await createCanvas(client, { name: need(o.name, '--name'), space: o.space, teamUid: o['team-uid'], projectId: o['project-id'] === undefined ? undefined : number(o['project-id'], undefined, Number.MAX_SAFE_INTEGER) })
437
+ else if (group === 'canvas') data = await client.raw(action === 'get' ? `/canvas/docs/${segment(id())}` : query('/canvas/docs', { ...spaceContext({ space: o.space, teamUid: o['team-uid'], projectId: o['project-id'] }), canvas_project_id: o['project-id'], cursor: o.cursor, limit: number(o.limit, 20, 100) }))
438
+ else if (group === 'node' && action === 'delete') data = await deleteNodes(client,await input(o.input),o.yes)
439
+ else if (group === 'node' && action === 'copy-batch') data = await copyTextNodes(client,await input(o.input),o.yes)
440
+ else if (group === 'node' && action === 'copy') data = await copyTextNode(client,await input(o.input),o.yes)
441
+ else if (group === 'node') data = await writeNode(client, { docId: need(o.doc, '--doc'), nodeId: action === 'update' ? need(o['node-id'], '--node-id') : o['node-id'], type: o.type, patch: action === 'update' || o.input ? await input(o.input) : {}, update: action === 'update' })
442
+ else if (group === 'resource') data = action==='bind' ? await bindUpload(client,{docId:need(o.doc,'--doc'),nodeId:need(o.node,'--node'),result:await input(o.input)}) : await uploadAndBind(client, { file: need(o.file, '--file'), doc: need(o.doc, '--doc'), node: o.node, type: need(o.type, '--type'), mime: need(o.mime, '--mime'), assetType: o['asset-type'], name: o.name })
443
+ else if (action === 'list') data = await client.raw(query('/canvas/generations', { doc_id: need(o.doc, '--doc'), node_id: o.node, status: o.status, cursor: o.cursor, limit: number(o.limit, 20, 100) }))
444
+ else if (action === 'estimate') { const body = await input(o.input); validateGeneration(body); data = await client.raw('/canvas/generations/estimate', { method: 'POST', body }) }
445
+ else if (action === 'submit') data = await submit(client, store.name, await input(o.input), need(o.operation, '--operation'), o.yes, expectedCost,{onAgreementRequired:({operation,body,estimate})=>openAgreement({operation,model:body.model_name,cost:estimate.estimated_credit_cost})})
446
+ else if (action === 'recover') data = await recover(client, store.name, id(), o.yes, expectedCost,{onAgreementRequired:({operation,body,estimate})=>openAgreement({operation,model:body.model_name,cost:estimate.estimated_credit_cost})})
447
+ else if (action === 'sync') data=await syncGeneration(client,id())
448
+ else if (action === 'wait') {data = await wait(client, id(), number(o.interval, 3000), number(o.timeout, 600000));data={...data,canvas_sync:await syncGeneration(client,id())}}
449
+ else if (action === 'download') { if (o.sha256 && !/^[a-fA-F0-9]{64}$/.test(o.sha256)) throw new Error('--sha256 必须是 64 位十六进制'); data = await download(client, id(), o.output, o.sha256) }
450
+ else data = await client.raw(`/canvas/generations/${segment(id())}${action === 'logs' ? '/logs' : ''}`)
451
+ print(data, format)
452
+ } finally { await unlock() }
453
+ }
454
+ process.on('SIGINT', () => cancellation.abort())
455
+ main(process.argv.slice(2)).catch(async error => {
456
+ const service = error.service || (diagnosticClient ? await diagnoseServiceError(diagnosticClient,error) : null)
457
+ const codes = { MATTING_BACKGROUND_FAILED:4, MATTING_BACKGROUND_ACTIVE:4, MATTING_SUBMISSION_PENDING:4, MATTING_SYNC_PENDING:4, DRAFT_SUBMISSION_UNKNOWN:4, DRAFT_SYNC_PENDING:4, NODE_HISTORY_UNAVAILABLE:2, NODE_HISTORY_SAVE_PENDING:4, REPAINT_RESULT_UNAVAILABLE:2, REPAINT_ARCHIVE_UNAVAILABLE:2, REPAINT_ARCHIVE_INVALID:2, REPAINT_ARCHIVE_CONFLICT:2, DELIVERY_ATTACHMENT_ADDRESS_UNUSABLE:2, REPAINT_UPLOAD_UNKNOWN:4, REPAINT_SETTINGS_SAVE_UNKNOWN: 4, REPAINT_SETTINGS_SYNC_PENDING: 4, REPAINT_VIDEO_DOWNLOAD_FORBIDDEN: 3, API_ERROR: 3, SERVICE_UNAVAILABLE: 3, MEDIA_UNAVAILABLE: 2, FRAME_UPLOAD_UNKNOWN: 4, FRAME_SYNC_PENDING: 4, CROP_UPLOAD_UNKNOWN: 4, CROP_SYNC_PENDING: 4, ANNOTATION_UPLOAD_UNKNOWN: 4, ANNOTATION_SYNC_PENDING: 4, REPAINT_VIDEO_SUBMISSION_UNKNOWN: 4, REPAINT_VIDEO_SYNC_PENDING: 4, DELIVERY_SUBMISSION_UNKNOWN: 4, DELIVERY_SYNC_PENDING: 4, REVERSE_SUBMISSION_UNKNOWN: 4, REVERSE_SYNC_PENDING: 4, REMAKE_SUBMISSION_UNKNOWN: 4, REMAKE_SYNC_PENDING: 4, SUBMISSION_UNKNOWN: 4, CLIP_RESULT_UNKNOWN: 4, CLIP_SYNC_PENDING: 4, MATERIAL_SAVE_REJECTED: 3, MATERIAL_SAVE_UNKNOWN: 4, REVIEW_REQUEST_REJECTED: 3, REVIEW_SUBMISSION_UNKNOWN: 4, REVIEW_SYNC_PENDING: 4, WAIT_TIMEOUT: 5, TASK_FAILED: 6 }
458
+ const timeout = ['TimeoutError', 'AbortError'].includes(error.name)
459
+ const agreement=String(error.code || '').startsWith('AGREEMENT_')
460
+ const code = agreement?error.code:error.code && codes[error.code] ? error.code : timeout ? 'REQUEST_TIMEOUT' : 'CLI_ERROR'
461
+ const message = error instanceof SyntaxError ? 'JSON 或 URL 格式无效' : error instanceof TypeError ? '请求失败,请检查网络与配置' : error.message
462
+ console.error(JSON.stringify({ error: { code, message,...(error.dependency?{dependency:error.dependency}:{}),...(error.code==='DELIVERY_ATTACHMENT_ADDRESS_UNUSABLE'?{blockers:error.blockers}:{}),...(service?{service}:{}),...(error.requestId?{request_id:error.requestId}:{}),...(error.diagnostic?{diagnostic:error.diagnostic}:{}),...(agreement?{status:error.code==='AGREEMENT_REJECTED'?'agreement_rejected':'awaiting_agreement',...(error.apiCode?{api_code:error.apiCode}:{}),agreement:'《统一协议》',document_title:'《Seedance2.0模型使用承诺书》',...(error.protocol?{protocol:error.protocol}:{})}:{}) } }))
463
+ process.exitCode = cancellation.signal.aborted ? 130 : agreement?7:codes[code] || (timeout ? 5 : 2)
464
+ })
@@ -0,0 +1,17 @@
1
+ // Read the delivered filename; this never sends a filename/format override.
2
+ function safeLeaf(value) {
3
+ if (typeof value !== 'string' || !value.trim() || /[\x00-\x1f\x7f<>:"/\\|?*]/.test(value)) return null
4
+ const name=value.trim().replace(/[. ]+$/g,'')
5
+ if (!name || name==='.' || name==='..' || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(name)) return null
6
+ return name
7
+ }
8
+ export function downloadName(headers, fallback) {
9
+ const disposition=headers.get('content-disposition') || ''
10
+ const extended=disposition.match(/(?:^|;)\s*filename\*\s*=\s*(?:"([^"]*)"|([^;]*))/i)
11
+ if(extended){
12
+ const encoded=(extended[1]??extended[2]).trim().match(/^utf-8'[^']*'(.*)$/i)
13
+ if(encoded)try{const name=safeLeaf(decodeURIComponent(encoded[1]));if(name)return name}catch{}
14
+ }
15
+ const plain=disposition.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]*))/i)
16
+ return safeLeaf(plain ? (plain[1]===undefined?plain[2].trim():plain[1].replace(/\\(.)/g,'$1')) : null) || safeLeaf(fallback)
17
+ }