openclaw-zero-video 0.1.4 → 0.1.7
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/README.md +2 -2
- package/dist/config.js +1 -1
- package/dist/index.js +52 -17
- package/dist/jobs.d.ts +2 -0
- package/dist/jobs.js +62 -8
- package/dist/providers.js +12 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +46 -9
- package/dist/types.d.ts +5 -0
- package/openclaw.plugin.json +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ The plugin is a thin CDP driver plus a durable job store. It does not use provid
|
|
|
15
15
|
| `cancel` | Best-effort cancel; reports whether the credit may already be spent |
|
|
16
16
|
| `providers` | `list`, `add`, `configure`, `remove`, `test` provider profiles |
|
|
17
17
|
|
|
18
|
-
Jobs are asynchronous. A render can take 1–15 minutes, so `submit` returns immediately and `status` is polled. `waitSec` (0–
|
|
18
|
+
Jobs are asynchronous. A render can take 1–15 minutes, so `submit` returns immediately and `status` is polled. `waitSec` (0–20) lets one status call wait inside the tool. `providers list` returns a compact summary by default; pass `includeDetails: true` for selectors. `status` and `submit` return compact jobs by default; pass `verbose: true` for the full record. Tool calls stay under the Gateway's drain budget.
|
|
19
19
|
|
|
20
20
|
## Provider profiles
|
|
21
21
|
|
|
@@ -120,7 +120,7 @@ The ledger uses balance delta, not a calendar:
|
|
|
120
120
|
| `mediaDir` | `~/.openclaw/media/zero-video` |
|
|
121
121
|
| `maxParallel` | `2` |
|
|
122
122
|
| `jobTimeoutMs` | `1200000` |
|
|
123
|
-
| `downloadTimeoutMs` | `
|
|
123
|
+
| `downloadTimeoutMs` | `20000` |
|
|
124
124
|
| `maxDownloadBytes` | `52428800` |
|
|
125
125
|
| `maxPromptChars` | `2000` |
|
|
126
126
|
| `sshHost` | `192.168.1.12` |
|
package/dist/config.js
CHANGED
|
@@ -79,7 +79,7 @@ export function parseConfig(raw, home = homedir()) {
|
|
|
79
79
|
mediaDir: expandHome(text(input.mediaDir, '~/.openclaw/media/zero-video'), home),
|
|
80
80
|
maxParallel: integer(input.maxParallel, 2, 1, 3),
|
|
81
81
|
jobTimeoutMs: integer(input.jobTimeoutMs, 1_200_000, 60_000, 7_200_000),
|
|
82
|
-
downloadTimeoutMs: integer(input.downloadTimeoutMs,
|
|
82
|
+
downloadTimeoutMs: integer(input.downloadTimeoutMs, 20_000, 10_000, 25_000),
|
|
83
83
|
maxDownloadBytes: integer(input.maxDownloadBytes, 52_428_800, 1_048_576, 104_857_600),
|
|
84
84
|
maxPromptChars: integer(input.maxPromptChars, 2000, 1, 8000),
|
|
85
85
|
sshHost: text(input.sshHost, '192.168.1.12'),
|
package/dist/index.js
CHANGED
|
@@ -21,8 +21,23 @@ export default definePluginEntry({
|
|
|
21
21
|
const action = args.providersAction;
|
|
22
22
|
const id = args.providerId ?? (typeof args.provider?.id === 'string' ? args.provider.id : undefined);
|
|
23
23
|
switch (action) {
|
|
24
|
-
case 'list':
|
|
25
|
-
|
|
24
|
+
case 'list': {
|
|
25
|
+
const rows = await runner.providerView();
|
|
26
|
+
if (args.includeDetails)
|
|
27
|
+
return { ok: true, providers: rows, active: runner.activeSummary() };
|
|
28
|
+
return {
|
|
29
|
+
ok: true,
|
|
30
|
+
providers: rows.map((p) => ({
|
|
31
|
+
id: p.id,
|
|
32
|
+
label: p.label,
|
|
33
|
+
enabled: p.enabled,
|
|
34
|
+
verifiedAt: p.verifiedAt,
|
|
35
|
+
ledger: p.ledger,
|
|
36
|
+
activeJobId: p.activeJobId,
|
|
37
|
+
})),
|
|
38
|
+
active: runner.activeSummary(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
26
41
|
case 'add': {
|
|
27
42
|
if (!args.provider)
|
|
28
43
|
throw new ToolError('INVALID_REQUEST');
|
|
@@ -72,8 +87,10 @@ export default definePluginEntry({
|
|
|
72
87
|
aspect: { type: 'string', enum: ['16:9', '9:16', '1:1', '4:3', '3:4'] },
|
|
73
88
|
imagePath: { type: 'string' },
|
|
74
89
|
jobId: { type: 'string' },
|
|
75
|
-
waitSec: { type: 'integer', minimum: 0, maximum:
|
|
90
|
+
waitSec: { type: 'integer', minimum: 0, maximum: 20 },
|
|
76
91
|
retryDownload: { type: 'boolean' },
|
|
92
|
+
includeDetails: { type: 'boolean' },
|
|
93
|
+
verbose: { type: 'boolean' },
|
|
77
94
|
providersAction: { type: 'string', enum: ['list', 'add', 'configure', 'remove', 'test'] },
|
|
78
95
|
provider: { type: 'object' },
|
|
79
96
|
providerId: { type: 'string' },
|
|
@@ -92,30 +109,48 @@ export default definePluginEntry({
|
|
|
92
109
|
case 'submit': {
|
|
93
110
|
if (!args.site || !args.prompt)
|
|
94
111
|
throw new ToolError('INVALID_REQUEST');
|
|
112
|
+
const job = await runner.submit({
|
|
113
|
+
site: args.site,
|
|
114
|
+
prompt: args.prompt,
|
|
115
|
+
negativePrompt: args.negativePrompt,
|
|
116
|
+
model: args.model,
|
|
117
|
+
durationSec: args.durationSec,
|
|
118
|
+
aspect: args.aspect,
|
|
119
|
+
imagePath: args.imagePath,
|
|
120
|
+
}, signal);
|
|
95
121
|
result = {
|
|
96
122
|
ok: true,
|
|
97
|
-
job:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
imagePath: args.imagePath,
|
|
105
|
-
}, signal),
|
|
123
|
+
job: args.verbose ? job : {
|
|
124
|
+
id: job.id,
|
|
125
|
+
site: job.site,
|
|
126
|
+
state: job.state,
|
|
127
|
+
submittedAt: job.submittedAt,
|
|
128
|
+
creditsBefore: job.creditsBefore,
|
|
129
|
+
},
|
|
106
130
|
};
|
|
107
131
|
break;
|
|
108
132
|
}
|
|
109
133
|
case 'status': {
|
|
110
134
|
if (!args.jobId)
|
|
111
135
|
throw new ToolError('INVALID_REQUEST');
|
|
136
|
+
const job = await runner.status({
|
|
137
|
+
jobId: args.jobId,
|
|
138
|
+
waitSec: args.waitSec,
|
|
139
|
+
retryDownload: args.retryDownload,
|
|
140
|
+
}, signal);
|
|
112
141
|
result = {
|
|
113
142
|
ok: true,
|
|
114
|
-
job:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
job: args.verbose ? job : {
|
|
144
|
+
id: job.id,
|
|
145
|
+
site: job.site,
|
|
146
|
+
state: job.state,
|
|
147
|
+
errorCode: job.errorCode,
|
|
148
|
+
artifact: job.artifact,
|
|
149
|
+
creditsBefore: job.creditsBefore,
|
|
150
|
+
creditsAfter: job.creditsAfter,
|
|
151
|
+
submittedAt: job.submittedAt,
|
|
152
|
+
completedAt: job.completedAt,
|
|
153
|
+
},
|
|
119
154
|
};
|
|
120
155
|
break;
|
|
121
156
|
}
|
package/dist/jobs.d.ts
CHANGED
package/dist/jobs.js
CHANGED
|
@@ -419,6 +419,7 @@ export class Runner {
|
|
|
419
419
|
try {
|
|
420
420
|
job = await this.jobs.update(job.id, { state: 'opening' });
|
|
421
421
|
page = await this.driver.open(provider.url, signal);
|
|
422
|
+
await page.bringToFront();
|
|
422
423
|
const promptReady = await page.waitFor(provider.promptInput, 20_000, signal);
|
|
423
424
|
if (!promptReady) {
|
|
424
425
|
if (provider.loginCheck && !(await page.isVisible(provider.loginCheck).catch(() => false))) {
|
|
@@ -431,6 +432,9 @@ export class Runner {
|
|
|
431
432
|
}
|
|
432
433
|
// Let the SPA finish mounting the editor and submit handlers before typing.
|
|
433
434
|
await sleep(provider.readinessMs ?? 3000, signal);
|
|
435
|
+
const blocked = await this.readBlockedText(page, provider);
|
|
436
|
+
if (blocked)
|
|
437
|
+
throw new ToolError('PROVIDER_BLOCKED', blocked);
|
|
434
438
|
if (request.imagePath) {
|
|
435
439
|
if (!provider.imageInput)
|
|
436
440
|
throw new ToolError('IMAGE_INPUT_NOT_CONFIGURED');
|
|
@@ -440,7 +444,8 @@ export class Runner {
|
|
|
440
444
|
await page.setFileInput(provider.imageInput, remote);
|
|
441
445
|
await sleep(750, signal);
|
|
442
446
|
}
|
|
443
|
-
|
|
447
|
+
const promptToFill = provider.promptPrefix ? `${provider.promptPrefix} ${prompt}` : prompt;
|
|
448
|
+
await page.fill(provider.promptInput, promptToFill);
|
|
444
449
|
await sleep(300, signal);
|
|
445
450
|
await this.applyOptions(page, provider, request);
|
|
446
451
|
const balance = await this.readBalance(page, provider);
|
|
@@ -449,9 +454,18 @@ export class Runner {
|
|
|
449
454
|
const enabled = await this.waitEnabled(page, provider.submitButton, 8_000, signal);
|
|
450
455
|
if (!enabled)
|
|
451
456
|
throw new ToolError('SUBMIT_DISABLED');
|
|
452
|
-
if (!(await page.
|
|
457
|
+
if (!(await page.realClick(provider.submitButton)))
|
|
453
458
|
throw new ToolError('SUBMIT_DISABLED');
|
|
454
459
|
await sleep(1000, signal);
|
|
460
|
+
if (provider.adapter === 'flow') {
|
|
461
|
+
const confirmSelector = provider.confirmSelector ?? 'div[role="radio"][aria-label="Approve"]';
|
|
462
|
+
const confirmed = await page.waitFor(confirmSelector, 45_000, signal);
|
|
463
|
+
if (!confirmed)
|
|
464
|
+
throw new ToolError('FLOW_CONFIRMATION_TIMEOUT');
|
|
465
|
+
if (!(await page.realClick(confirmSelector)))
|
|
466
|
+
throw new ToolError('FLOW_CONFIRMATION_FAILED');
|
|
467
|
+
await sleep(1500, signal);
|
|
468
|
+
}
|
|
455
469
|
if (provider.errorText && (await page.isVisible(provider.errorText).catch(() => false))) {
|
|
456
470
|
const message = (await page.text(provider.errorText).catch(() => '')).slice(0, 500);
|
|
457
471
|
throw new ToolError(classifySubmitError(message), message || 'SUBMIT_FAILED');
|
|
@@ -482,8 +496,12 @@ export class Runner {
|
|
|
482
496
|
}
|
|
483
497
|
async status(request, signal) {
|
|
484
498
|
let job = await this.jobs.get(assertJobId(request.jobId));
|
|
485
|
-
if (isTerminal(job.state))
|
|
499
|
+
if (isTerminal(job.state)) {
|
|
500
|
+
const rt = this.active.get(job.site);
|
|
501
|
+
if (rt?.jobId === job.id)
|
|
502
|
+
this.active.delete(job.site);
|
|
486
503
|
return job;
|
|
504
|
+
}
|
|
487
505
|
const provider = await this.providers.get(job.site);
|
|
488
506
|
const runtime = this.active.get(provider.id);
|
|
489
507
|
if (!runtime || runtime.jobId !== job.id) {
|
|
@@ -496,10 +514,30 @@ export class Runner {
|
|
|
496
514
|
if (request.retryDownload) {
|
|
497
515
|
return this.download(job, provider, runtime.page);
|
|
498
516
|
}
|
|
499
|
-
const deadline = Date.now() + Math.min(Math.max(request.waitSec ?? 0, 0),
|
|
517
|
+
const deadline = Date.now() + Math.min(Math.max(request.waitSec ?? 0, 0), 20) * 1000;
|
|
500
518
|
do {
|
|
501
519
|
if (signal?.aborted)
|
|
502
520
|
throw new ToolError('CANCELLED');
|
|
521
|
+
const blocked = await this.readBlockedText(runtime.page, provider);
|
|
522
|
+
if (blocked) {
|
|
523
|
+
this.active.delete(provider.id);
|
|
524
|
+
return this.jobs.update(job.id, {
|
|
525
|
+
state: 'failed',
|
|
526
|
+
errorCode: 'PROVIDER_BLOCKED',
|
|
527
|
+
errorMessage: blocked,
|
|
528
|
+
completedAt: nowIso(),
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const failure = await this.readTextMatch(runtime.page, provider.failureText);
|
|
532
|
+
if (failure) {
|
|
533
|
+
this.active.delete(provider.id);
|
|
534
|
+
return this.jobs.update(job.id, {
|
|
535
|
+
state: 'failed',
|
|
536
|
+
errorCode: 'PROVIDER_FAILED',
|
|
537
|
+
errorMessage: failure,
|
|
538
|
+
completedAt: nowIso(),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
503
541
|
if (provider.errorText) {
|
|
504
542
|
const visible = await runtime.page.isVisible(provider.errorText).catch(() => false);
|
|
505
543
|
if (visible) {
|
|
@@ -550,7 +588,7 @@ export class Runner {
|
|
|
550
588
|
const runtime = this.active.get(provider.id);
|
|
551
589
|
if (runtime && runtime.jobId === job.id) {
|
|
552
590
|
if (provider.cancelButton)
|
|
553
|
-
await runtime.page.
|
|
591
|
+
await runtime.page.realClick(provider.cancelButton).catch(() => false);
|
|
554
592
|
await runtime.page.close().catch(() => undefined);
|
|
555
593
|
this.active.delete(provider.id);
|
|
556
594
|
}
|
|
@@ -597,12 +635,27 @@ export class Runner {
|
|
|
597
635
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
598
636
|
}`, { selector: option.selector, value: option.value });
|
|
599
637
|
}
|
|
600
|
-
else if (!(await page.
|
|
638
|
+
else if (!(await page.realClick(option.selector))) {
|
|
601
639
|
throw new ToolError('SELECTOR_CHANGED');
|
|
602
640
|
}
|
|
603
641
|
await sleep(300);
|
|
604
642
|
}
|
|
605
643
|
}
|
|
644
|
+
async readTextMatch(page, pattern) {
|
|
645
|
+
if (!pattern)
|
|
646
|
+
return null;
|
|
647
|
+
const text = await page.text('body').catch(() => '');
|
|
648
|
+
try {
|
|
649
|
+
const match = text.match(new RegExp(pattern, 'i'));
|
|
650
|
+
return match ? match[0].slice(0, 200) : null;
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async readBlockedText(page, provider) {
|
|
657
|
+
return this.readTextMatch(page, provider.blockedText);
|
|
658
|
+
}
|
|
606
659
|
async readBalance(page, provider) {
|
|
607
660
|
if (!provider.creditBalance)
|
|
608
661
|
return null;
|
|
@@ -646,12 +699,13 @@ export class Runner {
|
|
|
646
699
|
const remoteDir = `${this.config.cosmosDownloadsDir}/${job.id}`;
|
|
647
700
|
try {
|
|
648
701
|
await this.ssh.mkdir(remoteDir);
|
|
702
|
+
await page.bringToFront();
|
|
649
703
|
await page.setDownloadBehavior(remoteDir);
|
|
650
|
-
if (!(await page.
|
|
704
|
+
if (!(await page.realClick(provider.downloadButton)))
|
|
651
705
|
throw new ToolError('DOWNLOAD_FAILED');
|
|
652
706
|
if (provider.downloadConfirm) {
|
|
653
707
|
await sleep(700);
|
|
654
|
-
await page.
|
|
708
|
+
await page.realClick(provider.downloadConfirm);
|
|
655
709
|
}
|
|
656
710
|
const remoteName = await this.waitForRemoteFile(remoteDir);
|
|
657
711
|
const tempDir = path.join(this.config.stateDir, 'tmp');
|
package/dist/providers.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ToolError } from './config.js';
|
|
|
4
4
|
const PROVIDER_KEYS = new Set([
|
|
5
5
|
'id', 'label', 'url', 'enabled', 'promptInput', 'submitButton', 'downloadButton',
|
|
6
6
|
'downloadConfirm', 'loginCheck', 'busyIndicator', 'readyIndicator', 'errorText',
|
|
7
|
+
'blockedText', 'adapter', 'confirmSelector', 'failureText', 'promptPrefix',
|
|
7
8
|
'creditBalance', 'creditCost', 'dailyCap', 'timeoutMs', 'readinessMs', 'options', 'imageInput',
|
|
8
9
|
'cancelButton', 'libraryUrl', 'libraryItem', 'notes', 'createdAt', 'updatedAt',
|
|
9
10
|
'verifiedAt',
|
|
@@ -111,6 +112,17 @@ export function normalizeProvider(input, existing, touch = true) {
|
|
|
111
112
|
busyIndicator: optionalSelector(merged.busyIndicator, 'busyIndicator'),
|
|
112
113
|
readyIndicator: optionalSelector(merged.readyIndicator, 'readyIndicator'),
|
|
113
114
|
errorText: optionalSelector(merged.errorText, 'errorText'),
|
|
115
|
+
blockedText: optionalString(merged.blockedText, 'blockedText', 500),
|
|
116
|
+
adapter: (() => {
|
|
117
|
+
if (merged.adapter === undefined)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (merged.adapter === 'flow')
|
|
120
|
+
return 'flow';
|
|
121
|
+
throw new ToolError('INVALID_PROVIDER');
|
|
122
|
+
})(),
|
|
123
|
+
confirmSelector: optionalSelector(merged.confirmSelector, 'confirmSelector'),
|
|
124
|
+
failureText: optionalString(merged.failureText, 'failureText', 500),
|
|
125
|
+
promptPrefix: optionalString(merged.promptPrefix, 'promptPrefix', 500),
|
|
114
126
|
creditBalance: optionalSelector(merged.creditBalance, 'creditBalance'),
|
|
115
127
|
creditCost: numberValue(merged.creditCost, 1, 0, 10_000),
|
|
116
128
|
dailyCap: numberValue(merged.dailyCap, 6, 0, 10_000, true),
|
package/dist/transport.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface Page {
|
|
|
6
6
|
enabled(selector: string): Promise<boolean>;
|
|
7
7
|
text(selector: string): Promise<string>;
|
|
8
8
|
click(selector: string): Promise<boolean>;
|
|
9
|
+
realClick(selector: string): Promise<boolean>;
|
|
10
|
+
bringToFront(): Promise<void>;
|
|
9
11
|
fill(selector: string, text: string): Promise<void>;
|
|
10
12
|
setFileInput(selector: string, filePath: string): Promise<void>;
|
|
11
13
|
setDownloadBehavior(downloadPath: string): Promise<void>;
|
package/dist/transport.js
CHANGED
|
@@ -142,19 +142,56 @@ export class CdpDriver {
|
|
|
142
142
|
return true;
|
|
143
143
|
}`, selector);
|
|
144
144
|
},
|
|
145
|
+
async realClick(selector) {
|
|
146
|
+
const rect = await evaluate(`(s) => {
|
|
147
|
+
const el = document.querySelector(s);
|
|
148
|
+
if (!el) return null;
|
|
149
|
+
const r = el.getBoundingClientRect();
|
|
150
|
+
const st = getComputedStyle(el);
|
|
151
|
+
return { x: r.x, y: r.y, w: r.width, h: r.height,
|
|
152
|
+
visible: r.width > 1 && r.height > 1 && st.display !== 'none' && st.visibility !== 'hidden' && Number(st.opacity || '1') > 0.01 };
|
|
153
|
+
}`, selector);
|
|
154
|
+
if (!rect?.visible)
|
|
155
|
+
return false;
|
|
156
|
+
const x = rect.x + rect.w / 2;
|
|
157
|
+
const y = rect.y + rect.h / 2;
|
|
158
|
+
await command('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
|
|
159
|
+
await command('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }, sessionId);
|
|
160
|
+
await command('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 }, sessionId);
|
|
161
|
+
return true;
|
|
162
|
+
},
|
|
163
|
+
async bringToFront() {
|
|
164
|
+
await command('Page.bringToFront', {}, sessionId);
|
|
165
|
+
},
|
|
145
166
|
async fill(selector, value) {
|
|
146
|
-
await evaluate(`(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
const isValue = await evaluate(`(s) => { const el = document.querySelector(s); return !!el && 'value' in el; }`, selector);
|
|
168
|
+
if (isValue) {
|
|
169
|
+
await evaluate(`(a) => {
|
|
170
|
+
const el = document.querySelector(a.selector);
|
|
171
|
+
if (!el) throw new Error('INPUT_NOT_FOUND');
|
|
172
|
+
el.focus();
|
|
151
173
|
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), 'value');
|
|
152
174
|
descriptor?.set?.call(el, a.text);
|
|
153
175
|
el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: a.text }));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
}`, { selector, text: value });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
await evaluate(`(s) => {
|
|
180
|
+
const el = document.querySelector(s);
|
|
181
|
+
if (!el) throw new Error('INPUT_NOT_FOUND');
|
|
182
|
+
el.focus();
|
|
183
|
+
const selection = window.getSelection();
|
|
184
|
+
const range = document.createRange();
|
|
185
|
+
range.selectNodeContents(el);
|
|
186
|
+
selection?.removeAllRanges();
|
|
187
|
+
selection?.addRange(range);
|
|
188
|
+
}`, selector);
|
|
189
|
+
try {
|
|
190
|
+
await command('Input.insertText', { text: value }, sessionId);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
await evaluate(`(a) => document.execCommand('insertText', false, a.text)`, { text: value });
|
|
194
|
+
}
|
|
158
195
|
},
|
|
159
196
|
async setFileInput(selector, filePath) {
|
|
160
197
|
await command('DOM.enable', {}, sessionId);
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface ProviderSpec {
|
|
|
19
19
|
busyIndicator?: string;
|
|
20
20
|
readyIndicator?: string;
|
|
21
21
|
errorText?: string;
|
|
22
|
+
blockedText?: string;
|
|
23
|
+
adapter?: 'flow';
|
|
24
|
+
confirmSelector?: string;
|
|
25
|
+
failureText?: string;
|
|
26
|
+
promptPrefix?: string;
|
|
22
27
|
creditBalance?: string;
|
|
23
28
|
creditCost: number;
|
|
24
29
|
dailyCap: number;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "zero-video",
|
|
3
3
|
"name": "Zero Video",
|
|
4
4
|
"description": "Generate videos through signed-in consumer AI video sites, with runtime-managed provider profiles.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.7",
|
|
6
6
|
"providers": [],
|
|
7
7
|
"contracts": {
|
|
8
8
|
"tools": [
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"downloadTimeoutMs": {
|
|
52
52
|
"type": "integer",
|
|
53
53
|
"minimum": 10000,
|
|
54
|
-
"maximum":
|
|
55
|
-
"default":
|
|
54
|
+
"maximum": 25000,
|
|
55
|
+
"default": 20000
|
|
56
56
|
},
|
|
57
57
|
"maxDownloadBytes": {
|
|
58
58
|
"type": "integer",
|