@usenaive-sdk/cli 0.12.2 → 0.13.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/README.md +76 -5
- package/dist/client.d.ts +74 -1
- package/dist/client.js +351 -17
- package/dist/client.js.map +1 -1
- package/dist/commands/aeo.js +69 -27
- package/dist/commands/aeo.js.map +1 -1
- package/dist/commands/app-data.js +29 -11
- package/dist/commands/app-data.js.map +1 -1
- package/dist/commands/apps.d.ts +7 -0
- package/dist/commands/apps.js +255 -18
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/audio.d.ts +9 -0
- package/dist/commands/audio.js +391 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/auth.d.ts +31 -0
- package/dist/commands/auth.js +403 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/billing.js +12 -12
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/brain.d.ts +2 -0
- package/dist/commands/brain.js +1255 -0
- package/dist/commands/brain.js.map +1 -0
- package/dist/commands/browser.js +35 -8
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/business.js +117 -16
- package/dist/commands/business.js.map +1 -1
- package/dist/commands/clone.d.ts +2 -0
- package/dist/commands/clone.js +203 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/companies.d.ts +8 -0
- package/dist/commands/companies.js +137 -47
- package/dist/commands/companies.js.map +1 -1
- package/dist/commands/company-data.d.ts +2 -0
- package/dist/commands/company-data.js +168 -0
- package/dist/commands/company-data.js.map +1 -0
- package/dist/commands/compute.js +10 -3
- package/dist/commands/compute.js.map +1 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +36 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connections.d.ts +6 -0
- package/dist/commands/connections.js +29 -1
- package/dist/commands/connections.js.map +1 -1
- package/dist/commands/domains.js +1 -1
- package/dist/commands/ecommerce.js +19 -9
- package/dist/commands/ecommerce.js.map +1 -1
- package/dist/commands/email.js +56 -9
- package/dist/commands/email.js.map +1 -1
- package/dist/commands/env.d.ts +3 -3
- package/dist/commands/env.js +68 -4
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/formation.js +3 -3
- package/dist/commands/identity.js +4 -3
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/images.js +87 -13
- package/dist/commands/images.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +39 -36
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/jobs.js +1 -0
- package/dist/commands/jobs.js.map +1 -1
- package/dist/commands/keys.js +26 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/legacy-auth.d.ts +27 -0
- package/dist/commands/legacy-auth.js +64 -0
- package/dist/commands/legacy-auth.js.map +1 -0
- package/dist/commands/llm.js +13 -0
- package/dist/commands/llm.js.map +1 -1
- package/dist/commands/login.js +2 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/loops.d.ts +2 -0
- package/dist/commands/loops.js +260 -0
- package/dist/commands/loops.js.map +1 -0
- package/dist/commands/media.js +3 -3
- package/dist/commands/media.js.map +1 -1
- package/dist/commands/mobile.d.ts +4 -0
- package/dist/commands/mobile.js +382 -0
- package/dist/commands/mobile.js.map +1 -0
- package/dist/commands/payments.d.ts +2 -0
- package/dist/commands/payments.js +131 -0
- package/dist/commands/payments.js.map +1 -0
- package/dist/commands/people.d.ts +2 -0
- package/dist/commands/people.js +193 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/phone.js +3 -3
- package/dist/commands/phone.js.map +1 -1
- package/dist/commands/projects.d.ts +13 -0
- package/dist/commands/projects.js +220 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/register.js +16 -8
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/sandbox.d.ts +2 -0
- package/dist/commands/sandbox.js +245 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/search.js +26 -12
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/seo.js +16 -8
- package/dist/commands/seo.js.map +1 -1
- package/dist/commands/sessions.d.ts +6 -0
- package/dist/commands/sessions.js +109 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/social-data.d.ts +2 -0
- package/dist/commands/social-data.js +244 -0
- package/dist/commands/social-data.js.map +1 -0
- package/dist/commands/social.js +8 -6
- package/dist/commands/social.js.map +1 -1
- package/dist/commands/teams.d.ts +69 -0
- package/dist/commands/teams.js +503 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/commands/up.d.ts +150 -0
- package/dist/commands/up.js +415 -15
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/usage.js +15 -4
- package/dist/commands/usage.js.map +1 -1
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.js +76 -11
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/users.d.ts +8 -0
- package/dist/commands/users.js +28 -9
- package/dist/commands/users.js.map +1 -1
- package/dist/commands/vault.js +28 -1
- package/dist/commands/vault.js.map +1 -1
- package/dist/commands/video.js +190 -270
- package/dist/commands/video.js.map +1 -1
- package/dist/commands/voice.d.ts +2 -0
- package/dist/commands/voice.js +516 -0
- package/dist/commands/voice.js.map +1 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +244 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/whoami.js +17 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config.d.ts +24 -1
- package/dist/config.js +36 -7
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +290 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/brain-select.d.ts +193 -0
- package/dist/lib/brain-select.js +403 -0
- package/dist/lib/brain-select.js.map +1 -0
- package/dist/lib/cli-validation.d.ts +12 -0
- package/dist/lib/cli-validation.js +46 -0
- package/dist/lib/cli-validation.js.map +1 -0
- package/dist/lib/deprecation.d.ts +158 -0
- package/dist/lib/deprecation.js +140 -0
- package/dist/lib/deprecation.js.map +1 -0
- package/dist/lib/duration.d.ts +8 -0
- package/dist/lib/duration.js +11 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/http-response.d.ts +11 -0
- package/dist/lib/http-response.js +96 -0
- package/dist/lib/http-response.js.map +1 -0
- package/dist/lib/pricing.d.ts +132 -0
- package/dist/lib/pricing.js +148 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/stream-frames.d.ts +114 -0
- package/dist/lib/stream-frames.js +169 -0
- package/dist/lib/stream-frames.js.map +1 -0
- package/dist/lib/teams-catalog.d.ts +141 -0
- package/dist/lib/teams-catalog.js +765 -0
- package/dist/lib/teams-catalog.js.map +1 -0
- package/dist/lib/unknown-command.d.ts +57 -0
- package/dist/lib/unknown-command.js +93 -0
- package/dist/lib/unknown-command.js.map +1 -0
- package/dist/lib/version-check.d.ts +26 -0
- package/dist/lib/version-check.js +21 -0
- package/dist/lib/version-check.js.map +1 -0
- package/dist/output.d.ts +119 -10
- package/dist/output.js +309 -15
- package/dist/output.js.map +1 -1
- package/dist/prompt.d.ts +17 -0
- package/dist/prompt.js +70 -0
- package/dist/prompt.js.map +1 -0
- package/package.json +11 -6
- package/dist/commands/billing-client.d.ts +0 -2
- package/dist/commands/billing-client.js +0 -271
- package/dist/commands/billing-client.js.map +0 -1
- package/dist/commands/crm-app.d.ts +0 -2
- package/dist/commands/crm-app.js +0 -306
- package/dist/commands/crm-app.js.map +0 -1
- package/dist/commands/crm.d.ts +0 -2
- package/dist/commands/crm.js +0 -319
- package/dist/commands/crm.js.map +0 -1
- package/dist/commands/integrations.d.ts +0 -2
- package/dist/commands/integrations.js +0 -199
- package/dist/commands/integrations.js.map +0 -1
- package/dist/commands/link.d.ts +0 -3
- package/dist/commands/link.js +0 -91
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/operators.d.ts +0 -11
- package/dist/commands/operators.js +0 -120
- package/dist/commands/operators.js.map +0 -1
package/dist/commands/video.js
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
2
|
import { apiRequest, handleApiError } from "../client.js";
|
|
3
|
-
import { agentOutput } from "../output.js";
|
|
3
|
+
import { agentOutput, creditCostHint, creditEstimateHint } from "../output.js";
|
|
4
|
+
import { parseDurationArg } from "../lib/duration.js";
|
|
5
|
+
import { REFRAME_FEE_CREDITS } from "../lib/pricing.js";
|
|
6
|
+
// Validate --duration (pure check in lib/duration.ts) and fail fast with a clear,
|
|
7
|
+
// structured message rather than forwarding a bad value that would 422 at fal.
|
|
8
|
+
function durationOrExit(raw) {
|
|
9
|
+
const parsed = parseDurationArg(raw);
|
|
10
|
+
if (!parsed.ok) {
|
|
11
|
+
console.error(JSON.stringify({
|
|
12
|
+
success: false,
|
|
13
|
+
action: "video.generate",
|
|
14
|
+
error: { code: "invalid_argument", message: parsed.message },
|
|
15
|
+
recovery_steps: [
|
|
16
|
+
{ command: 'naive video generate "your prompt" --model <model> --duration 5', description: "Pass a numeric duration a Kling model accepts" },
|
|
17
|
+
{ command: "naive video models", description: "List models (note: some, e.g. minimax-video, have a fixed length)" },
|
|
18
|
+
],
|
|
19
|
+
}, null, 2));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
return parsed.value;
|
|
23
|
+
}
|
|
4
24
|
export const videoCmd = new Command("video")
|
|
5
|
-
.description("Generate AI videos
|
|
25
|
+
.description("Generate AI videos and extract short-form clips")
|
|
6
26
|
.addHelpText("after", `
|
|
7
27
|
Subcommands:
|
|
8
|
-
naive video generate <prompt>
|
|
9
|
-
naive video status <job_id>
|
|
10
|
-
naive video models
|
|
11
|
-
naive video clip <
|
|
12
|
-
naive video clip-status <
|
|
13
|
-
naive video clip-edit <video_url> Add captions, effects, and B-roll to a video
|
|
28
|
+
naive video generate <prompt> Generate a video from text or image
|
|
29
|
+
naive video status <job_id> Check video generation status
|
|
30
|
+
naive video models List available video models
|
|
31
|
+
naive video clip <video_url> Auto-clip a long video into short clips
|
|
32
|
+
naive video clip-status <id> Check clipping job status
|
|
14
33
|
|
|
15
34
|
Examples:
|
|
16
35
|
$ naive video generate "a rocket launching into space" --model fal-ai/kling-video/v3/pro/text-to-video
|
|
17
|
-
$ naive video clip "https://
|
|
18
|
-
$ naive video clip-edit "https://example.com/video.mp4" --title "Edited Video" --magic-zooms --magic-brolls
|
|
36
|
+
$ naive video clip "https://example.com/video.mp4" --aspect-ratio 9:16
|
|
19
37
|
$ naive video clip-status job-uuid-123
|
|
20
38
|
$ naive video models
|
|
21
39
|
|
|
22
40
|
Cost:
|
|
23
|
-
- Video generation: dynamic, model/duration-dependent (
|
|
24
|
-
- Video clipping:
|
|
25
|
-
- Video editing: 3 credits per video
|
|
41
|
+
- Video generation: dynamic, model/duration-dependent (run 'naive video pricing --model <id>' to preview)
|
|
42
|
+
- Video clipping: metered per input minute
|
|
26
43
|
- Credits deducted only on successful completion
|
|
27
44
|
`);
|
|
28
45
|
videoCmd
|
|
@@ -30,7 +47,7 @@ videoCmd
|
|
|
30
47
|
.description("Generate a video using AI models (async — returns job ID)")
|
|
31
48
|
.requiredOption("--model <model>", "Video model ID (required)")
|
|
32
49
|
.option("--input <json>", "Full input parameters as JSON (overrides other options)")
|
|
33
|
-
.option("--duration <seconds>", "Video
|
|
50
|
+
.option("--duration <seconds>", "Video length in seconds — Kling models only (e.g. 5 or 10); some models have a fixed length and reject it")
|
|
34
51
|
.option("--image-url <url>", "Source image URL for image-to-video models")
|
|
35
52
|
.option("--wait", "Wait for job to complete (polls until done)")
|
|
36
53
|
.addHelpText("after", `
|
|
@@ -39,6 +56,12 @@ Examples:
|
|
|
39
56
|
$ naive video generate "product showcase" --model fal-ai/kling-video/v3/pro/text-to-video --duration 10 --wait
|
|
40
57
|
$ naive video generate --model fal-ai/kling-video/v3/pro/image-to-video --image-url https://example.com/img.jpg --input '{"prompt":"zoom in slowly"}'
|
|
41
58
|
|
|
59
|
+
Duration:
|
|
60
|
+
--duration is accepted by Kling models (e.g. fal-ai/kling-video/v3/pro/*), which
|
|
61
|
+
is why the example above pairs it with one. Fixed-length models such as
|
|
62
|
+
fal-ai/minimax-video/video-01-live reject --duration — pass a numeric value only
|
|
63
|
+
with a model that supports it, or omit it to use the model's default length.
|
|
64
|
+
|
|
42
65
|
What this does:
|
|
43
66
|
1. Submits a video generation request
|
|
44
67
|
2. Returns a job ID (async processing, typically 30-120s)
|
|
@@ -60,15 +83,21 @@ Available models (use 'naive video models' for full list):
|
|
|
60
83
|
|
|
61
84
|
Parameters (via --input JSON):
|
|
62
85
|
prompt Text description of the video
|
|
63
|
-
duration
|
|
86
|
+
duration Length in seconds (Kling models only): "5" or "10". Fixed-length
|
|
87
|
+
models (e.g. minimax-video) reject it — see the Duration note above.
|
|
64
88
|
image_url Source image for image-to-video
|
|
65
89
|
aspect_ratio "16:9", "9:16", "1:1" (model-dependent)
|
|
66
90
|
negative_prompt Text describing what to avoid
|
|
67
91
|
cfg_scale Guidance scale 0-1 (higher = more literal, default 0.5)
|
|
68
92
|
generate_audio Enable native audio generation (boolean, default true for Kling v3)
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
Reading the result (after completion, via 'naive video status <id>'):
|
|
95
|
+
Submit returns the job under 'id' (job_id is a deprecated alias).
|
|
96
|
+
The output URL is at result.media[0].url — a flat, model-independent path.
|
|
97
|
+
(result.result.video.url is the raw provider payload; media[] is preferred.)
|
|
98
|
+
|
|
99
|
+
Cost: dynamic — based on model pricing ($0.05/credit)
|
|
100
|
+
Run 'naive video pricing --model <id> --duration <s>' to preview cost before generating.
|
|
72
101
|
Credits deducted only on successful completion. Failed jobs are free.
|
|
73
102
|
`)
|
|
74
103
|
.action(async (prompt, opts) => {
|
|
@@ -79,14 +108,14 @@ Cost: dynamic — based on model pricing ($0.50/credit)
|
|
|
79
108
|
else if (prompt) {
|
|
80
109
|
input = { prompt };
|
|
81
110
|
if (opts.duration)
|
|
82
|
-
input.duration = opts.duration;
|
|
111
|
+
input.duration = durationOrExit(opts.duration);
|
|
83
112
|
if (opts.imageUrl)
|
|
84
113
|
input.image_url = opts.imageUrl;
|
|
85
114
|
}
|
|
86
115
|
else if (opts.imageUrl) {
|
|
87
116
|
input = { image_url: opts.imageUrl };
|
|
88
117
|
if (opts.duration)
|
|
89
|
-
input.duration = opts.duration;
|
|
118
|
+
input.duration = durationOrExit(opts.duration);
|
|
90
119
|
}
|
|
91
120
|
else {
|
|
92
121
|
console.error(JSON.stringify({
|
|
@@ -111,23 +140,28 @@ Cost: dynamic — based on model pricing ($0.50/credit)
|
|
|
111
140
|
if (opts.wait) {
|
|
112
141
|
agentOutput({
|
|
113
142
|
action: "video.generate.submitted",
|
|
114
|
-
|
|
143
|
+
// Surface the estimate at submit so spend is visible before the wait (N6).
|
|
144
|
+
result: { job_id: data.job_id, status: "polling", model: opts.model, estimated_credits: data.estimated_credits ?? null },
|
|
115
145
|
next_steps: [],
|
|
116
|
-
hints: [
|
|
146
|
+
hints: [
|
|
147
|
+
"Waiting for video generation to complete (typically 30-120s)...",
|
|
148
|
+
...(creditEstimateHint(resp.data) ? [creditEstimateHint(resp.data)] : []),
|
|
149
|
+
],
|
|
117
150
|
});
|
|
118
151
|
await pollJob(data.job_id);
|
|
119
152
|
}
|
|
120
153
|
else {
|
|
121
154
|
agentOutput({
|
|
122
155
|
action: "video.generate.submitted",
|
|
123
|
-
result: { job_id: data.job_id, status: "queued", model: opts.model },
|
|
156
|
+
result: { job_id: data.job_id, status: "queued", model: opts.model, estimated_credits: data.estimated_credits ?? null },
|
|
124
157
|
next_steps: [
|
|
125
158
|
{ command: `naive video status ${data.job_id}`, description: "Check generation progress" },
|
|
126
159
|
{ command: `naive jobs get ${data.job_id}`, description: "Get full job details" },
|
|
127
160
|
],
|
|
128
161
|
hints: [
|
|
129
162
|
`Video generation submitted (model: ${opts.model})`,
|
|
130
|
-
|
|
163
|
+
// Echoes the API's pre-flight estimate (same source that bills at completion) — N6.
|
|
164
|
+
...(creditEstimateHint(resp.data) ? [creditEstimateHint(resp.data)] : ["Credits deducted only on successful completion"]),
|
|
131
165
|
"Typical time: 30-120 seconds depending on model and duration",
|
|
132
166
|
],
|
|
133
167
|
related_commands: ["naive video status", "naive jobs get", "naive jobs"],
|
|
@@ -146,6 +180,83 @@ Cost: dynamic — based on model pricing ($0.50/credit)
|
|
|
146
180
|
});
|
|
147
181
|
}
|
|
148
182
|
});
|
|
183
|
+
videoCmd
|
|
184
|
+
.command("clip <video_url>")
|
|
185
|
+
.description("Auto-clip a long video into short clips (async — returns job ID)")
|
|
186
|
+
// Strict enum via commander's Option.choices — rejects anything outside
|
|
187
|
+
// the two supported aspect ratios at parse time, so the API never sees a
|
|
188
|
+
// typo'd value. Default "9:16" matches the REST POST handler's default
|
|
189
|
+
// (vertical-first social baseline). "16:9" preserves the source aspect.
|
|
190
|
+
.addOption(new Option("--aspect <ratio>", "Output aspect ratio for generated clips")
|
|
191
|
+
.choices(["9:16", "16:9"])
|
|
192
|
+
.default("9:16"))
|
|
193
|
+
.addHelpText("after", `
|
|
194
|
+
Examples:
|
|
195
|
+
$ naive video clip https://example.com/podcast.mp4
|
|
196
|
+
$ naive video clip https://example.com/podcast.mp4 --aspect 16:9
|
|
197
|
+
|
|
198
|
+
What this does:
|
|
199
|
+
1. Submits a long video for AI auto-clipping
|
|
200
|
+
2. Returns a job ID (async processing)
|
|
201
|
+
3. Check progress with 'naive video clip-status <job_id>'
|
|
202
|
+
|
|
203
|
+
Input:
|
|
204
|
+
A public https URL to a video no longer than 90 minutes.
|
|
205
|
+
|
|
206
|
+
Options:
|
|
207
|
+
--aspect <ratio> "9:16" (default, vertical via SAM-3 smart-crop) or "16:9" (preserve source)
|
|
208
|
+
|
|
209
|
+
Cost: per input minute — credits deducted only on successful completion.
|
|
210
|
+
9:16 reframe adds a flat ${REFRAME_FEE_CREDITS}-credit face-tracking fee per job.
|
|
211
|
+
`)
|
|
212
|
+
.action(async (videoUrl, opts) => {
|
|
213
|
+
const resp = await apiRequest("POST", "/v1/video/clip", {
|
|
214
|
+
video_url: videoUrl,
|
|
215
|
+
aspect_ratio: opts.aspect ?? "9:16",
|
|
216
|
+
});
|
|
217
|
+
handleApiError("video.clip", resp);
|
|
218
|
+
const data = resp.data;
|
|
219
|
+
agentOutput({
|
|
220
|
+
action: "video.clip.submitted",
|
|
221
|
+
result: { job_id: data.job_id, status: data.status },
|
|
222
|
+
next_steps: [
|
|
223
|
+
{ command: `naive video clip-status ${data.job_id}`, description: "Check clipping progress" },
|
|
224
|
+
{ command: `naive jobs get ${data.job_id}`, description: "Get full job details" },
|
|
225
|
+
],
|
|
226
|
+
hints: [
|
|
227
|
+
"Clip job submitted",
|
|
228
|
+
"Input must be a public https URL to a video 90 minutes or shorter",
|
|
229
|
+
"Credits deducted only on successful completion",
|
|
230
|
+
],
|
|
231
|
+
related_commands: ["naive video clip-status", "naive jobs"],
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
videoCmd
|
|
235
|
+
.command("clip-status <job_id>")
|
|
236
|
+
.description("Check the status of a video clipping job")
|
|
237
|
+
.addHelpText("after", `
|
|
238
|
+
Examples:
|
|
239
|
+
$ naive video clip-status 550e8400-e29b-41d4-a716-446655440000
|
|
240
|
+
|
|
241
|
+
When completed, the result includes the generated clip URLs.
|
|
242
|
+
`)
|
|
243
|
+
.action(async (jobId) => {
|
|
244
|
+
const resp = await apiRequest("GET", `/v1/video/clip/${jobId}`);
|
|
245
|
+
handleApiError("video.clip-status", resp);
|
|
246
|
+
const data = resp.data;
|
|
247
|
+
agentOutput({
|
|
248
|
+
action: "video.clip-status",
|
|
249
|
+
result: resp.data,
|
|
250
|
+
next_steps: [
|
|
251
|
+
...(data.status === "queued" || data.status === "processing"
|
|
252
|
+
? [{ command: `naive video clip-status ${jobId}`, description: "Check again in 20-30 seconds" }]
|
|
253
|
+
: []),
|
|
254
|
+
{ command: "naive video clip <video_url>", description: "Clip another video" },
|
|
255
|
+
],
|
|
256
|
+
hints: [`Job status: ${data.status}`],
|
|
257
|
+
related_commands: ["naive video clip", "naive jobs"],
|
|
258
|
+
});
|
|
259
|
+
});
|
|
149
260
|
videoCmd
|
|
150
261
|
.command("status <job_id>")
|
|
151
262
|
.description("Check the status of a video generation job")
|
|
@@ -166,12 +277,15 @@ When completed, the result includes a video URL.
|
|
|
166
277
|
const resp = await apiRequest("GET", `/v1/video/${jobId}`);
|
|
167
278
|
handleApiError("video.status", resp);
|
|
168
279
|
const data = resp.data;
|
|
280
|
+
// N12: prefer the flat, model-independent media[] path; fall back to the raw
|
|
281
|
+
// provider nesting for older API responses that predate media[].
|
|
282
|
+
const videoUrl = data.media?.[0]?.url ?? data.result?.video?.url;
|
|
169
283
|
agentOutput({
|
|
170
284
|
action: "video.status",
|
|
171
285
|
result: resp.data,
|
|
172
286
|
next_steps: [
|
|
173
|
-
...(data.status === "completed" &&
|
|
174
|
-
? [{ command: `# Download: curl -o video.mp4 "${
|
|
287
|
+
...(data.status === "completed" && videoUrl
|
|
288
|
+
? [{ command: `# Download: curl -o video.mp4 "${videoUrl}"`, description: "Download the generated video (result.media[0].url)" }]
|
|
175
289
|
: []),
|
|
176
290
|
...(data.status === "queued" || data.status === "processing"
|
|
177
291
|
? [{ command: `naive video status ${jobId}`, description: "Check again in 10-30 seconds" }]
|
|
@@ -181,6 +295,10 @@ When completed, the result includes a video URL.
|
|
|
181
295
|
hints: [
|
|
182
296
|
`Job status: ${data.status}`,
|
|
183
297
|
...(data.status === "processing" ? ["Video generation typically takes 30-120 seconds"] : []),
|
|
298
|
+
// Post-hoc: on completion the job carries the ACTUAL credits_used — same
|
|
299
|
+
// single source as the pre-flight estimate, so before/at/after agree (N6).
|
|
300
|
+
...(creditCostHint(resp.data) ? [creditCostHint(resp.data)] : []),
|
|
301
|
+
...(data.status === "failed" ? [`Failed (no credits deducted). Reason: ${data.error || "unknown"}`] : []),
|
|
184
302
|
],
|
|
185
303
|
related_commands: ["naive video generate", "naive jobs"],
|
|
186
304
|
});
|
|
@@ -212,282 +330,84 @@ What this does:
|
|
|
212
330
|
hints: [
|
|
213
331
|
"Use the model 'id' field as the --model parameter in 'naive video generate'",
|
|
214
332
|
"Text-to-video models accept a text prompt; image-to-video models require --image-url",
|
|
333
|
+
"Each model includes credits_estimate (~5s clip; null when pricing is unavailable)",
|
|
334
|
+
"For an exact quote at a given length: naive video pricing --model <id> --duration <s>",
|
|
215
335
|
],
|
|
216
|
-
related_commands: ["naive video generate", "naive video status"],
|
|
336
|
+
related_commands: ["naive video generate", "naive video pricing", "naive video status"],
|
|
217
337
|
});
|
|
218
338
|
});
|
|
219
|
-
// ── Video Clipping ──────────────────────────────────────────────────────
|
|
220
339
|
videoCmd
|
|
221
|
-
.command("
|
|
222
|
-
.description("
|
|
223
|
-
.requiredOption("--
|
|
224
|
-
.option("--
|
|
225
|
-
.option("--template <name>", "Caption template name")
|
|
226
|
-
.option("--min-length <seconds>", "Minimum clip length in seconds (15-300, default: 15)")
|
|
227
|
-
.option("--max-length <seconds>", "Maximum clip length in seconds (15-300, default: 60)")
|
|
228
|
-
.option("--wait", "Wait for job to complete (polls until done)")
|
|
340
|
+
.command("pricing")
|
|
341
|
+
.description("Preview the estimated credit cost of a video model before generating")
|
|
342
|
+
.requiredOption("--model <model>", "Video model ID (get IDs from 'naive video models')")
|
|
343
|
+
.option("--duration <seconds>", "Clip length in seconds to price (default: 5)", "5")
|
|
229
344
|
.addHelpText("after", `
|
|
230
345
|
Examples:
|
|
231
|
-
$ naive video
|
|
232
|
-
$ naive video
|
|
346
|
+
$ naive video pricing --model fal-ai/kling-video/v3/pro/text-to-video
|
|
347
|
+
$ naive video pricing --model fal-ai/kling-video/v3/pro/text-to-video --duration 10
|
|
233
348
|
|
|
234
349
|
What this does:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
4. Use --wait to block until done, or check with 'naive video clip-status'
|
|
239
|
-
|
|
240
|
-
Virality scores (0-100):
|
|
241
|
-
80+ Strong — prioritize for distribution
|
|
242
|
-
60-79 Decent — usable content
|
|
243
|
-
<60 Weak — skip unless no alternatives
|
|
350
|
+
Returns the ESTIMATED credit cost for one generation, derived from the model's
|
|
351
|
+
live fal.ai price — the same source that bills the job on completion, so the
|
|
352
|
+
estimate tracks the real charge. Nothing is spent by this command.
|
|
244
353
|
|
|
245
|
-
Cost:
|
|
354
|
+
Cost: free (this is a price preview; generation itself is charged on completion)
|
|
246
355
|
`)
|
|
247
|
-
.action(async (
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (opts.minLength)
|
|
257
|
-
body.min_clip_length = parseInt(opts.minLength);
|
|
258
|
-
if (opts.maxLength)
|
|
259
|
-
body.max_clip_length = parseInt(opts.maxLength);
|
|
260
|
-
const resp = await apiRequest("POST", "/v1/video/clips", body);
|
|
261
|
-
handleApiError("video.clip", resp);
|
|
262
|
-
const data = resp.data;
|
|
263
|
-
if (opts.wait) {
|
|
264
|
-
agentOutput({
|
|
265
|
-
action: "video.clip.submitted",
|
|
266
|
-
result: { job_id: data.job_id, status: "polling" },
|
|
267
|
-
next_steps: [],
|
|
268
|
-
hints: ["Waiting for clip extraction to complete (typically 5-10 minutes)..."],
|
|
269
|
-
});
|
|
270
|
-
await pollClipJob(data.job_id);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
agentOutput({
|
|
274
|
-
action: "video.clip.submitted",
|
|
275
|
-
result: { job_id: data.job_id, status: "queued" },
|
|
276
|
-
next_steps: [
|
|
277
|
-
{ command: `naive video clip-status ${data.job_id}`, description: "Check clipping progress" },
|
|
278
|
-
{ command: `naive jobs get ${data.job_id}`, description: "Get full job details" },
|
|
279
|
-
],
|
|
280
|
-
hints: [
|
|
281
|
-
"Clip extraction submitted — typically takes 5-10 minutes",
|
|
282
|
-
"Credits deducted only on successful completion (4 credits)",
|
|
283
|
-
"When complete, clips are ranked by AI virality score (0-100)",
|
|
356
|
+
.action(async (opts) => {
|
|
357
|
+
const duration = parseInt(opts.duration ?? "5", 10);
|
|
358
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
359
|
+
console.error(JSON.stringify({
|
|
360
|
+
success: false,
|
|
361
|
+
action: "video.pricing",
|
|
362
|
+
error: { code: "invalid_argument", message: "--duration must be a positive number of seconds" },
|
|
363
|
+
recovery_steps: [
|
|
364
|
+
{ command: `naive video pricing --model ${opts.model} --duration 5`, description: "Price a 5-second clip" },
|
|
284
365
|
],
|
|
285
|
-
|
|
286
|
-
|
|
366
|
+
}, null, 2));
|
|
367
|
+
process.exit(1);
|
|
287
368
|
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.description("Check the status of a clip extraction or video editing job")
|
|
292
|
-
.addHelpText("after", `
|
|
293
|
-
Examples:
|
|
294
|
-
$ naive video clip-status 550e8400-e29b-41d4-a716-446655440000
|
|
295
|
-
|
|
296
|
-
What this does:
|
|
297
|
-
Returns the current status of a clipping/editing job:
|
|
298
|
-
- queued: submitted, processing typically takes 5-10 minutes
|
|
299
|
-
- completed: clips or edited video ready
|
|
300
|
-
- failed: processing failed (no credits deducted)
|
|
301
|
-
|
|
302
|
-
When completed, clip jobs include multiple clips with virality scores.
|
|
303
|
-
Edit jobs include the download URL for the processed video.
|
|
304
|
-
`)
|
|
305
|
-
.action(async (jobId) => {
|
|
306
|
-
const resp = await apiRequest("GET", `/v1/video/clips/${jobId}`);
|
|
307
|
-
handleApiError("video.clip-status", resp);
|
|
369
|
+
const params = new URLSearchParams({ model: opts.model, duration: String(duration) });
|
|
370
|
+
const resp = await apiRequest("GET", `/v1/video/pricing?${params}`);
|
|
371
|
+
handleApiError("video.pricing", resp);
|
|
308
372
|
const data = resp.data;
|
|
309
|
-
const clips = data.result?.clips ?? [];
|
|
310
|
-
const topClips = clips.filter((c) => (c.virality_score?.total ?? 0) >= 75);
|
|
311
373
|
agentOutput({
|
|
312
|
-
action: "video.
|
|
374
|
+
action: "video.pricing",
|
|
313
375
|
result: resp.data,
|
|
314
376
|
next_steps: [
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
...topClips.slice(0, 3).map((c, i) => ({
|
|
318
|
-
command: `naive social post "${c.title ?? `Clip ${i + 1}`}" --platforms youtube --youtube-type SHORT --media-url "${c.direct_url}" --publish`,
|
|
319
|
-
description: `Post clip ${i + 1} (score: ${c.virality_score?.total ?? "N/A"}) as YouTube Short`,
|
|
320
|
-
})),
|
|
321
|
-
{ command: `# Download top clip: curl -o clip.mp4 "${topClips[0]?.direct_url ?? clips[0]?.direct_url}"`, description: "Download the top-ranked clip" },
|
|
322
|
-
]
|
|
323
|
-
: []),
|
|
324
|
-
...(data.status === "completed" && data.result?.video_url
|
|
325
|
-
? [{ command: `# Download: curl -o edited.mp4 "${data.result.video_url}"`, description: "Download the edited video" }]
|
|
326
|
-
: []),
|
|
327
|
-
...(data.status === "queued"
|
|
328
|
-
? [{ command: `naive video clip-status ${jobId}`, description: "Check again in 30-60 seconds" }]
|
|
329
|
-
: []),
|
|
330
|
-
{ command: `naive video clip "https://youtube.com/watch?v=..." --title "New Clips"`, description: "Start another clipping job" },
|
|
377
|
+
{ command: `naive video generate "your prompt" --model ${opts.model}${duration ? ` --duration ${duration}` : ""}`, description: "Generate a video with this model" },
|
|
378
|
+
{ command: "naive video models", description: "Compare estimates across all models" },
|
|
331
379
|
],
|
|
332
380
|
hints: [
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
`${clips.length} clips extracted`,
|
|
338
|
-
"Scores 80+ are strong candidates for social media distribution",
|
|
339
|
-
"Use 'naive social post' to publish top clips directly",
|
|
340
|
-
]
|
|
341
|
-
: []),
|
|
381
|
+
typeof data.estimated_credits === "number"
|
|
382
|
+
? `Estimated cost: ${data.estimated_credits} credits for a ${data.duration_seconds ?? duration}s clip (charged only on successful completion)`
|
|
383
|
+
: "Pricing unavailable for this model",
|
|
384
|
+
"This is an estimate; the exact charge is metered from the delivered video's duration",
|
|
342
385
|
],
|
|
343
|
-
related_commands: ["naive video
|
|
386
|
+
related_commands: ["naive video generate", "naive video models"],
|
|
344
387
|
});
|
|
345
388
|
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.requiredOption("--title <title>", "Project title for the editing job")
|
|
350
|
-
.option("--language <lang>", "Video language code (default: en)")
|
|
351
|
-
.option("--template <name>", "Caption template name")
|
|
352
|
-
.option("--magic-zooms", "Enable AI-powered zooms")
|
|
353
|
-
.option("--magic-brolls", "Enable AI-generated B-roll")
|
|
354
|
-
.option("--brolls-percentage <n>", "B-roll percentage (0-100)")
|
|
355
|
-
.option("--wait", "Wait for job to complete (polls until done)")
|
|
356
|
-
.addHelpText("after", `
|
|
357
|
-
Examples:
|
|
358
|
-
$ naive video clip-edit "https://example.com/video.mp4" --title "Edited Podcast"
|
|
359
|
-
$ naive video clip-edit "https://example.com/video.mp4" --title "Enhanced" --magic-zooms --magic-brolls --wait
|
|
360
|
-
|
|
361
|
-
What this does:
|
|
362
|
-
1. Submits a video for AI-powered editing (captions, zooms, B-roll)
|
|
363
|
-
2. Returns a job ID (async processing, typically 5-10 minutes)
|
|
364
|
-
3. When complete, returns a download URL for the edited video
|
|
365
|
-
|
|
366
|
-
Input requirements:
|
|
367
|
-
- Public URL to an MP4 or MOV file
|
|
368
|
-
- Maximum file size: 2GB, maximum duration: 2 hours
|
|
369
|
-
|
|
370
|
-
Cost: 3 credits per edit. Credits deducted only on completion.
|
|
371
|
-
`)
|
|
372
|
-
.action(async (videoUrl, opts) => {
|
|
373
|
-
const body = {
|
|
374
|
-
video_url: videoUrl,
|
|
375
|
-
title: opts.title,
|
|
376
|
-
};
|
|
377
|
-
if (opts.language)
|
|
378
|
-
body.language = opts.language;
|
|
379
|
-
if (opts.template)
|
|
380
|
-
body.template = opts.template;
|
|
381
|
-
if (opts.magicZooms)
|
|
382
|
-
body.magic_zooms = true;
|
|
383
|
-
if (opts.magicBrolls)
|
|
384
|
-
body.magic_brolls = true;
|
|
385
|
-
if (opts.brollsPercentage)
|
|
386
|
-
body.magic_brolls_percentage = parseInt(opts.brollsPercentage);
|
|
387
|
-
const resp = await apiRequest("POST", "/v1/video/clips/edit", body);
|
|
388
|
-
handleApiError("video.clip-edit", resp);
|
|
389
|
-
const data = resp.data;
|
|
390
|
-
if (opts.wait) {
|
|
391
|
-
agentOutput({
|
|
392
|
-
action: "video.clip-edit.submitted",
|
|
393
|
-
result: { job_id: data.job_id, status: "polling" },
|
|
394
|
-
next_steps: [],
|
|
395
|
-
hints: ["Waiting for video editing to complete (typically 5-10 minutes)..."],
|
|
396
|
-
});
|
|
397
|
-
await pollClipJob(data.job_id);
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
agentOutput({
|
|
401
|
-
action: "video.clip-edit.submitted",
|
|
402
|
-
result: { job_id: data.job_id, status: "queued" },
|
|
403
|
-
next_steps: [
|
|
404
|
-
{ command: `naive video clip-status ${data.job_id}`, description: "Check editing progress" },
|
|
405
|
-
{ command: `naive jobs get ${data.job_id}`, description: "Get full job details" },
|
|
406
|
-
],
|
|
407
|
-
hints: [
|
|
408
|
-
"Video edit submitted — typically takes 5-10 minutes",
|
|
409
|
-
"Credits deducted only on successful completion (3 credits)",
|
|
410
|
-
],
|
|
411
|
-
related_commands: ["naive video clip-status", "naive jobs get"],
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
async function pollClipJob(jobId) {
|
|
416
|
-
while (true) {
|
|
417
|
-
await new Promise((r) => setTimeout(r, 15000));
|
|
418
|
-
const resp = await apiRequest("GET", `/v1/jobs/${jobId}`);
|
|
419
|
-
const data = resp.data;
|
|
420
|
-
if (data.status === "completed" || data.status === "failed") {
|
|
421
|
-
const clips = data.result?.clips ?? [];
|
|
422
|
-
const topClips = clips.filter((c) => (c.virality_score?.total ?? 0) >= 75);
|
|
423
|
-
agentOutput({
|
|
424
|
-
action: data.type === "video_clip_edit" ? "video.clip-edit.completed" : "video.clip.completed",
|
|
425
|
-
result: resp.data,
|
|
426
|
-
next_steps: [
|
|
427
|
-
...(data.status === "completed" && clips.length > 0
|
|
428
|
-
? topClips.slice(0, 2).map((c, i) => ({
|
|
429
|
-
command: `naive social post "Your caption" --platforms youtube --youtube-type SHORT --media-url "${c.direct_url}" --publish`,
|
|
430
|
-
description: `Post top clip ${i + 1} (score: ${c.virality_score?.total}) as YouTube Short`,
|
|
431
|
-
}))
|
|
432
|
-
: []),
|
|
433
|
-
...(data.status === "completed" && data.result?.video_url
|
|
434
|
-
? [{ command: `# Download: curl -o edited.mp4 "${data.result.video_url}"`, description: "Download edited video" }]
|
|
435
|
-
: []),
|
|
436
|
-
{ command: `naive video clip "https://youtube.com/watch?v=..." --title "New Clips"`, description: "Start another job" },
|
|
437
|
-
],
|
|
438
|
-
hints: [
|
|
439
|
-
data.status === "completed"
|
|
440
|
-
? clips.length > 0
|
|
441
|
-
? `${data.result?.clip_count ?? clips.length} clips ready (top score: ${data.result?.top_virality_score ?? "N/A"})`
|
|
442
|
-
: "Video editing complete — download URL available"
|
|
443
|
-
: "Job failed — no credits deducted",
|
|
444
|
-
...(data.status === "completed" && clips.length > 0
|
|
445
|
-
? [
|
|
446
|
-
"Tip: Use 'naive social post' to publish top clips to YouTube, TikTok, Instagram",
|
|
447
|
-
"Tip: Clips are auto-ingested to Media Assets. Use 'naive media list --source video_clipping' to find them.",
|
|
448
|
-
]
|
|
449
|
-
: []),
|
|
450
|
-
...(data.status === "completed" && clips.length === 0
|
|
451
|
-
? ["Tip: Edited video is auto-ingested to Media Assets. Use 'naive media list --source video_clipping' to find it."]
|
|
452
|
-
: []),
|
|
453
|
-
],
|
|
454
|
-
related_commands: ["naive video clip", "naive video clip-edit", "naive social post", "naive jobs"],
|
|
455
|
-
});
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
process.stderr.write(` Status: ${data.status} (clipping typically takes 5-10 minutes)\n`);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
389
|
+
// Polls a job to completion for `video generate --wait`. Local helper mirroring
|
|
390
|
+
// images.ts/search.ts; the prior copy lived in main's Submagic clip block that
|
|
391
|
+
// was removed during the main->branch merge.
|
|
461
392
|
async function pollJob(jobId) {
|
|
462
393
|
while (true) {
|
|
463
|
-
await new Promise((r) => setTimeout(r,
|
|
394
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
464
395
|
const resp = await apiRequest("GET", `/v1/jobs/${jobId}`);
|
|
396
|
+
handleApiError("video.generate.poll", resp);
|
|
465
397
|
const data = resp.data;
|
|
466
398
|
if (data.status === "completed" || data.status === "failed") {
|
|
467
399
|
agentOutput({
|
|
468
400
|
action: "video.generate.completed",
|
|
469
401
|
result: resp.data,
|
|
470
|
-
next_steps: [
|
|
471
|
-
...(data.status === "completed" && data.result?.video
|
|
472
|
-
? [
|
|
473
|
-
{ command: `# Download: curl -o video.mp4 "${data.result.video.url}"`, description: "Download the generated video" },
|
|
474
|
-
{ command: `naive social post "Your caption here" --platforms youtube --youtube-type SHORT --media-url "${data.result.video.url}" --publish`, description: "Post as YouTube Short" },
|
|
475
|
-
{ command: `naive social post "Your caption here" --platforms tiktok --media-url "${data.result.video.url}" --publish`, description: "Post to TikTok" },
|
|
476
|
-
{ command: `naive social post "Your caption here" --platforms instagram --media-url "${data.result.video.url}" --publish`, description: "Post as Instagram Reel" },
|
|
477
|
-
]
|
|
478
|
-
: []),
|
|
479
|
-
{ command: 'naive video generate "another prompt" --model <model>', description: "Generate another video" },
|
|
480
|
-
],
|
|
402
|
+
next_steps: [{ command: `naive jobs get ${jobId}`, description: "Get full job details" }],
|
|
481
403
|
hints: [
|
|
482
404
|
data.status === "completed"
|
|
483
|
-
? "Video generation complete
|
|
484
|
-
:
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
"Tip: Use 'naive media list --source video_generation' to find this video in your asset library.",
|
|
488
|
-
] : []),
|
|
405
|
+
? "Video generation complete"
|
|
406
|
+
: `Generation failed — no credits deducted. Reason: ${data.error || "unknown"}`,
|
|
407
|
+
// Actual charge, derived from the completed job's credits_used (N6/N7).
|
|
408
|
+
...(creditCostHint(resp.data) ? [creditCostHint(resp.data)] : []),
|
|
489
409
|
],
|
|
490
|
-
related_commands: ["naive video generate", "naive video
|
|
410
|
+
related_commands: ["naive video generate", "naive video status", "naive jobs"],
|
|
491
411
|
});
|
|
492
412
|
return;
|
|
493
413
|
}
|