@uzooai/anime-ai-cli 0.0.0-stage → 0.5.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 (123) hide show
  1. package/.agents/skills/uzoo-anime-cli/SKILL.md +83 -0
  2. package/.agents/skills/uzoo-anime-cli/references/agreement-workflow.md +51 -0
  3. package/.agents/skills/uzoo-anime-cli/references/canvas-workflow.md +53 -0
  4. package/.agents/skills/uzoo-anime-cli/references/commands.md +250 -0
  5. package/.agents/skills/uzoo-anime-cli/references/generation-workflow.md +152 -0
  6. package/.agents/skills/uzoo-anime-cli/references/image-tools-workflow.md +175 -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 +123 -0
  9. package/.agents/skills/uzoo-anime-cli/references/node-capabilities.md +95 -0
  10. package/.agents/skills/uzoo-anime-cli/references/node-workflow.md +94 -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 +300 -0
  13. package/.agents/skills/uzoo-anime-cli/references/seedance-review-workflow.md +41 -0
  14. package/.agents/skills/uzoo-anime-cli/references/video-tools-workflow.md +71 -0
  15. package/CHANGELOG.md +92 -0
  16. package/LICENSE +21 -0
  17. package/README.md +242 -3
  18. package/assets/README.md +4 -0
  19. package/assets/favicon-uzoo.png +0 -0
  20. package/assets/logo-uzoo.svg +7 -0
  21. package/docs/CLI/345/275/223/345/211/215/350/214/203/345/233/264/344/270/216/346/234/254/345/234/260/346/211/223/345/214/205-20261103.md +134 -0
  22. package/docs/CLI/346/216/210/346/235/203/346/216/245/345/217/243/344/270/216/344/273/244/347/211/214/350/207/252/345/212/250/345/210/267/346/226/260/350/256/276/350/256/241.md +540 -0
  23. package/docs/CLI/347/274/272/345/217/243/346/240/270/345/257/271/344/270/216/346/224/266/345/260/276-20261105.md +65 -0
  24. package/docs/CLI/347/275/221/351/241/265/344/277/235/345/255/230/350/212/202/347/202/271/347/274/272/345/244/261/344/272/244/346/216/245.md +52 -0
  25. package/docs/M0-M5/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +89 -0
  26. package/docs/npm/347/272/277/344/270/212/345/217/221/345/270/203/344/270/216/345/256/211/350/243/205/344/275/277/347/224/250.md +151 -0
  27. package/docs//344/270/213/350/275/275/346/216/245/345/217/243/351/200/202/351/205/215/344/270/216/351/252/214/346/224/266.md +107 -0
  28. package/docs//344/270/232/345/212/241/350/203/275/345/212/233/350/241/245/345/205/250/344/270/216Skill/347/273/223/346/236/204/350/247/204/345/210/222.md +311 -0
  29. package/docs//345/205/250/345/261/200/351/231/204/344/273/266/350/265/204/346/272/220/345/234/260/345/235/200/344/270/216/346/250/241/345/236/213/350/257/273/345/217/226/345/267/256/345/274/202.md +47 -0
  30. package/docs//345/206/205/351/203/250Windows/345/256/211/350/243/205/344/270/216/345/215/207/347/272/247/351/252/214/346/224/266/346/270/205/345/215/225.md +34 -0
  31. package/docs//345/233/276/347/211/207/346/212/240/345/233/276/346/216/245/345/205/245/344/270/216/351/252/214/346/224/266-20261104.md +110 -0
  32. package/docs//345/274/200/345/217/221/350/256/241/345/210/222.md +127 -0
  33. package/docs//345/274/202/346/255/245/346/212/240/345/233/276CLI/346/235/203/351/231/220/344/272/244/346/216/245-20261105.md +50 -0
  34. package/docs//345/274/202/346/255/245/346/212/240/345/233/276/347/234/237/345/256/236/351/252/214/346/224/266-20261105.md +44 -0
  35. package/docs//346/226/260/344/274/232/350/257/235/344/273/273/345/212/241/344/272/244/344/273/243-/351/230/266/346/256/2652A.md +79 -0
  36. package/docs//346/226/260/351/203/250/347/275/262/347/224/273/345/270/203/345/206/262/347/252/201/351/252/214/346/224/266-20261102.md +61 -0
  37. package/docs//346/231/256/351/200/232/346/265/217/350/247/210/345/231/250/347/231/273/345/275/225.md +53 -0
  38. package/docs//346/255/243/345/274/217/347/253/231/345/210/207/346/215/242/344/270/216/351/252/214/346/224/266-20261104.md +90 -0
  39. package/docs//347/224/273/345/270/203CLI/344/270/213/344/270/200/351/230/266/346/256/265/345/274/200/345/217/221/350/256/241/345/210/222.md +136 -0
  40. package/docs//347/224/273/345/270/203CLI/351/235/236/350/275/254/347/273/230/345/256/214/345/226/204/350/256/241/345/210/222.md +183 -0
  41. package/docs//347/224/273/345/270/203/350/203/275/345/212/233/345/245/221/347/272/246/346/240/270/345/257/271.md +59 -0
  42. package/docs//347/234/237/345/256/236/344/270/232/345/212/241/346/216/245/345/205/245/344/270/216/346/265/213/350/257/225.md +1004 -0
  43. package/docs//347/264/240/346/235/220/345/205/245/345/272/223/344/270/216/347/224/273/345/270/203/344/277/235/345/255/230/351/227/256/351/242/230/345/256/232/344/275/215.md +52 -0
  44. package/docs//351/230/266/346/256/2653B/351/252/214/346/224/266/344/272/244/346/216/245.md +44 -0
  45. package/docs//351/230/266/346/256/265/344/270/200/351/252/214/346/224/266.md +60 -0
  46. package/docs//351/235/236/350/275/254/347/273/230/346/214/201/347/273/255/345/256/214/345/226/204/344/270/216/351/252/214/346/224/266-20261103.md +76 -0
  47. package/docs//351/235/236/350/275/254/347/273/230/347/254/254/344/272/214/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +39 -0
  48. package/docs//351/235/236/350/275/254/347/273/230/350/241/245/345/205/205/346/235/241/344/273/266/351/252/214/346/224/266-20261103.md +113 -0
  49. package/docs//351/235/236/350/275/254/347/273/230/351/246/226/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +71 -0
  50. package/examples/matting.json +5 -0
  51. package/examples/real-business/image-request.template.json +7 -0
  52. package/examples/real-business/node-patch.template.json +11 -0
  53. package/extensions/uzoo-cli-login/manifest.json +9 -0
  54. package/extensions/uzoo-cli-login/popup.css +1 -0
  55. package/extensions/uzoo-cli-login/popup.html +14 -0
  56. package/extensions/uzoo-cli-login/popup.js +65 -0
  57. package/package.json +56 -6
  58. package/scripts/check-package-resources.mjs +56 -0
  59. package/scripts/check-release.mjs +19 -0
  60. package/scripts/pack-local.mjs +33 -0
  61. package/scripts/release-policy.mjs +22 -0
  62. package/src/agreement-page.js +62 -0
  63. package/src/agreement.js +419 -0
  64. package/src/annotation-media.js +386 -0
  65. package/src/api.js +168 -0
  66. package/src/browser-login.js +13 -0
  67. package/src/callback-page.js +44 -0
  68. package/src/canvas.js +105 -0
  69. package/src/cli-authorization.js +122 -0
  70. package/src/cli.js +459 -0
  71. package/src/download-name.js +17 -0
  72. package/src/draft-finalize.js +229 -0
  73. package/src/generation-graph.js +124 -0
  74. package/src/generation-input.js +472 -0
  75. package/src/graph-disconnect.js +89 -0
  76. package/src/graph.js +431 -0
  77. package/src/group-edit.js +53 -0
  78. package/src/group-layout.js +113 -0
  79. package/src/group-membership.js +126 -0
  80. package/src/history-add.js +119 -0
  81. package/src/image-annotation.js +246 -0
  82. package/src/image-crop.js +283 -0
  83. package/src/image-matting.js +82 -0
  84. package/src/image-media.js +89 -0
  85. package/src/image-output.js +385 -0
  86. package/src/local-output.js +23 -0
  87. package/src/material-download.js +55 -0
  88. package/src/material-save.js +254 -0
  89. package/src/materials.js +147 -0
  90. package/src/matting-background.js +73 -0
  91. package/src/matting-jobs.js +63 -0
  92. package/src/matting-worker.js +49 -0
  93. package/src/matting.js +170 -0
  94. package/src/media-copy.js +53 -0
  95. package/src/media.js +343 -0
  96. package/src/model-options.js +76 -0
  97. package/src/multipart.js +28 -0
  98. package/src/node-copy.js +95 -0
  99. package/src/node-delete.js +49 -0
  100. package/src/node-history.js +231 -0
  101. package/src/normal-login.js +118 -0
  102. package/src/package-info.js +8 -0
  103. package/src/png-alpha.js +60 -0
  104. package/src/protocol.js +83 -0
  105. package/src/remake.js +738 -0
  106. package/src/repaint-archive.js +53 -0
  107. package/src/repaint-delivery.js +783 -0
  108. package/src/repaint-export.js +63 -0
  109. package/src/repaint-graph.js +214 -0
  110. package/src/repaint-history.js +267 -0
  111. package/src/repaint-materials.js +601 -0
  112. package/src/repaint-reverse.js +614 -0
  113. package/src/repaint-sequence.js +132 -0
  114. package/src/repaint-settings.js +576 -0
  115. package/src/repaint-video.js +798 -0
  116. package/src/repaint.js +326 -0
  117. package/src/resource.js +110 -0
  118. package/src/review.js +435 -0
  119. package/src/service.js +85 -0
  120. package/src/store.js +100 -0
  121. package/src/video-clip.js +381 -0
  122. package/src/video-frame.js +296 -0
  123. package/src/workflows.js +214 -0
