@renoise/video-maker 0.1.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.
- package/.claude-plugin/plugin.json +5 -0
- package/README.md +50 -0
- package/hooks/hooks.json +16 -0
- package/hooks/session-start.sh +17 -0
- package/lib/gemini.ts +49 -0
- package/package.json +22 -0
- package/skills/director/SKILL.md +272 -0
- package/skills/director/references/narrative-pacing.md +257 -0
- package/skills/director/references/style-library.md +179 -0
- package/skills/product-sheet-generate/SKILL.md +75 -0
- package/skills/renoise-gen/SKILL.md +362 -0
- package/skills/renoise-gen/references/api-endpoints.md +138 -0
- package/skills/renoise-gen/references/video-capabilities.md +524 -0
- package/skills/renoise-gen/renoise-cli.mjs +723 -0
- package/skills/scene-generate/SKILL.md +52 -0
- package/skills/short-film-editor/SKILL.md +479 -0
- package/skills/short-film-editor/examples/mystery-package-4shot.md +260 -0
- package/skills/short-film-editor/references/continuity-guide.md +170 -0
- package/skills/short-film-editor/scripts/analyze-beats.py +271 -0
- package/skills/short-film-editor/scripts/batch-generate.sh +150 -0
- package/skills/short-film-editor/scripts/generate-storyboard-html.ts +714 -0
- package/skills/short-film-editor/scripts/split-grid.sh +70 -0
- package/skills/tiktok-content-maker/SKILL.md +143 -0
- package/skills/tiktok-content-maker/examples/dress-demo.md +86 -0
- package/skills/tiktok-content-maker/references/ecom-prompt-guide.md +261 -0
- package/skills/tiktok-content-maker/scripts/analyze-images.ts +122 -0
- package/skills/video-download/SKILL.md +161 -0
- package/skills/video-download/scripts/download-video.sh +91 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var ApiError = class extends Error {
|
|
5
|
+
constructor(status, body, message) {
|
|
6
|
+
super(message || `API Error ${status}: ${JSON.stringify(body)}`);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
this.name = "ApiError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var AuthError = class extends ApiError {
|
|
13
|
+
constructor(body) {
|
|
14
|
+
super(401, body, "Authentication failed \u2014 check your API key");
|
|
15
|
+
this.name = "AuthError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var InsufficientCreditError = class extends ApiError {
|
|
19
|
+
available;
|
|
20
|
+
required;
|
|
21
|
+
constructor(body) {
|
|
22
|
+
super(402, body, `Insufficient credits: need ${body.required}, have ${body.available}`);
|
|
23
|
+
this.name = "InsufficientCreditError";
|
|
24
|
+
this.available = body.available ?? 0;
|
|
25
|
+
this.required = body.required ?? 0;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/client.ts
|
|
30
|
+
var RenoiseClient = class {
|
|
31
|
+
baseUrl;
|
|
32
|
+
apiKey;
|
|
33
|
+
authToken;
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
36
|
+
this.apiKey = config.apiKey;
|
|
37
|
+
this.authToken = config.authToken;
|
|
38
|
+
}
|
|
39
|
+
buildAuthHeaders() {
|
|
40
|
+
const headers = {};
|
|
41
|
+
if (this.apiKey) headers["X-API-Key"] = this.apiKey;
|
|
42
|
+
if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
// ---- HTTP ----
|
|
46
|
+
async request(method, path, body) {
|
|
47
|
+
const url = `${this.baseUrl}${path}`;
|
|
48
|
+
const headers = this.buildAuthHeaders();
|
|
49
|
+
if (body) headers["Content-Type"] = "application/json";
|
|
50
|
+
const resp = await fetch(url, {
|
|
51
|
+
method,
|
|
52
|
+
headers,
|
|
53
|
+
body: body ? JSON.stringify(body) : void 0
|
|
54
|
+
});
|
|
55
|
+
if (resp.status === 401) throw new AuthError(await resp.json().catch(() => ({})));
|
|
56
|
+
if (resp.status === 402) throw new InsufficientCreditError(await resp.json().catch(() => ({})));
|
|
57
|
+
const data = await resp.json().catch(() => ({}));
|
|
58
|
+
if (!resp.ok) throw new ApiError(resp.status, data, data.error);
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
// ---- Credit ----
|
|
62
|
+
async getMe() {
|
|
63
|
+
return this.request("GET", "/me");
|
|
64
|
+
}
|
|
65
|
+
async estimateCost(params) {
|
|
66
|
+
const qs = new URLSearchParams();
|
|
67
|
+
if (params.model) qs.set("model", params.model);
|
|
68
|
+
if (params.duration) qs.set("duration", String(params.duration));
|
|
69
|
+
if (params.hasVideoRef) qs.set("hasVideoRef", "1");
|
|
70
|
+
return this.request("GET", `/credit/estimate?${qs}`);
|
|
71
|
+
}
|
|
72
|
+
async getCreditHistory(limit = 50, offset = 0) {
|
|
73
|
+
return this.request("GET", `/credit/history?limit=${limit}&offset=${offset}`);
|
|
74
|
+
}
|
|
75
|
+
// ---- Task ----
|
|
76
|
+
async createTask(params) {
|
|
77
|
+
return this.request("POST", "/tasks", params);
|
|
78
|
+
}
|
|
79
|
+
async listTasks(params = {}) {
|
|
80
|
+
const qs = new URLSearchParams();
|
|
81
|
+
if (params.status) qs.set("status", params.status);
|
|
82
|
+
if (params.tag) qs.set("tag", params.tag);
|
|
83
|
+
qs.set("limit", String(params.limit ?? 50));
|
|
84
|
+
qs.set("offset", String(params.offset ?? 0));
|
|
85
|
+
return this.request("GET", `/tasks?${qs}`);
|
|
86
|
+
}
|
|
87
|
+
async getTask(id) {
|
|
88
|
+
return this.request("GET", `/tasks/${id}`);
|
|
89
|
+
}
|
|
90
|
+
async getTaskResult(id) {
|
|
91
|
+
return this.request("GET", `/tasks/${id}/result`);
|
|
92
|
+
}
|
|
93
|
+
async cancelTask(id) {
|
|
94
|
+
return this.request("POST", `/tasks/${id}/cancel`);
|
|
95
|
+
}
|
|
96
|
+
async updateTags(id, tags) {
|
|
97
|
+
return this.request("PATCH", `/tasks/${id}/tags`, { tags });
|
|
98
|
+
}
|
|
99
|
+
async listTags() {
|
|
100
|
+
return this.request("GET", "/tags");
|
|
101
|
+
}
|
|
102
|
+
async waitForTask(id, options = {}) {
|
|
103
|
+
const interval = options.pollInterval ?? 1e4;
|
|
104
|
+
const timeout = options.timeout ?? 6e5;
|
|
105
|
+
const start = Date.now();
|
|
106
|
+
while (true) {
|
|
107
|
+
const { task } = await this.getTask(id);
|
|
108
|
+
options.onPoll?.(task);
|
|
109
|
+
if (task.status === "completed") {
|
|
110
|
+
return this.getTaskResult(id);
|
|
111
|
+
}
|
|
112
|
+
if (task.status === "failed") {
|
|
113
|
+
throw new ApiError(400, { error: task.error, status: "failed" }, `Task ${id} failed: ${task.error}`);
|
|
114
|
+
}
|
|
115
|
+
if (task.status === "cancelled") {
|
|
116
|
+
throw new ApiError(400, { status: "cancelled" }, `Task ${id} was cancelled`);
|
|
117
|
+
}
|
|
118
|
+
if (Date.now() - start > timeout) {
|
|
119
|
+
throw new Error(`Task ${id} timed out after ${timeout / 1e3}s (status: ${task.status})`);
|
|
120
|
+
}
|
|
121
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async generate(params, options) {
|
|
125
|
+
const { task } = await this.createTask(params);
|
|
126
|
+
return this.waitForTask(task.id, options);
|
|
127
|
+
}
|
|
128
|
+
// ---- Material ----
|
|
129
|
+
async uploadMaterial(file, filename, type = "image") {
|
|
130
|
+
const url = `${this.baseUrl}/materials/upload`;
|
|
131
|
+
const form = new FormData();
|
|
132
|
+
const blob = file instanceof Blob ? file : new Blob([file]);
|
|
133
|
+
form.append("file", blob, filename);
|
|
134
|
+
form.append("type", type);
|
|
135
|
+
const resp = await fetch(url, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: this.buildAuthHeaders(),
|
|
138
|
+
body: form
|
|
139
|
+
});
|
|
140
|
+
if (resp.status === 401) throw new AuthError(await resp.json().catch(() => ({})));
|
|
141
|
+
const data = await resp.json().catch(() => ({}));
|
|
142
|
+
if (!resp.ok) throw new ApiError(resp.status, data, data.error);
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
async listMaterials(params = {}) {
|
|
146
|
+
const qs = new URLSearchParams();
|
|
147
|
+
if (params.type) qs.set("type", params.type);
|
|
148
|
+
if (params.search) qs.set("search", params.search);
|
|
149
|
+
if (params.limit) qs.set("limit", String(params.limit));
|
|
150
|
+
if (params.offset) qs.set("offset", String(params.offset));
|
|
151
|
+
return this.request("GET", `/materials?${qs}`);
|
|
152
|
+
}
|
|
153
|
+
// ---- Character ----
|
|
154
|
+
async listCharacters(params = {}) {
|
|
155
|
+
const qs = new URLSearchParams();
|
|
156
|
+
if (params.category) qs.set("category", params.category);
|
|
157
|
+
if (params.usage_group) qs.set("usage_group", params.usage_group);
|
|
158
|
+
if (params.search) qs.set("search", params.search);
|
|
159
|
+
if (params.page) qs.set("page", String(params.page));
|
|
160
|
+
if (params.page_size) qs.set("page_size", String(params.page_size));
|
|
161
|
+
return this.request("GET", `/characters?${qs}`);
|
|
162
|
+
}
|
|
163
|
+
async getCharacter(id) {
|
|
164
|
+
return this.request("GET", `/characters/${id}`);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/cli.ts
|
|
169
|
+
import { readFileSync } from "fs";
|
|
170
|
+
import { join, extname, basename } from "path";
|
|
171
|
+
import { fileURLToPath } from "url";
|
|
172
|
+
var __dir = fileURLToPath(new URL(".", import.meta.url));
|
|
173
|
+
function loadEnv() {
|
|
174
|
+
const candidates = [
|
|
175
|
+
join(process.cwd(), ".env"),
|
|
176
|
+
join(__dir, ".env")
|
|
177
|
+
];
|
|
178
|
+
for (const p of candidates) {
|
|
179
|
+
try {
|
|
180
|
+
const content = readFileSync(p, "utf-8");
|
|
181
|
+
for (const line of content.split("\n")) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
184
|
+
const eqIdx = trimmed.indexOf("=");
|
|
185
|
+
if (eqIdx === -1) continue;
|
|
186
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
187
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
188
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
189
|
+
val = val.slice(1, -1);
|
|
190
|
+
}
|
|
191
|
+
if (!process.env[key]) process.env[key] = val;
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function env(key, fallback) {
|
|
199
|
+
const v = process.env[key] ?? fallback;
|
|
200
|
+
if (!v) {
|
|
201
|
+
console.error(`Error: ${key} is not set.
|
|
202
|
+
Set it via environment variable or .env file.`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
return v;
|
|
206
|
+
}
|
|
207
|
+
function createClient() {
|
|
208
|
+
loadEnv();
|
|
209
|
+
const apiKey = process.env["RENOISE_API_KEY"];
|
|
210
|
+
const authToken = process.env["RENOISE_AUTH_TOKEN"];
|
|
211
|
+
if (!apiKey && !authToken) {
|
|
212
|
+
console.error("Error: RENOISE_API_KEY or RENOISE_AUTH_TOKEN is required.\nSet one via environment variable or .env file.");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
return new RenoiseClient({
|
|
216
|
+
baseUrl: env("RENOISE_BASE_URL", "https://www.renoise.ai/api/public/v1"),
|
|
217
|
+
apiKey,
|
|
218
|
+
authToken
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function json(data) {
|
|
222
|
+
console.log(JSON.stringify(data, null, 2));
|
|
223
|
+
}
|
|
224
|
+
function parseArgs(args) {
|
|
225
|
+
const flags = {};
|
|
226
|
+
const positional = [];
|
|
227
|
+
for (let i = 0; i < args.length; i++) {
|
|
228
|
+
const arg = args[i];
|
|
229
|
+
if (arg.startsWith("--")) {
|
|
230
|
+
const key = arg.slice(2);
|
|
231
|
+
const next = args[i + 1];
|
|
232
|
+
if (next && !next.startsWith("--")) {
|
|
233
|
+
flags[key] = next;
|
|
234
|
+
i++;
|
|
235
|
+
} else {
|
|
236
|
+
flags[key] = "true";
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
positional.push(arg);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { flags, positional };
|
|
243
|
+
}
|
|
244
|
+
var HELP = `
|
|
245
|
+
RENOISE CLI \u2014 AI generation task management
|
|
246
|
+
|
|
247
|
+
Usage:
|
|
248
|
+
renoise <domain> <action> [options]
|
|
249
|
+
|
|
250
|
+
Domains:
|
|
251
|
+
task Create, list, and manage generation tasks
|
|
252
|
+
material Upload and manage materials
|
|
253
|
+
character Browse available characters
|
|
254
|
+
credit Check balance and transaction history
|
|
255
|
+
|
|
256
|
+
Environment:
|
|
257
|
+
RENOISE_API_KEY API key (starts with fk_), sent as X-API-Key
|
|
258
|
+
RENOISE_AUTH_TOKEN Auth token, sent as Authorization: Bearer
|
|
259
|
+
(at least one of API_KEY or AUTH_TOKEN required)
|
|
260
|
+
RENOISE_BASE_URL (optional) Full API base URL (including path)
|
|
261
|
+
Default: https://www.renoise.ai/api/public/v1
|
|
262
|
+
|
|
263
|
+
Run "renoise <domain> help" for domain-specific commands.
|
|
264
|
+
`.trim();
|
|
265
|
+
var HELP_TASK = `
|
|
266
|
+
renoise task \u2014 Manage generation tasks
|
|
267
|
+
|
|
268
|
+
Commands:
|
|
269
|
+
generate Create task + wait for result (one step)
|
|
270
|
+
create Create a task (returns immediately)
|
|
271
|
+
list List tasks
|
|
272
|
+
get <id> Get task detail
|
|
273
|
+
result <id> Get task result
|
|
274
|
+
wait <id> Wait for task to complete
|
|
275
|
+
cancel <id> Cancel a pending task
|
|
276
|
+
tags List all your tags
|
|
277
|
+
tag <id> --tags a,b,c Update tags on a task
|
|
278
|
+
|
|
279
|
+
Options for generate/create:
|
|
280
|
+
--prompt <text> (required) Generation prompt
|
|
281
|
+
--model <name> Model name (default: renoise-2.0)
|
|
282
|
+
--duration <seconds> Video duration (default: 5)
|
|
283
|
+
--ratio <w:h> Aspect ratio (default: 1:1)
|
|
284
|
+
--resolution <1k|2k> Image resolution (for image models)
|
|
285
|
+
--tags <a,b,c> Comma-separated tags
|
|
286
|
+
--materials <spec> Material refs: "id:role" or "id1:role1,id2:role2"
|
|
287
|
+
--characters <spec> Character refs: "id1,id2" or "id1:role,id2:role"
|
|
288
|
+
|
|
289
|
+
Options for list:
|
|
290
|
+
--status <status> Filter by status
|
|
291
|
+
--tag <tag> Filter by tag
|
|
292
|
+
--limit <n> Max results (default: 20)
|
|
293
|
+
--offset <n> Pagination offset
|
|
294
|
+
|
|
295
|
+
Options for wait:
|
|
296
|
+
--interval <seconds> Poll interval (default: 10)
|
|
297
|
+
--timeout <seconds> Timeout (default: 600)
|
|
298
|
+
|
|
299
|
+
Examples:
|
|
300
|
+
renoise task generate --prompt "a cat dancing" --duration 5
|
|
301
|
+
renoise task generate --prompt "cute cat" --model nano-banana-2
|
|
302
|
+
renoise task create --prompt "epic scene" --duration 10 --ratio 16:9
|
|
303
|
+
renoise task list --status completed --limit 5
|
|
304
|
+
renoise task result 123
|
|
305
|
+
renoise task wait 123 --interval 15
|
|
306
|
+
`.trim();
|
|
307
|
+
var HELP_MATERIAL = `
|
|
308
|
+
renoise material \u2014 Manage materials
|
|
309
|
+
|
|
310
|
+
Commands:
|
|
311
|
+
list List your uploaded materials
|
|
312
|
+
upload <file> Upload a material (image or video)
|
|
313
|
+
|
|
314
|
+
Options for list:
|
|
315
|
+
--type <image|video> Filter by type
|
|
316
|
+
--search <keyword> Search by name
|
|
317
|
+
--limit <n> Max results (default: 20)
|
|
318
|
+
|
|
319
|
+
Options for upload:
|
|
320
|
+
--type <image|video> Override auto-detected type
|
|
321
|
+
|
|
322
|
+
Examples:
|
|
323
|
+
renoise material list
|
|
324
|
+
renoise material upload /path/to/image.jpg
|
|
325
|
+
renoise material upload /path/to/video.mp4 --type video
|
|
326
|
+
`.trim();
|
|
327
|
+
var HELP_CHARACTER = `
|
|
328
|
+
renoise character \u2014 Browse characters
|
|
329
|
+
|
|
330
|
+
Commands:
|
|
331
|
+
list List available characters
|
|
332
|
+
get <id> Get character detail
|
|
333
|
+
|
|
334
|
+
Options for list:
|
|
335
|
+
--category <category> Filter by category
|
|
336
|
+
--usage_group <group> Filter by usage group
|
|
337
|
+
--search <keyword> Search by name
|
|
338
|
+
--page <n> Page number
|
|
339
|
+
--page_size <n> Page size
|
|
340
|
+
|
|
341
|
+
Examples:
|
|
342
|
+
renoise character list
|
|
343
|
+
renoise character list --category female --search Jasmine
|
|
344
|
+
renoise character get 3
|
|
345
|
+
`.trim();
|
|
346
|
+
var HELP_CREDIT = `
|
|
347
|
+
renoise credit \u2014 Balance and transactions
|
|
348
|
+
|
|
349
|
+
Commands:
|
|
350
|
+
me Show current user info and balance
|
|
351
|
+
estimate Estimate task cost
|
|
352
|
+
history Show credit transaction history
|
|
353
|
+
|
|
354
|
+
Options for estimate:
|
|
355
|
+
--model <name> Model name
|
|
356
|
+
--duration <seconds> Duration
|
|
357
|
+
--hasVideoRef Has video reference material
|
|
358
|
+
|
|
359
|
+
Options for history:
|
|
360
|
+
--limit <n> Max results (default: 20)
|
|
361
|
+
--offset <n> Pagination offset
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
renoise credit me
|
|
365
|
+
renoise credit estimate --model renoise-2.0 --duration 5
|
|
366
|
+
renoise credit history --limit 10
|
|
367
|
+
`.trim();
|
|
368
|
+
async function taskGenerate(client, flags) {
|
|
369
|
+
if (!flags.prompt) {
|
|
370
|
+
console.error("Error: --prompt is required.\n");
|
|
371
|
+
console.log(HELP_TASK);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
const params = buildCreateParams(flags);
|
|
375
|
+
console.log("Creating task...");
|
|
376
|
+
const { task } = await client.createTask(params);
|
|
377
|
+
console.log(`Task #${task.id} created (${task.status}). Waiting for completion...`);
|
|
378
|
+
if (task.estimatedCredit) console.log(`Cost: ${task.estimatedCredit} credits`);
|
|
379
|
+
const interval = (flags.interval ? parseInt(flags.interval) : 10) * 1e3;
|
|
380
|
+
const timeout = (flags.timeout ? parseInt(flags.timeout) : 600) * 1e3;
|
|
381
|
+
const result = await client.waitForTask(task.id, {
|
|
382
|
+
pollInterval: interval,
|
|
383
|
+
timeout,
|
|
384
|
+
onPoll: (t) => {
|
|
385
|
+
console.log(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${t.status}`);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
console.log("\nDone!");
|
|
389
|
+
printResult(result);
|
|
390
|
+
}
|
|
391
|
+
async function taskCreate(client, flags) {
|
|
392
|
+
if (!flags.prompt) {
|
|
393
|
+
console.error("Error: --prompt is required.\n");
|
|
394
|
+
console.log(HELP_TASK);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
const params = buildCreateParams(flags);
|
|
398
|
+
const data = await client.createTask(params);
|
|
399
|
+
console.log(`Task created: id=${data.task.id}, status=${data.task.status}`);
|
|
400
|
+
if (data.task.estimatedCredit) console.log(`Cost: ${data.task.estimatedCredit} credits`);
|
|
401
|
+
json(data);
|
|
402
|
+
}
|
|
403
|
+
async function taskList(client, flags) {
|
|
404
|
+
const data = await client.listTasks({
|
|
405
|
+
status: flags.status,
|
|
406
|
+
tag: flags.tag,
|
|
407
|
+
limit: flags.limit ? parseInt(flags.limit) : 20,
|
|
408
|
+
offset: flags.offset ? parseInt(flags.offset) : 0
|
|
409
|
+
});
|
|
410
|
+
console.log(`Found ${data.tasks.length} task(s):
|
|
411
|
+
`);
|
|
412
|
+
for (const t of data.tasks) {
|
|
413
|
+
const tags = (() => {
|
|
414
|
+
try {
|
|
415
|
+
return JSON.parse(t.tags || "[]");
|
|
416
|
+
} catch {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
})();
|
|
420
|
+
const tagStr = tags.length ? ` [${tags.join(", ")}]` : "";
|
|
421
|
+
console.log(` #${t.id} ${t.status.padEnd(10)} ${t.model} ${t.prompt.slice(0, 60)}${tagStr}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function taskGet(client, positional) {
|
|
425
|
+
const id = parseInt(positional[0]);
|
|
426
|
+
if (!id) {
|
|
427
|
+
console.error("Error: task ID required.\nUsage: renoise task get <id>");
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
json(await client.getTask(id));
|
|
431
|
+
}
|
|
432
|
+
async function taskResult(client, positional) {
|
|
433
|
+
const id = parseInt(positional[0]);
|
|
434
|
+
if (!id) {
|
|
435
|
+
console.error("Error: task ID required.\nUsage: renoise task result <id>");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const result = await client.getTaskResult(id);
|
|
439
|
+
printResult(result);
|
|
440
|
+
}
|
|
441
|
+
async function taskWait(client, positional, flags) {
|
|
442
|
+
const id = parseInt(positional[0]);
|
|
443
|
+
if (!id) {
|
|
444
|
+
console.error("Error: task ID required.\nUsage: renoise task wait <id>");
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
const interval = (flags.interval ? parseInt(flags.interval) : 10) * 1e3;
|
|
448
|
+
const timeout = (flags.timeout ? parseInt(flags.timeout) : 600) * 1e3;
|
|
449
|
+
console.log(`Waiting for task #${id} (poll every ${interval / 1e3}s, timeout ${timeout / 1e3}s)...`);
|
|
450
|
+
const result = await client.waitForTask(id, {
|
|
451
|
+
pollInterval: interval,
|
|
452
|
+
timeout,
|
|
453
|
+
onPoll: (task) => {
|
|
454
|
+
console.log(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${task.status}`);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
console.log("\nDone!");
|
|
458
|
+
printResult(result);
|
|
459
|
+
}
|
|
460
|
+
async function taskCancel(client, positional) {
|
|
461
|
+
const id = parseInt(positional[0]);
|
|
462
|
+
if (!id) {
|
|
463
|
+
console.error("Error: task ID required.\nUsage: renoise task cancel <id>");
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
await client.cancelTask(id);
|
|
467
|
+
console.log(`Task #${id} cancelled.`);
|
|
468
|
+
}
|
|
469
|
+
async function taskTags(client) {
|
|
470
|
+
json(await client.listTags());
|
|
471
|
+
}
|
|
472
|
+
async function taskTag(client, positional, flags) {
|
|
473
|
+
const id = parseInt(positional[0]);
|
|
474
|
+
if (!id || !flags.tags) {
|
|
475
|
+
console.error("Usage: renoise task tag <id> --tags a,b,c");
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
const tags = flags.tags.split(",").map((t) => t.trim());
|
|
479
|
+
json(await client.updateTags(id, tags));
|
|
480
|
+
}
|
|
481
|
+
async function materialList(client, flags) {
|
|
482
|
+
const data = await client.listMaterials({
|
|
483
|
+
type: flags.type,
|
|
484
|
+
search: flags.search,
|
|
485
|
+
limit: flags.limit ? parseInt(flags.limit) : 20,
|
|
486
|
+
offset: flags.offset ? parseInt(flags.offset) : 0
|
|
487
|
+
});
|
|
488
|
+
console.log(`Found ${data.materials.length} material(s):
|
|
489
|
+
`);
|
|
490
|
+
for (const m of data.materials) {
|
|
491
|
+
console.log(` #${m.id} ${m.type.padEnd(6)} ${m.name}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function materialUpload(client, positional, flags) {
|
|
495
|
+
const filePath = positional[0];
|
|
496
|
+
if (!filePath) {
|
|
497
|
+
console.error("Error: file path required.\nUsage: renoise material upload <file> [--type image|video]");
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
const ext = extname(filePath).toLowerCase();
|
|
501
|
+
const videoExts = [".mp4", ".mov", ".avi", ".webm", ".mkv"];
|
|
502
|
+
const type = flags.type || (videoExts.includes(ext) ? "video" : "image");
|
|
503
|
+
const buffer = readFileSync(filePath);
|
|
504
|
+
const filename = basename(filePath);
|
|
505
|
+
console.log(`Uploading ${filename} (${type}, ${(buffer.byteLength / 1024).toFixed(1)}KB)...`);
|
|
506
|
+
const data = await client.uploadMaterial(buffer, filename, type);
|
|
507
|
+
if (data.action === "exists") {
|
|
508
|
+
console.log(`Material already exists: #${data.material.id}`);
|
|
509
|
+
} else {
|
|
510
|
+
console.log(`Material uploaded: #${data.material.id}`);
|
|
511
|
+
}
|
|
512
|
+
json(data);
|
|
513
|
+
}
|
|
514
|
+
async function characterList(client, flags) {
|
|
515
|
+
const data = await client.listCharacters({
|
|
516
|
+
category: flags.category,
|
|
517
|
+
usage_group: flags.usage_group,
|
|
518
|
+
search: flags.search,
|
|
519
|
+
page: flags.page ? parseInt(flags.page) : void 0,
|
|
520
|
+
page_size: flags.page_size ? parseInt(flags.page_size) : void 0
|
|
521
|
+
});
|
|
522
|
+
console.log(`Found ${data.characters.length} character(s) (total: ${data.total}):
|
|
523
|
+
`);
|
|
524
|
+
for (const ch of data.characters) {
|
|
525
|
+
console.log(` #${String(ch.id).padEnd(4)} ${ch.code.padEnd(5)} ${ch.name.padEnd(16)} ${ch.category.padEnd(8)} ${ch.usage_group}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function characterGet(client, positional) {
|
|
529
|
+
const id = parseInt(positional[0]);
|
|
530
|
+
if (!id) {
|
|
531
|
+
console.error("Error: character ID required.\nUsage: renoise character get <id>");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
json(await client.getCharacter(id));
|
|
535
|
+
}
|
|
536
|
+
async function creditMe(client) {
|
|
537
|
+
json(await client.getMe());
|
|
538
|
+
}
|
|
539
|
+
async function creditEstimate(client, flags) {
|
|
540
|
+
json(await client.estimateCost({
|
|
541
|
+
model: flags.model,
|
|
542
|
+
duration: flags.duration ? parseInt(flags.duration) : void 0,
|
|
543
|
+
hasVideoRef: flags.hasVideoRef === "true" || flags.hasVideoRef === "1"
|
|
544
|
+
}));
|
|
545
|
+
}
|
|
546
|
+
async function creditHistory(client, flags) {
|
|
547
|
+
const limit = flags.limit ? parseInt(flags.limit) : 20;
|
|
548
|
+
const offset = flags.offset ? parseInt(flags.offset) : 0;
|
|
549
|
+
json(await client.getCreditHistory(limit, offset));
|
|
550
|
+
}
|
|
551
|
+
function buildCreateParams(flags) {
|
|
552
|
+
const params = { prompt: flags.prompt };
|
|
553
|
+
if (flags.model) params.model = flags.model;
|
|
554
|
+
if (flags.duration) params.duration = parseInt(flags.duration);
|
|
555
|
+
if (flags.ratio) params.ratio = flags.ratio;
|
|
556
|
+
if (flags.resolution) params.resolution = flags.resolution;
|
|
557
|
+
if (flags.tags) params.tags = flags.tags.split(",").map((t) => t.trim());
|
|
558
|
+
const allMaterials = [];
|
|
559
|
+
if (flags.materials) {
|
|
560
|
+
for (const m of flags.materials.split(",")) {
|
|
561
|
+
const [id, role] = m.trim().split(":");
|
|
562
|
+
allMaterials.push({ id: parseInt(id), role: role || "ref_video" });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (flags.characters) {
|
|
566
|
+
for (const m of flags.characters.split(",")) {
|
|
567
|
+
const trimmed = m.trim();
|
|
568
|
+
const parts = trimmed.split(":");
|
|
569
|
+
const charId = parseInt(parts[0]);
|
|
570
|
+
const role = parts[1] || "reference_image";
|
|
571
|
+
allMaterials.push({ character_id: charId, role });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (allMaterials.length) params.materials = allMaterials;
|
|
575
|
+
return params;
|
|
576
|
+
}
|
|
577
|
+
function printResult(result) {
|
|
578
|
+
console.log(`Task #${result.taskId} ${result.status}`);
|
|
579
|
+
if (result.videoUrl) console.log(` Video: ${result.videoUrl}`);
|
|
580
|
+
if (result.coverUrl) console.log(` Cover: ${result.coverUrl}`);
|
|
581
|
+
if (result.imageUrl) console.log(` Image: ${result.imageUrl}`);
|
|
582
|
+
if (result.resolutions && Object.keys(result.resolutions).length) {
|
|
583
|
+
console.log(` Resolutions: ${Object.keys(result.resolutions).join(", ")}`);
|
|
584
|
+
}
|
|
585
|
+
if (result.warning) console.log(` Warning: ${result.warning}`);
|
|
586
|
+
json(result);
|
|
587
|
+
}
|
|
588
|
+
var DOMAIN_HELP = {
|
|
589
|
+
task: HELP_TASK,
|
|
590
|
+
material: HELP_MATERIAL,
|
|
591
|
+
character: HELP_CHARACTER,
|
|
592
|
+
credit: HELP_CREDIT
|
|
593
|
+
};
|
|
594
|
+
async function main() {
|
|
595
|
+
const args = process.argv.slice(2);
|
|
596
|
+
const { flags, positional } = parseArgs(args);
|
|
597
|
+
const domain = positional[0];
|
|
598
|
+
const action = positional[1];
|
|
599
|
+
const subPositional = positional.slice(2);
|
|
600
|
+
if (!domain || domain === "help" || flags.help === "true") {
|
|
601
|
+
console.log(HELP);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (action === "help" || !action && flags.help !== "true") {
|
|
605
|
+
console.log(DOMAIN_HELP[domain] || HELP);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (flags.help === "true") {
|
|
609
|
+
console.log(DOMAIN_HELP[domain] || HELP);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const client = createClient();
|
|
613
|
+
try {
|
|
614
|
+
switch (domain) {
|
|
615
|
+
case "task":
|
|
616
|
+
switch (action) {
|
|
617
|
+
case "generate":
|
|
618
|
+
await taskGenerate(client, flags);
|
|
619
|
+
break;
|
|
620
|
+
case "create":
|
|
621
|
+
await taskCreate(client, flags);
|
|
622
|
+
break;
|
|
623
|
+
case "list":
|
|
624
|
+
await taskList(client, flags);
|
|
625
|
+
break;
|
|
626
|
+
case "get":
|
|
627
|
+
await taskGet(client, subPositional);
|
|
628
|
+
break;
|
|
629
|
+
case "result":
|
|
630
|
+
await taskResult(client, subPositional);
|
|
631
|
+
break;
|
|
632
|
+
case "wait":
|
|
633
|
+
await taskWait(client, subPositional, flags);
|
|
634
|
+
break;
|
|
635
|
+
case "cancel":
|
|
636
|
+
await taskCancel(client, subPositional);
|
|
637
|
+
break;
|
|
638
|
+
case "tags":
|
|
639
|
+
await taskTags(client);
|
|
640
|
+
break;
|
|
641
|
+
case "tag":
|
|
642
|
+
await taskTag(client, subPositional, flags);
|
|
643
|
+
break;
|
|
644
|
+
default:
|
|
645
|
+
console.error(`Unknown task action: ${action}
|
|
646
|
+
`);
|
|
647
|
+
console.log(HELP_TASK);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
case "material":
|
|
652
|
+
switch (action) {
|
|
653
|
+
case "list":
|
|
654
|
+
await materialList(client, flags);
|
|
655
|
+
break;
|
|
656
|
+
case "upload":
|
|
657
|
+
await materialUpload(client, subPositional, flags);
|
|
658
|
+
break;
|
|
659
|
+
default:
|
|
660
|
+
console.error(`Unknown material action: ${action}
|
|
661
|
+
`);
|
|
662
|
+
console.log(HELP_MATERIAL);
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
case "character":
|
|
667
|
+
switch (action) {
|
|
668
|
+
case "list":
|
|
669
|
+
await characterList(client, flags);
|
|
670
|
+
break;
|
|
671
|
+
case "get":
|
|
672
|
+
await characterGet(client, subPositional);
|
|
673
|
+
break;
|
|
674
|
+
default:
|
|
675
|
+
console.error(`Unknown character action: ${action}
|
|
676
|
+
`);
|
|
677
|
+
console.log(HELP_CHARACTER);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
break;
|
|
681
|
+
case "credit":
|
|
682
|
+
switch (action) {
|
|
683
|
+
case "me":
|
|
684
|
+
await creditMe(client);
|
|
685
|
+
break;
|
|
686
|
+
case "estimate":
|
|
687
|
+
await creditEstimate(client, flags);
|
|
688
|
+
break;
|
|
689
|
+
case "history":
|
|
690
|
+
await creditHistory(client, flags);
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
console.error(`Unknown credit action: ${action}
|
|
694
|
+
`);
|
|
695
|
+
console.log(HELP_CREDIT);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
default:
|
|
700
|
+
console.error(`Unknown domain: ${domain}
|
|
701
|
+
`);
|
|
702
|
+
console.log(HELP);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
if (e instanceof AuthError) {
|
|
707
|
+
console.error(`Auth Error: ${e.message}`);
|
|
708
|
+
console.error("Make sure RENOISE_API_KEY is set correctly.");
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
if (e instanceof InsufficientCreditError) {
|
|
712
|
+
console.error(`Credit Error: ${e.message}`);
|
|
713
|
+
console.error(` Available: ${e.available}, Required: ${e.required}`);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
if (e instanceof ApiError) {
|
|
717
|
+
console.error(`API Error (${e.status}): ${e.message}`);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
main();
|