@@ -0,0 +1,419 @@
1
+ import crypto from 'node:crypto'
2
+ import http from 'node:http'
3
+ import { request, cancellation } from './api.js'
4
+ import { unwrap } from './protocol.js'
5
+ import { openLoginBrowser } from './normal-login.js'
6
+ import { agreementPage } from './agreement-page.js'
7
+
8
+ export const AGREEMENT_LABEL = '《统一协议》'
9
+ export const AGREEMENT_TITLE = '《Seedance2.0模型使用承诺书》'
10
+ export function agreementError(code, message, protocol) {
11
+ const error = new Error(message)
12
+ Object.assign(error, { code, apiCode: 4401, status: 'awaiting_agreement', protocol })
13
+ return error
14
+ }
15
+ export function validateProtocol(value) {
16
+ if (
17
+ !value ||
18
+ typeof value.required !== 'boolean' ||
19
+ typeof value.agreed !== 'boolean' ||
20
+ typeof value.version !== 'string'
21
+ )
22
+ throw new Error('协议状态响应不完整,停止继续生成')
23
+ return {
24
+ required: value.required,
25
+ agreed: value.agreed,
26
+ version: value.version,
27
+ label: AGREEMENT_LABEL,
28
+ document_title: AGREEMENT_TITLE,
29
+ message: String(value.message || '请阅读并确认协议后继续').slice(0, 2000)
30
+ }
31
+ }
32
+ export async function readProtocol(client) {
33
+ return validateProtocol((await client.raw('/video/protocol-agreement')).protocol)
34
+ }
35
+ async function publicText(url, timeout) {
36
+ const response = await request(
37
+ url,
38
+ { headers: { Accept: 'text/html, application/json' } },
39
+ timeout
40
+ )
41
+ if (!response.ok) {
42
+ await response.body?.cancel()
43
+ throw new Error('协议正文读取失败')
44
+ }
45
+ const chunks = []
46
+ let size = 0
47
+ for await (const chunk of response.body) {
48
+ size += chunk.length
49
+ if (size > 2 * 1024 * 1024) {
50
+ await response.body.cancel().catch(() => {})
51
+ throw new Error('协议正文超过读取限制')
52
+ }
53
+ chunks.push(chunk)
54
+ }
55
+ return {
56
+ text: Buffer.concat(chunks).toString('utf8'),
57
+ mime: response.headers.get('content-type') || ''
58
+ }
59
+ }
60
+ export async function readAgreementDocument(client) {
61
+ const base = new URL(client.baseUrl)
62
+ const list = await publicText(client.baseUrl + '/content', client.timeout)
63
+ const items = unwrap(JSON.parse(list.text)).items
64
+ if (!Array.isArray(items)) throw new Error('已发布协议目录无效')
65
+ const matches = items.filter(
66
+ (i) => i.type === 'terms_of_service' && /seedance/i.test(i.title || '')
67
+ )
68
+ const exact = matches.filter(
69
+ (i) => String(i.title).replace(/\s/g, '') === 'Seedance2.0模型使用承诺书'
70
+ )
71
+ const candidates = exact.length ? exact : matches
72
+ if (candidates.length !== 1) throw new Error('未找到唯一的 Seedance 模型使用承诺书,停止签署')
73
+ const item = candidates[0]
74
+ let html = typeof item.content === 'string' ? item.content : ''
75
+ let source = client.baseUrl + '/content'
76
+ if (!html.trim()) {
77
+ const url = new URL(item.content_url, client.baseUrl + '/')
78
+ const prefix = base.pathname.replace(/\/$/, '')
79
+ if (
80
+ url.origin !== base.origin ||
81
+ url.username ||
82
+ url.password ||
83
+ url.hash ||
84
+ !new RegExp(
85
+ '^' + prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/content/[0-9]+/body$'
86
+ ).test(url.pathname) ||
87
+ url.searchParams.get('kind') !== 'doc' ||
88
+ [...url.searchParams.keys()].some((k) => k !== 'kind')
89
+ )
90
+ throw new Error('协议正文地址不属于当前服务的公开内容接口')
91
+ source = url.href
92
+ const content = await publicText(source, client.timeout)
93
+ if (!/text\/html/i.test(content.mime)) throw new Error('协议正文不是 HTML')
94
+ html = content.text
95
+ }
96
+ if (!html.trim() || html.length > 2 * 1024 * 1024) throw new Error('协议正文为空或过大,不能确认')
97
+ return {
98
+ id: item.id,
99
+ title: String(item.title),
100
+ version: item.version,
101
+ source,
102
+ html,
103
+ digest: crypto
104
+ .createHash('sha256')
105
+ .update(JSON.stringify([item.id, item.title, item.version, html]))
106
+ .digest('hex')
107
+ }
108
+ }
109
+ const identity = (value) => {
110
+ const uid = String(value.uid ?? value.id ?? '')
111
+ if (!uid) throw new Error('无法核对协议签署账号')
112
+ return { uid, name: String(value.name || value.username || uid) }
113
+ }
114
+
115
+ export async function startAgreementBridge({
116
+ client,
117
+ protocol,
118
+ document,
119
+ account,
120
+ continuation,
121
+ timeout = 300000,
122
+ signal = cancellation.signal
123
+ }) {
124
+ const token = crypto.randomBytes(32).toString('base64url'),
125
+ csrf = crypto.randomBytes(32).toString('base64url'),
126
+ nonce = crypto.randomBytes(24).toString('base64url')
127
+ const pathname = '/agreement/' + token
128
+ let origin,
129
+ closed = false,
130
+ settled = false,
131
+ processing = false,
132
+ decisionSent = false,
133
+ timer,
134
+ resolveWait,
135
+ rejectWait
136
+ const wait = new Promise((resolve, reject) => {
137
+ resolveWait = resolve
138
+ rejectWait = reject
139
+ })
140
+ void wait.catch(() => {})
141
+ function fail(code, message, diagnostic) {
142
+ if (!settled) {
143
+ settled = true
144
+ const error=agreementError(code, message, protocol)
145
+ if(diagnostic){error.diagnostic=diagnostic;error.apiCode=diagnostic.api_code;error.requestId=diagnostic.request_id}
146
+ rejectWait(error)
147
+ }
148
+ }
149
+ function agreed(state) {
150
+ if (!settled) {
151
+ settled = true
152
+ resolveWait({
153
+ status: state.agreed ? 'agreed' : 'not_required',
154
+ protocol: state,
155
+ account: { uid: account.uid, name: account.name }
156
+ })
157
+ }
158
+ }
159
+ async function checkAccount() {
160
+ if (identity(await client.raw('/auth/me')).uid !== account.uid)
161
+ throw agreementError(
162
+ 'AGREEMENT_ACCOUNT_CHANGED',
163
+ 'CLI 账号已变化,未继续签署或生成',
164
+ protocol
165
+ )
166
+ }
167
+ async function freshState() {
168
+ await checkAccount()
169
+ const state = await readProtocol(client)
170
+ if (state.version !== protocol.version)
171
+ throw agreementError(
172
+ 'AGREEMENT_CHANGED',
173
+ '协议版本已更新,请重新打开并阅读当前版本后确认',
174
+ state
175
+ )
176
+ return state
177
+ }
178
+ async function freshDocument() {
179
+ const latest = await readAgreementDocument(client)
180
+ if (latest.digest !== document.digest)
181
+ throw agreementError('AGREEMENT_CHANGED', '协议正文已变化,请重新打开并阅读后确认', protocol)
182
+ }
183
+ const server = http.createServer(async (req, res) => {
184
+ res.setHeader('Cache-Control', 'no-store')
185
+ res.setHeader('Referrer-Policy', 'no-referrer')
186
+ res.setHeader('X-Content-Type-Options', 'nosniff')
187
+ const send = (status, data) => {
188
+ if (!res.writableEnded) {
189
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })
190
+ res.end(JSON.stringify(data))
191
+ }
192
+ }
193
+ if (closed || req.headers.host !== new URL(origin).host) {
194
+ send(403, { status: 'rejected', message: '本机确认地址不匹配' })
195
+ return
196
+ }
197
+ if (req.method === 'GET' && req.url === pathname) {
198
+ res.setHeader(
199
+ 'Content-Security-Policy',
200
+ `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; frame-src 'self'; connect-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'`
201
+ )
202
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
203
+ res.end(
204
+ agreementPage({
205
+ protocol,
206
+ document,
207
+ account: { ...account, origin: new URL(client.baseUrl).origin },
208
+ continuation,
209
+ actionPath: pathname + '/decision',
210
+ csrf,
211
+ nonce
212
+ })
213
+ )
214
+ return
215
+ }
216
+ if (req.method === 'GET' && req.url === pathname + '/status') {
217
+ if (settled) {
218
+ send(200, { status: 'cancelled', message: '本次页面已结束,请查看 CLI 状态' })
219
+ return
220
+ }
221
+ if (processing) {
222
+ send(200, { status: 'waiting', message: '正在确认' })
223
+ return
224
+ }
225
+ try {
226
+ const state = await freshState()
227
+ if (state.agreed || !state.required) {
228
+ if (state.required) await freshDocument()
229
+ res.once('finish', () => agreed(state))
230
+ send(200, {
231
+ status: state.agreed ? 'agreed' : 'not_required',
232
+ message: state.agreed
233
+ ? '当前账号已确认当前版本协议,CLI 将核验后继续'
234
+ : '当前服务不要求协议确认,未记录同意,CLI 将重新核验后继续'
235
+ })
236
+ } else send(200, { status: 'waiting', protocol: state })
237
+ } catch (error) {
238
+ if (error.code?.startsWith('AGREEMENT_')) {
239
+ send(409, { status: 'changed', message: error.message })
240
+ fail(error.code, error.message)
241
+ } else send(503, { status: 'waiting', message: '协议状态查询暂不可用,尚未继续生成' })
242
+ }
243
+ return
244
+ }
245
+ if (req.method !== 'POST' || req.url !== pathname + '/decision') {
246
+ send(404, { status: 'rejected', message: '页面不存在' })
247
+ return
248
+ }
249
+ if (
250
+ req.headers.origin !== origin ||
251
+ req.headers['x-uzoo-agreement'] !== csrf ||
252
+ !/^application\/json(?:;|$)/i.test(req.headers['content-type'] || '')
253
+ ) {
254
+ send(403, { status: 'rejected', message: '需要从本页明确确认' })
255
+ return
256
+ }
257
+ if (settled || processing || decisionSent) {
258
+ send(409, { status: 'waiting', message: '本次确认已处理或结果待核实,请勿重复点击' })
259
+ return
260
+ }
261
+ processing = true
262
+ try {
263
+ const chunks = []
264
+ let size = 0
265
+ for await (const chunk of req) {
266
+ size += chunk.length
267
+ if (size > 1024) {
268
+ send(413, { status: 'rejected', message: '确认请求过大' })
269
+ return
270
+ }
271
+ chunks.push(chunk)
272
+ }
273
+ let body
274
+ try {
275
+ body = JSON.parse(Buffer.concat(chunks).toString('utf8'))
276
+ } catch {
277
+ send(400, { status: 'rejected', message: '确认请求无效' })
278
+ return
279
+ }
280
+ if (!body || typeof body.agreed !== 'boolean' || Object.keys(body).length !== 1) {
281
+ send(400, { status: 'rejected', message: '缺少明确选择' })
282
+ return
283
+ }
284
+ if (!body.agreed) {
285
+ res.once('finish', () =>
286
+ fail(
287
+ 'AGREEMENT_DECLINED',
288
+ '用户未同意《统一协议》《Seedance2.0模型使用承诺书》,本次生成未继续'
289
+ )
290
+ )
291
+ send(200, { status: 'declined', message: '你选择了不同意,本次生成未继续' })
292
+ return
293
+ }
294
+ const state = await freshState()
295
+ if (state.required) await freshDocument()
296
+ if (!state.agreed && state.required) {
297
+ decisionSent = true
298
+ // This authenticated POST only occurs after the human's checkbox + click.
299
+ await client.raw(
300
+ '/video/protocol-agreement',
301
+ { method: 'POST', body: { agreed: true } },
302
+ { retry: false }
303
+ )
304
+ }
305
+ const verified = await freshState()
306
+ if (verified.required) await freshDocument()
307
+ if (verified.required && !verified.agreed) throw new Error('签署记录尚未确认')
308
+ res.once('finish', () => agreed(verified))
309
+ send(200, {
310
+ status: verified.agreed ? 'agreed' : 'not_required',
311
+ message: verified.agreed
312
+ ? '协议状态已核验,CLI 将检查原任务参数与费用后继续'
313
+ : '当前服务不要求协议确认,未记录同意,CLI 将重新核验后继续'
314
+ })
315
+ } catch (error) {
316
+ if (error.code === 'API_ERROR' && (error.apiCode === 2001 || [401,403].includes(error.status))) {
317
+ const requestId=/^[a-zA-Z0-9_-]{1,120}$/.test(error.requestId||'')?error.requestId:undefined
318
+ const message='协议确认被服务端拒绝(HTTP '+error.status+(error.apiCode?',code '+error.apiCode:'')+(requestId?',request_id '+requestId:'')+');尚未核验签署成功,未继续生成。请核对账号和接口权限。'
319
+ res.once('finish',()=>fail('AGREEMENT_REJECTED',message,{http_status:error.status,...(error.apiCode?{api_code:error.apiCode}:{}),...(requestId?{request_id:requestId}:{})}))
320
+ send(403,{status:'rejected',message})
321
+ } else
322
+ if (error.code?.startsWith('AGREEMENT_')) {
323
+ send(409, { status: 'changed', message: error.message })
324
+ fail(error.code, error.message)
325
+ } else
326
+ send(503, {
327
+ status: 'waiting',
328
+ message: '协议签署结果尚未确认,CLI 会只读查询;不会重复提交同意或继续生成'
329
+ })
330
+ } finally {
331
+ processing = false
332
+ }
333
+ })
334
+ server.requestTimeout = 10000
335
+ server.headersTimeout = 10000
336
+ await new Promise((resolve, reject) => {
337
+ server.once('error', reject)
338
+ server.listen(0, '127.0.0.1', resolve)
339
+ })
340
+ origin = `http://127.0.0.1:${server.address().port}`
341
+ const abort = () => fail('AGREEMENT_CANCELLED', '协议确认已取消,原任务未继续')
342
+ signal.addEventListener('abort', abort, { once: true })
343
+ if (signal.aborted) abort()
344
+ timer = setTimeout(
345
+ () =>
346
+ fail(
347
+ 'AGREEMENT_WAIT_TIMEOUT',
348
+ '等待用户确认协议超时,原任务保持等待签署,可恢复原 operation'
349
+ ),
350
+ timeout
351
+ )
352
+ return {
353
+ url: origin + pathname,
354
+ wait,
355
+ close: async () => {
356
+ if (closed) return
357
+ closed = true
358
+ clearTimeout(timer)
359
+ signal.removeEventListener('abort', abort)
360
+ server.closeAllConnections()
361
+ await new Promise((resolve) => server.close(resolve))
362
+ }
363
+ }
364
+ }
365
+
366
+ export async function browserAgreement(
367
+ client,
368
+ {
369
+ browser = 'default',
370
+ timeout = 300000,
371
+ openUrl = openLoginBrowser,
372
+ onReady = () => {},
373
+ onOpenError = () => {},
374
+ continuation,
375
+ signal = cancellation.signal
376
+ } = {}
377
+ ) {
378
+ if (
379
+ !['default', 'msedge', 'chrome'].includes(browser) ||
380
+ !Number.isSafeInteger(timeout) ||
381
+ timeout < 1 ||
382
+ timeout > 1800000
383
+ )
384
+ throw new Error('协议浏览器或等待时间无效')
385
+ const account = identity(await client.raw('/auth/me'))
386
+ const protocol = await readProtocol(client)
387
+ if (!protocol.required || protocol.agreed)
388
+ return { status: protocol.agreed ? 'already_agreed' : 'not_required', protocol, account }
389
+ let document
390
+ try {
391
+ document = await readAgreementDocument(client)
392
+ } catch {
393
+ throw agreementError(
394
+ 'AGREEMENT_DOCUMENT_UNAVAILABLE',
395
+ '《统一协议》《Seedance2.0模型使用承诺书》正文暂不可用,未签署也未继续生成',
396
+ protocol
397
+ )
398
+ }
399
+ const bridge = await startAgreementBridge({
400
+ client,
401
+ protocol,
402
+ document,
403
+ account,
404
+ continuation,
405
+ timeout,
406
+ signal
407
+ })
408
+ try {
409
+ onReady(bridge.url, { protocol, account, document: { title: document.title, id: document.id } })
410
+ try {
411
+ await openUrl(bridge.url, browser)
412
+ } catch (error) {
413
+ onOpenError(error.message)
414
+ }
415
+ return await bridge.wait
416
+ } finally {
417
+ await bridge.close()
418
+ }
419
+ }