vidjutsu 1.3.0 → 1.4.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 CHANGED
@@ -1,8 +1,14 @@
1
1
  # vidjutsu
2
2
 
3
- TypeScript SDK for the [VidJutsu](https://vidjutsu.ai) video intelligence API.
3
+ Clone viral TikTok and Instagram videos onto your own characters. Typed SDK, CLI, and MCP server for the [VidJutsu](https://vidjutsu.ai) API, auto-generated from the [OpenAPI spec](https://raw.githubusercontent.com/tfcbot/vidjutsu-openapi/main/openapi/spec.json).
4
4
 
5
- Watch, extract, transcribe, and overlay fully typed, auto-generated from the [OpenAPI spec](https://raw.githubusercontent.com/tfcbot/vidjutsu-openapi/main/openapi/spec.json).
5
+ Point it at a source video and it does three things:
6
+
7
+ 1. **Score it.** A clone check tells you whether the video will clone well before you spend anything on rendering.
8
+ 2. **Swap in your character.** Create a reusable character once, then reuse it across every clone.
9
+ 3. **Get a rendered clone.** Kling 3.0 Motion Control maps the source motion onto your character while preserving the source soundtrack.
10
+
11
+ Supporting endpoints cover discovery (scrape TikTok/Instagram), single-video download, and media processing (watch, extract, transcribe, overlay, compliance).
6
12
 
7
13
  ## Install
8
14
 
@@ -10,130 +16,209 @@ Watch, extract, transcribe, and overlay — fully typed, auto-generated from the
10
16
  npm install vidjutsu
11
17
  ```
12
18
 
13
- ## Get your API key
19
+ Or install the standalone CLI (no Node required):
14
20
 
15
21
  ```bash
16
22
  curl -fsSL https://vidjutsu.ai/install.sh | bash
23
+ ```
24
+
25
+ ## Get an API key
26
+
27
+ ```bash
17
28
  vidjutsu subscribe --email you@example.com
18
29
  ```
19
30
 
20
- Your key saves to `~/.vidjutsu/config.json` automatically. The SDK reads it from there no extra setup needed.
31
+ The key saves to `~/.vidjutsu/config.json`. The SDK and CLI read it from there, so nothing else needs configuring.
32
+
33
+ ## Three ways to use it
34
+
35
+ - **MCP** — connect an agent (Claude and others) and let it drive the clone workflow.
36
+ - **CLI** — `vidjutsu clone ...` from a terminal.
37
+ - **SDK** — typed methods in TypeScript.
38
+
39
+ ### MCP
40
+
41
+ For **Claude Code**:
42
+
43
+ ```bash
44
+ claude mcp add --transport http vidjutsu https://api.vidjutsu.ai/mcp
45
+ ```
46
+
47
+ For the **Claude desktop and web apps**, add a custom connector pointing at `https://api.vidjutsu.ai/mcp` and complete the OAuth prompt. There is no command to run.
48
+
49
+ ### CLI
50
+
51
+ Characters are reusable. Create one, then clone any number of videos with it.
52
+
53
+ ```bash
54
+ # 1. Create a character once. This prints a char_... id.
55
+ vidjutsu clone character --prompt "A neutral, camera-ready presenter"
56
+
57
+ # 2. Check whether a source video will clone well (optional but recommended).
58
+ vidjutsu clone check "https://www.tiktok.com/@creator/video/123456789"
59
+
60
+ # 3. Clone the video onto your character.
61
+ vidjutsu clone run "https://www.tiktok.com/@creator/video/123456789" \
62
+ --character char_abc123
63
+
64
+ # List your characters any time.
65
+ vidjutsu clone character list
66
+ ```
67
+
68
+ `clone run` downloads the source video, runs the clone check, extracts frame 0, builds a character-swapped starting frame, submits a Kling 3.0 Motion Control generation, and waits for the final URL. It stops if the check verdict is weak unless you pass `--force`. Source clips are limited to 15 seconds.
21
69
 
22
- ## Quick start
70
+ ### SDK
23
71
 
24
72
  ```ts
25
73
  import { createClient } from "vidjutsu";
26
74
 
27
- const vj = createClient();
75
+ const vj = createClient(); // reads ~/.vidjutsu/config.json or env vars
28
76
 
29
- // Watch AI analyzes a video and answers your prompt (50/day)
30
- const { data: watch } = await vj.watchMedia({
31
- mediaUrl: "https://cdn.example.com/video.mp4",
32
- prompt: "Is the hook strong enough for fitness TikTok?",
77
+ // 1. Create a reusable character (returns { id: "char_...", imageUrl, model }).
78
+ const { data: character } = await vj.createCharacter({
79
+ prompt: "A neutral, camera-ready presenter",
33
80
  });
34
- console.log(watch.response);
35
81
 
36
- // Extract pull frames, audio, and metadata (100/day)
37
- const { data: extract } = await vj.extractMedia({
38
- mediaUrl: "https://cdn.example.com/video.mp4",
39
- frames: "auto",
40
- audio: true,
82
+ // 2. Stage the source video's file, then check it.
83
+ const { data: source } = await vj.downloadTikTokVideo({
84
+ url: "https://www.tiktok.com/@creator/video/123456789",
85
+ });
86
+ const { data: check } = await vj.cloneCheck({ videoUrl: source.url });
87
+ console.log(check.verdict, check.score);
88
+
89
+ // 3. Extract frame 0 and build a character-swapped starting frame.
90
+ const { data: extracted } = await vj.extractMedia({
91
+ mediaUrl: source.url,
92
+ frames: [0],
93
+ audio: false,
94
+ metadata: false,
41
95
  });
42
- console.log(extract.frames, extract.metadata);
96
+ const firstFrame = extracted.frames?.[0]?.url;
97
+ if (!firstFrame) throw new Error("No first frame returned");
43
98
 
44
- // Transcribe speech-to-text with word-level timing (30/day)
45
- const { data: transcript } = await vj.transcribeMedia({
46
- mediaUrl: "https://cdn.example.com/video.mp4",
99
+ const { data: starting } = await vj.cloneStartingImage({
100
+ firstFrame,
101
+ characterId: character.id,
102
+ prompt: "Match the framing and lighting of the source",
47
103
  });
48
- console.log(transcript.transcript, transcript.words);
49
104
 
50
- // Overlay burn text onto video (50/day)
51
- const { data: overlay } = await vj.createOverlay({
52
- videoUrl: "https://cdn.example.com/video.mp4",
53
- text: "Follow for more tips",
54
- position: "bottom",
105
+ // 4. Generate the clone video, then poll for the final URL.
106
+ const { data: task } = await vj.cloneVideo({
107
+ startingImageUrl: starting.imageUrl,
108
+ sourceVideoUrl: source.url,
109
+ model: "kling", // the only supported clone-video model
55
110
  });
56
- console.log(overlay.resultUrl);
111
+ let result;
112
+ do {
113
+ await new Promise((resolve) => setTimeout(resolve, 3000));
114
+ ({ data: result } = await vj.getCloneVideo(task.id));
115
+ } while (result.status === "processing");
116
+ if (result.status === "failed") throw new Error(result.error);
117
+ console.log(result.videoUrl);
57
118
  ```
58
119
 
120
+ Every method returns the `openapi-fetch` shape: `{ data, error, response }`.
121
+
59
122
  ## Authentication
60
123
 
61
124
  The client resolves your API key in this order:
62
125
 
63
126
  1. **Explicit** — `createClient({ apiKey: "vj_live_..." })`
64
127
  2. **Environment variable** — `VIDJUTSU_API_KEY`
65
- 3. **Config file** — `~/.vidjutsu/config.json` (written by `vidjutsu auth`)
128
+ 3. **Config file** — `~/.vidjutsu/config.json` (written by `vidjutsu subscribe` / `vidjutsu auth`)
66
129
 
67
130
  ```ts
68
- // Reads from env or config file
69
- const vj = createClient();
131
+ const vj = createClient(); // env or config file
132
+ const vj = createClient({ apiKey: "vj_live_..." }); // explicit
133
+ const vj = createClient({ baseUrl: "https://staging.api.vidjutsu.ai" }); // custom host
134
+ ```
70
135
 
71
- // Or pass explicitly
72
- const vj = createClient({ apiKey: "vj_live_..." });
136
+ The default base URL is `https://api.vidjutsu.ai`.
73
137
 
74
- // Custom base URL
75
- const vj = createClient({ baseUrl: "https://staging.api.vidjutsu.ai" });
76
- ```
138
+ ## Methods
77
139
 
78
- ## All methods
140
+ ### Clone
79
141
 
80
- ### Video intelligence (metered)
142
+ | Method | Description |
143
+ |--------|-------------|
144
+ | `cloneCheck(body)` | Score whether a source video will clone well |
145
+ | `createCharacter(body)` / `getCharacter(id)` / `listCharacters()` | Create and reuse persisted characters |
146
+ | `cloneStartingImage(body)` | Build a character-swapped starting frame (no overlays) |
147
+ | `cloneVideo(body)` / `getCloneVideo(id)` | Generate a clone video and poll the task |
148
+
149
+ ### Discovery (scrape)
81
150
 
82
- Requires an active subscription. Each endpoint has its own daily rate limit (fixed window, resets 00:00 UTC). Hitting a limit returns HTTP 429 with a `retryAfter` hint.
151
+ Metadata and discovery only. Scrape returns the provider's raw source URLs and never stages media.
83
152
 
84
- | Method | Daily limit | Description |
85
- |--------|-------------|-------------|
86
- | `watchMedia(body)` | 50 / day | AI watches a video/image and answers your prompt |
87
- | `extractMedia(body)` | 100 / day | Extract frames, audio, and metadata from video |
88
- | `transcribeMedia(body)` | 30 / day | Speech-to-text with word-level timing |
89
- | `checkSpec(body)` | 100 / day | Validate a VidLang spec against rules |
90
- | `createOverlay(body)` | 50 / day | Burn text overlay onto video |
153
+ | Method | Description |
154
+ |--------|-------------|
155
+ | `scrapeTikTokTrending(body)` / `scrapeTikTokProfile(body)` / `scrapeTikTokProfileVideos(body)` | Find TikTok videos |
156
+ | `scrapeTikTokVideo(body)` / `scrapeTikTokVideoTranscript(body)` / `scrapeTikTokVideoComments(body)` | Single TikTok video, transcript, comments |
157
+ | `scrapeTikTokSearchUsers(body)` | Search TikTok users |
158
+ | `scrapeInstagramProfile(body)` / `scrapeInstagramUserPosts(body)` / `scrapeInstagramUserReels(body)` | Find Instagram content |
159
+ | `scrapeInstagramPost(body)` / `scrapeInstagramPostComments(body)` | Single Instagram post, comments |
160
+
161
+ ### Download
162
+
163
+ Stage one video's file to the CDN, one call per video.
164
+
165
+ | Method | Description |
166
+ |--------|-------------|
167
+ | `downloadTikTokVideo(body)` / `downloadInstagramVideo(body)` | Download and stage a single video |
168
+
169
+ ### Media processing (metered)
170
+
171
+ Each endpoint has its own daily rate limit (fixed window, resets 00:00 UTC). Hitting a limit returns HTTP 429 with a `retryAfter` hint.
172
+
173
+ | Method | Description |
174
+ |--------|-------------|
175
+ | `watchMedia(body)` | AI watches a video/image and answers your prompt |
176
+ | `extractMedia(body)` | Extract frames, audio, and metadata |
177
+ | `transcribeMedia(body)` | Speech-to-text with word-level timing |
178
+ | `createOverlay(body)` / `createDisclaimer(body)` | Burn text or a disclaimer onto video |
179
+ | `checkComplianceVideo(body)` / `checkCompliancePrompt(body)` | Ad-compliance checks |
91
180
 
92
- ### Resources (unmetered)
181
+ ### Resources
93
182
 
94
183
  | Method | Description |
95
184
  |--------|-------------|
96
- | `createAccount(body)` / `updateAccount(body, query)` / `listOrGetAccounts(query)` / `deleteAccount(query)` | Manage accounts |
97
- | `createPost(body)` / `updatePost(body, query)` / `listOrGetPosts(query)` / `deletePost(query)` | Manage posts |
98
- | `createAsset(body)` / `updateAsset(body, query)` / `listOrGetAssets(query)` / `deleteAsset(query)` | Manage assets |
99
- | `createReference(body)` / `updateReference(body, query)` / `listOrGetReferences(query)` / `deleteReference(query)` | Manage references |
100
- | `uploadFile(body)` / `uploadFromUrl(body)` | Upload media to CDN |
185
+ | `createAccount` / `updateAccount` / `listOrGetAccounts` / `deleteAccount` | Accounts |
186
+ | `createPost` / `updatePost` / `listOrGetPosts` / `deletePost` | Posts |
187
+ | `createAsset` / `updateAsset` / `listOrGetAssets` / `deleteAsset` | Assets |
188
+ | `createReference` / `updateReference` / `listOrGetReferences` / `deleteReference` | References |
189
+ | `createEditorProject` / `updateEditorProject` / `listOrGetEditorProjects` / `deleteEditorProject` | Editor projects |
190
+ | `uploadFile(body)` / `uploadFromUrl(body)` | Upload media to the CDN |
101
191
 
102
- ### Utilities (free)
192
+ ### Account and billing
103
193
 
104
194
  | Method | Description |
105
195
  |--------|-------------|
106
- | `getInfo()` | API info and endpoint discovery |
107
- | `getPricing()` | Current pricing |
108
- | `createCheckout(body)` | Create Stripe checkout session |
109
- | `getCheckoutStatus(query)` | Check checkout status |
110
- | `createSubscription(body)` | Subscribe ($99/mo) |
111
- | `rotateApiKey()` | Rotate API key (invalidates current key) |
112
- | `recoverApiKey(body)` | Recover key via email |
196
+ | `getInfo()` / `getPricing()` / `getUsage()` | API info, pricing, remaining daily capacity |
197
+ | `createSubscription(body)` / `getCheckoutStatus(query)` | Subscribe and check checkout status |
198
+ | `rotateApiKey()` / `recoverApiKey(body)` | Rotate or recover your key |
113
199
 
114
200
  ## Raw client
115
201
 
116
- Every method returns the `openapi-fetch` response shape: `{ data, error, response }`. For endpoints not covered by convenience methods, use the raw client:
202
+ For any endpoint without a convenience method, use the raw `openapi-fetch` client:
117
203
 
118
204
  ```ts
119
- const { data, error } = await vj.api.GET("/v1/usage");
120
- const { data, error } = await vj.api.POST("/v1/watch", {
121
- body: { mediaUrl: "...", prompt: "..." },
205
+ const { data } = await vj.api.GET("/v1/usage");
206
+ const { data } = await vj.api.POST("/v1/clones/check", {
207
+ body: { videoUrl: "https://..." },
122
208
  });
123
209
  ```
124
210
 
125
- ## Pricing & billing
211
+ ## Pricing and billing
126
212
 
127
- - **$99/month flat** full API access via Stripe, billed monthly, cancel anytime.
128
- - **No credits, no metered billing, no top-ups** usage is governed by per-endpoint daily rate limits (reset 00:00 UTC), not a credit balance.
213
+ - **$99/month flat** for full API access, via Stripe, billed monthly, cancel anytime.
214
+ - No credits and no top-ups. Usage is governed by per-endpoint daily rate limits that reset at 00:00 UTC.
129
215
  - Hitting a daily limit returns **HTTP 429** with a `retryAfter` hint. Check remaining capacity with `getUsage()` or `GET /v1/usage`.
130
- - `GET /v1/pricing` is the single source of truth for current price and limits.
216
+ - `GET /v1/pricing` is the source of truth for current price and limits.
131
217
 
132
218
  ## Links
133
219
 
134
220
  - [Documentation](https://docs.vidjutsu.ai)
135
221
  - [API Reference](https://docs.vidjutsu.ai/api-reference/introduction)
136
- - [CLI](https://github.com/tfcbot/vidjutsu-sdk) (included in this package)
137
222
  - [OpenAPI Spec](https://raw.githubusercontent.com/tfcbot/vidjutsu-openapi/main/openapi/spec.json)
138
223
 
139
224
  ## License
@@ -2190,7 +2190,7 @@ var init_dist2 = __esm(() => {
2190
2190
  });
2191
2191
 
2192
2192
  // src/cli/version.ts
2193
- var VERSION = "1.0.0";
2193
+ var VERSION = "1.4.0";
2194
2194
 
2195
2195
  // src/cli/client.ts
2196
2196
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -2607,21 +2607,19 @@ var init_status = __esm(() => {
2607
2607
  status_default = defineCommand({
2608
2608
  meta: { name: "status", description: "Check resource status by ID" },
2609
2609
  args: {
2610
- id: { type: "positional", description: "Resource ID (job_xxx, acc_xxx, post_xxx, ref_xxx)", required: true }
2610
+ id: { type: "positional", description: "Resource ID (acc_xxx, post_xxx, ref_xxx)", required: true }
2611
2611
  },
2612
2612
  async run({ args }) {
2613
2613
  const id = args.id;
2614
2614
  let path;
2615
- if (id.startsWith("job_"))
2616
- path = `/v1/distribution/jobs?id=${id}`;
2617
- else if (id.startsWith("acc_"))
2615
+ if (id.startsWith("acc_"))
2618
2616
  path = `/v1/accounts?id=${id}`;
2619
2617
  else if (id.startsWith("post_"))
2620
2618
  path = `/v1/posts?id=${id}`;
2621
2619
  else if (id.startsWith("ref_"))
2622
2620
  path = `/v1/references?id=${id}`;
2623
2621
  else {
2624
- console.error("Unknown ID prefix. Expected job_, acc_, post_, or ref_");
2622
+ console.error("Unknown ID prefix. Expected acc_, post_, or ref_");
2625
2623
  process.exit(1);
2626
2624
  }
2627
2625
  const result = await apiRequest("GET", path);
@@ -2630,148 +2628,6 @@ var init_status = __esm(() => {
2630
2628
  });
2631
2629
  });
2632
2630
 
2633
- // src/cli/commands/balance.ts
2634
- var exports_balance = {};
2635
- __export(exports_balance, {
2636
- default: () => balance_default
2637
- });
2638
- function fmtTs(ms) {
2639
- const d2 = new Date(ms);
2640
- const pad = (n2) => String(n2).padStart(2, "0");
2641
- return `${d2.getUTCFullYear()}-${pad(d2.getUTCMonth() + 1)}-${pad(d2.getUTCDate())} ${pad(d2.getUTCHours())}:${pad(d2.getUTCMinutes())} UTC`;
2642
- }
2643
- function fmtDelta(n2) {
2644
- const s2 = n2 >= 0 ? `+${n2}` : String(n2);
2645
- return s2.padStart(6);
2646
- }
2647
- var balance_default;
2648
- var init_balance = __esm(() => {
2649
- init_dist2();
2650
- init_client();
2651
- balance_default = defineCommand({
2652
- meta: {
2653
- name: "balance",
2654
- description: "Show credit balance and recent ledger entries"
2655
- },
2656
- args: {
2657
- json: {
2658
- type: "boolean",
2659
- description: "Emit raw JSON instead of a table",
2660
- default: false
2661
- }
2662
- },
2663
- async run({ args }) {
2664
- const res = await apiRequest("GET", "/v1/balance");
2665
- if (args.json) {
2666
- console.log(JSON.stringify(res, null, 2));
2667
- return;
2668
- }
2669
- console.log(`Balance: ${res.balance} credits`);
2670
- if (!res.ledger || res.ledger.length === 0) {
2671
- console.log(`
2672
- No ledger entries yet.`);
2673
- return;
2674
- }
2675
- console.log(`
2676
- Recent ledger (newest first):`);
2677
- for (const e2 of res.ledger) {
2678
- const detail = e2.operationId ?? e2.note ?? "";
2679
- console.log(` ${fmtDelta(e2.delta)} ${e2.reason.padEnd(7)} ${fmtTs(e2.createdAt)} ${detail}`);
2680
- }
2681
- }
2682
- });
2683
- });
2684
-
2685
- // src/cli/commands/admin.ts
2686
- var exports_admin = {};
2687
- __export(exports_admin, {
2688
- default: () => admin_default
2689
- });
2690
- import { spawnSync } from "node:child_process";
2691
- var grant, admin_default;
2692
- var init_admin = __esm(() => {
2693
- init_dist2();
2694
- grant = defineCommand({
2695
- meta: {
2696
- name: "grant",
2697
- description: "Grant credits to a client (looked up by email or clientId). Runs the Convex mutation directly via 'npx convex run'."
2698
- },
2699
- args: {
2700
- email: {
2701
- type: "string",
2702
- description: "Email of the client to grant credits to"
2703
- },
2704
- client: {
2705
- type: "string",
2706
- description: "clientId of the client to grant credits to"
2707
- },
2708
- amount: {
2709
- type: "string",
2710
- description: "Number of credits to grant (positive integer)",
2711
- required: true
2712
- },
2713
- note: {
2714
- type: "string",
2715
- description: "Optional admin note recorded on the ledger row"
2716
- },
2717
- repo: {
2718
- type: "string",
2719
- description: "Path to the vidjutsu Convex repo where 'npx convex run' executes (defaults to cwd)"
2720
- }
2721
- },
2722
- async run({ args }) {
2723
- if (!args.email && !args.client) {
2724
- console.error("Error: provide --email <e> or --client <clientId>");
2725
- process.exit(1);
2726
- }
2727
- const amount = Number(args.amount);
2728
- if (!Number.isInteger(amount) || amount <= 0) {
2729
- console.error("Error: --amount must be a positive integer");
2730
- process.exit(1);
2731
- }
2732
- const payload = { amount };
2733
- if (args.email)
2734
- payload.email = args.email;
2735
- if (args.client)
2736
- payload.clientId = args.client;
2737
- if (args.note)
2738
- payload.note = args.note;
2739
- const cwd = args.repo ?? process.cwd();
2740
- const cmdArgs = [
2741
- "convex",
2742
- "run",
2743
- "services/machineClients:grantCreditsByLookup",
2744
- JSON.stringify(payload)
2745
- ];
2746
- const result = spawnSync("npx", cmdArgs, {
2747
- cwd,
2748
- encoding: "utf-8",
2749
- stdio: ["ignore", "pipe", "pipe"]
2750
- });
2751
- if (result.error) {
2752
- console.error("Error invoking npx convex:", result.error.message);
2753
- process.exit(1);
2754
- }
2755
- if (result.stdout)
2756
- process.stdout.write(result.stdout);
2757
- if (result.stderr)
2758
- process.stderr.write(result.stderr);
2759
- if (result.status !== 0) {
2760
- process.exit(result.status ?? 1);
2761
- }
2762
- }
2763
- });
2764
- admin_default = defineCommand({
2765
- meta: {
2766
- name: "admin",
2767
- description: "Admin operations (credit grants, etc.)"
2768
- },
2769
- subCommands: {
2770
- grant: () => grant
2771
- }
2772
- });
2773
- });
2774
-
2775
2631
  // src/cli/commands/version.ts
2776
2632
  var exports_version = {};
2777
2633
  __export(exports_version, {
@@ -2866,58 +2722,6 @@ var init_update = __esm(() => {
2866
2722
  });
2867
2723
  });
2868
2724
 
2869
- // src/cli/commands/jobs.ts
2870
- var exports_jobs = {};
2871
- __export(exports_jobs, {
2872
- default: () => jobs_default
2873
- });
2874
- async function getJob(jobId) {
2875
- return await apiRequest("GET", `/v1/jobs?id=${encodeURIComponent(jobId)}`);
2876
- }
2877
- var jobs_default;
2878
- var init_jobs = __esm(() => {
2879
- init_dist2();
2880
- init_client();
2881
- jobs_default = defineCommand({
2882
- meta: { name: "jobs", description: "Inspect durable VidJutsu media jobs" },
2883
- subCommands: {
2884
- get: defineCommand({
2885
- meta: { name: "get", description: "Get a job" },
2886
- args: {
2887
- id: { type: "positional", description: "Job ID", required: true }
2888
- },
2889
- async run({ args }) {
2890
- console.log(JSON.stringify(await getJob(args.id), null, 2));
2891
- }
2892
- }),
2893
- wait: defineCommand({
2894
- meta: { name: "wait", description: "Wait for a job to complete" },
2895
- args: {
2896
- id: { type: "positional", description: "Job ID", required: true },
2897
- timeout: { type: "string", description: "Timeout in seconds", default: "600" },
2898
- interval: { type: "string", description: "Poll interval in milliseconds", default: "1000" }
2899
- },
2900
- async run({ args }) {
2901
- const deadline = Date.now() + Number(args.timeout) * 1000;
2902
- while (true) {
2903
- const job = await getJob(args.id);
2904
- if (job?.status === "completed") {
2905
- console.log(JSON.stringify(job, null, 2));
2906
- return;
2907
- }
2908
- if (job?.status === "failed" || job?.status === "cancelled") {
2909
- throw new Error(job.error?.message ?? `Job ${job.status}`);
2910
- }
2911
- if (Date.now() >= deadline)
2912
- throw new Error(`Timed out waiting for ${args.id}`);
2913
- await new Promise((resolve) => setTimeout(resolve, Number(args.interval)));
2914
- }
2915
- }
2916
- })
2917
- }
2918
- });
2919
- });
2920
-
2921
2725
  // src/cli/commands/clone.ts
2922
2726
  var exports_clone = {};
2923
2727
  __export(exports_clone, {
@@ -2934,8 +2738,11 @@ function validateModel(model) {
2934
2738
  function isCharacterId(value) {
2935
2739
  return value.startsWith("char_");
2936
2740
  }
2937
- function characterRef(value) {
2938
- return isCharacterId(value) ? { characterId: value } : { characterImageUrl: value };
2741
+ function requireCharacterId(value) {
2742
+ if (!isCharacterId(value)) {
2743
+ throw new Error("--character must be a stored character id beginning with char_");
2744
+ }
2745
+ return value;
2939
2746
  }
2940
2747
  async function pollCloneVideo(id, { intervalMs = 3000, timeoutMs = 600000 } = {}) {
2941
2748
  const deadline = Date.now() + timeoutMs;
@@ -2954,17 +2761,17 @@ var VALID_MODELS, clone_default;
2954
2761
  var init_clone = __esm(() => {
2955
2762
  init_dist2();
2956
2763
  init_client();
2957
- VALID_MODELS = ["kling", "seedance"];
2764
+ VALID_MODELS = ["kling"];
2958
2765
  clone_default = defineCommand({
2959
2766
  meta: {
2960
2767
  name: "clone",
2961
- description: "Clone a source video's motion onto a new character. About 5 minutes end to end for the full chain. " + "Kling motion control is the default model; output is 480p 9:16. Source clips are capped at 15 seconds. " + "Each step below is metered per call."
2768
+ description: "Clone a source video's motion onto a new character. About 5 minutes end to end for the full chain. " + "Kling 3.0 Motion Control is the supported model. Source clips are capped at 15 seconds. " + "Generation steps share the subscription's daily clone limit."
2962
2769
  },
2963
2770
  subCommands: {
2964
2771
  check: defineCommand({
2965
2772
  meta: {
2966
2773
  name: "check",
2967
- description: "Evaluate whether a source video can be cloned reliably. Metered per call. " + "Exits nonzero when the verdict is weak."
2774
+ description: "Evaluate whether a source video can be cloned reliably. Uses the daily clone limit. " + "Exits nonzero when the verdict is weak."
2968
2775
  },
2969
2776
  args: {
2970
2777
  videoUrl: { type: "positional", description: "Public HTTPS URL of the source video", required: true },
@@ -2989,7 +2796,7 @@ var init_clone = __esm(() => {
2989
2796
  character: defineCommand({
2990
2797
  meta: {
2991
2798
  name: "character",
2992
- description: "Create a reusable, persisted character. Create it once, then reuse the printed id for every " + "clone (see `vidjutsu clone starting-image --character <id>` and `clone run --character <id>`). " + "Metered per call."
2799
+ description: "Create a reusable, persisted character. Create it once, then reuse the printed id for every " + "clone (see `vidjutsu clone starting-image --character <id>` and `clone run --character <id>`). " + "Uses the daily clone limit."
2993
2800
  },
2994
2801
  subCommands: {
2995
2802
  list: defineCommand({
@@ -3034,22 +2841,26 @@ var init_clone = __esm(() => {
3034
2841
  "starting-image": defineCommand({
3035
2842
  meta: {
3036
2843
  name: "starting-image",
3037
- description: "Create a character-swapped starting frame, with no overlays. --character accepts either a stored " + "character id (char_...) or a public HTTPS character image URL. Metered per call."
2844
+ description: "Create a character-swapped starting frame from an extracted first frame and a stored character id. Uses the daily clone limit."
3038
2845
  },
3039
2846
  args: {
3040
2847
  character: {
3041
2848
  type: "string",
3042
- description: "Stored character id (char_...) from `clone character`, or a public HTTPS character image URL",
2849
+ description: "Stored character id (char_...) from `clone character`",
2850
+ required: true
2851
+ },
2852
+ "first-frame": {
2853
+ type: "string",
2854
+ description: "Public HTTPS URL of the source video's extracted first-frame image",
3043
2855
  required: true
3044
2856
  },
3045
- prompt: { type: "string", description: "Instructions for composing the starting frame", required: true },
3046
- source: { type: "string", description: "Optional public HTTPS source video URL to ground the composition" }
2857
+ prompt: { type: "string", description: "Instructions for composing the starting frame", required: true }
3047
2858
  },
3048
2859
  async run({ args }) {
3049
2860
  const result = await apiRequest("POST", "/v1/clones/starting-image", {
3050
- ...characterRef(args.character),
3051
- prompt: args.prompt,
3052
- sourceVideoUrl: args.source
2861
+ firstFrame: args["first-frame"],
2862
+ characterId: requireCharacterId(args.character),
2863
+ prompt: args.prompt
3053
2864
  });
3054
2865
  console.log(result.imageUrl);
3055
2866
  }
@@ -3057,13 +2868,13 @@ var init_clone = __esm(() => {
3057
2868
  video: defineCommand({
3058
2869
  meta: {
3059
2870
  name: "video",
3060
- description: "Clone source motion onto a starting image. Kling motion control is the default; seedance is the " + "alternate model, 9:16 at 480p. Metered per call. Returns a task id (HTTP 202); use --wait to poll to completion."
2871
+ description: "Clone source motion onto a starting image with Kling 3.0 Motion Control. Uses the daily clone limit. " + "Returns a task id (HTTP 202); use --wait to poll to completion."
3061
2872
  },
3062
2873
  args: {
3063
2874
  "starting-image": { type: "string", description: "Public HTTPS URL of the starting frame", required: true },
3064
2875
  source: { type: "string", description: "Public HTTPS URL of the source video whose motion is cloned", required: true },
3065
- model: { type: "string", description: "kling (default) or seedance" },
3066
- prompt: { type: "string", description: "Optional override for the default identity-swap prompt (seedance only)" },
2876
+ model: { type: "string", description: "kling (the only supported model)" },
2877
+ prompt: { type: "string", description: "Optional override for the default motion-control prompt" },
3067
2878
  wait: { type: "boolean", description: "Poll GET status until completed or failed, then print videoUrl" }
3068
2879
  },
3069
2880
  async run({ args }) {
@@ -3097,7 +2908,7 @@ var init_clone = __esm(() => {
3097
2908
  run: defineCommand({
3098
2909
  meta: {
3099
2910
  name: "run",
3100
- description: "Run the full clone chain: download the TikTok video, check it, generate a starting image from a " + "reusable stored character, generate the clone video, and wait for it to finish. About 5 minutes end " + "to end. Characters are reusable: create one once with `vidjutsu clone character --prompt ...`, then " + "pass its id here with --character on every run — this command never generates a new random character. " + "--character is required. Kling motion control is the default model; output is 480p 9:16, source clips " + "are capped at 15 seconds. Each step is metered per call. Stops before spending on generation when the " + "check verdict is weak, unless --force is given."
2911
+ description: "Run the full clone chain: download the TikTok video, check it, generate a starting image from a " + "reusable stored character, generate the clone video, and wait for it to finish. About 5 minutes end " + "to end. Characters are reusable: create one once with `vidjutsu clone character --prompt ...`, then " + "pass its id here with --character on every run — this command never generates a new random character. " + "--character is required. Kling 3.0 Motion Control is the only model; source clips " + "are capped at 15 seconds. Generation steps share the daily clone limit. Stops before generation when the " + "check verdict is weak, unless --force is given. The command extracts frame 0 and passes it directly " + "to the character edit step."
3101
2912
  },
3102
2913
  args: {
3103
2914
  tiktokUrl: { type: "positional", description: "TikTok video URL to clone", required: true },
@@ -3106,16 +2917,17 @@ var init_clone = __esm(() => {
3106
2917
  description: "Required. Stored character id (char_...) from `vidjutsu clone character --prompt ...`. " + "Characters are reusable across runs — create one once, then pass its id here every time."
3107
2918
  },
3108
2919
  "starting-prompt": { type: "string", description: "Prompt for the starting frame composition" },
3109
- model: { type: "string", description: "kling (default) or seedance" },
2920
+ model: { type: "string", description: "kling (the only supported model)" },
3110
2921
  force: { type: "boolean", description: "Proceed even if the clone check verdict is weak" }
3111
2922
  },
3112
2923
  async run({ args }) {
3113
- const model = validateModel(args.model);
2924
+ const model = validateModel(args.model) ?? "kling";
3114
2925
  if (!args.character) {
3115
2926
  console.log("Missing --character. Create a reusable character first with: " + 'vidjutsu clone character --prompt "A neutral, camera-ready presenter"');
3116
2927
  console.log("Then pass its id here: vidjutsu clone run <url> --character char_...");
3117
2928
  process.exit(1);
3118
2929
  }
2930
+ const characterId = requireCharacterId(args.character);
3119
2931
  console.log(`Downloading ${args.tiktokUrl}...`);
3120
2932
  const download = await apiRequest("POST", "/v1/videos/download/tiktok", {
3121
2933
  url: args.tiktokUrl
@@ -3132,11 +2944,21 @@ var init_clone = __esm(() => {
3132
2944
  console.log("Stopping: the source clip is unlikely to clone well. Pass --force to continue anyway.");
3133
2945
  process.exit(1);
3134
2946
  }
2947
+ console.log("Extracting first frame...");
2948
+ const extracted = await apiRequest("POST", "/v1/extract", {
2949
+ mediaUrl: download.url,
2950
+ frames: [0],
2951
+ audio: false,
2952
+ metadata: false
2953
+ });
2954
+ const firstFrame = extracted.frames?.find((frame) => frame.index === 0)?.url ?? extracted.frames?.[0]?.url;
2955
+ if (!firstFrame)
2956
+ throw new Error("The source video did not return an extracted first frame");
3135
2957
  console.log("Generating starting image...");
3136
2958
  const startingImage = await apiRequest("POST", "/v1/clones/starting-image", {
3137
- ...characterRef(args.character),
3138
- prompt: args["starting-prompt"] ?? "Match the framing and pose of the source video's opening frame",
3139
- sourceVideoUrl: download.url
2959
+ firstFrame,
2960
+ characterId,
2961
+ prompt: args["starting-prompt"] ?? "Match the framing and pose of the source video's opening frame"
3140
2962
  });
3141
2963
  console.log("Generating clone video...");
3142
2964
  const accepted = await apiRequest("POST", "/v1/clones/video", {
@@ -3924,7 +3746,7 @@ var main = defineCommand({
3924
3746
  meta: {
3925
3747
  name: "vidjutsu",
3926
3748
  version: VERSION,
3927
- description: "Video intelligence API watch, extract, transcribe, check."
3749
+ description: "Clone short-form videos and process media with the VidJutsu API."
3928
3750
  },
3929
3751
  subCommands: {
3930
3752
  auth: () => Promise.resolve().then(() => (init_auth(), exports_auth)).then((m2) => m2.default),
@@ -3932,11 +3754,8 @@ var main = defineCommand({
3932
3754
  upload: () => Promise.resolve().then(() => (init_upload(), exports_upload)).then((m2) => m2.default),
3933
3755
  subscribe: () => Promise.resolve().then(() => (init_subscribe(), exports_subscribe)).then((m2) => m2.default),
3934
3756
  status: () => Promise.resolve().then(() => (init_status(), exports_status)).then((m2) => m2.default),
3935
- balance: () => Promise.resolve().then(() => (init_balance(), exports_balance)).then((m2) => m2.default),
3936
- admin: () => Promise.resolve().then(() => (init_admin(), exports_admin)).then((m2) => m2.default),
3937
3757
  version: () => Promise.resolve().then(() => (init_version(), exports_version)).then((m2) => m2.default),
3938
3758
  update: () => Promise.resolve().then(() => (init_update(), exports_update)).then((m2) => m2.default),
3939
- jobs: () => Promise.resolve().then(() => (init_jobs(), exports_jobs)).then((m2) => m2.default),
3940
3759
  clone: () => Promise.resolve().then(() => (init_clone(), exports_clone)).then((m2) => m2.default),
3941
3760
  watch: () => Promise.resolve().then(() => (init_watch(), exports_watch)).then((m2) => m2.default),
3942
3761
  extract: () => Promise.resolve().then(() => (init_extract(), exports_extract)).then((m2) => m2.default),
package/dist/client.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import createFetchClient from "openapi-fetch";
2
2
  import type { paths } from "./schema.js";
3
3
  import { type VidJutsuMethods } from "./methods.js";
4
- import { type DistributionNamespaces } from "./distribution.js";
5
- import { type JobsNamespaces } from "./jobs.js";
6
4
  export interface VidJutsuConfig {
7
5
  /** API key. Falls back to VIDJUTSU_API_KEY env var, then ~/.vidjutsu/config.json. */
8
6
  apiKey?: string;
@@ -12,7 +10,7 @@ export interface VidJutsuConfig {
12
10
  /** The raw openapi-fetch client with typed GET/POST/PUT/DELETE */
13
11
  type FetchClient = ReturnType<typeof createFetchClient<paths>>;
14
12
  /** Combined client: typed convenience methods + raw openapi-fetch escape hatch */
15
- export type VidJutsuClient = VidJutsuMethods & DistributionNamespaces & JobsNamespaces & {
13
+ export type VidJutsuClient = VidJutsuMethods & {
16
14
  api: FetchClient;
17
15
  };
18
16
  /**
package/dist/client.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import createFetchClient from "openapi-fetch";
2
2
  import { bindMethods } from "./methods.js";
3
- import { bindDistribution } from "./distribution.js";
4
- import { bindJobs } from "./jobs.js";
5
3
  import { readFileSync, existsSync } from "fs";
6
4
  import { join } from "path";
7
5
  import { homedir } from "os";
@@ -52,7 +50,5 @@ export function createClient(config = {}) {
52
50
  },
53
51
  });
54
52
  const methods = bindMethods(fetchClient);
55
- const distribution = bindDistribution(fetchClient);
56
- const jobs = bindJobs(fetchClient);
57
- return { ...methods, ...distribution, ...jobs, api: fetchClient };
53
+ return { ...methods, api: fetchClient };
58
54
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  export { createClient } from "./client.js";
2
2
  export type { VidJutsuConfig, VidJutsuClient } from "./client.js";
3
3
  export type { VidJutsuMethods } from "./methods.js";
4
- export type { DistributionNamespaces, MutationOptions, WaitOptions } from "./distribution.js";
5
- export type { JobsNamespaces } from "./jobs.js";
6
4
  export type { paths, components } from "./schema.js";
package/dist/methods.d.ts CHANGED
@@ -14,143 +14,137 @@ type QueryParams<P extends keyof paths, M extends keyof paths[P]> = paths[P][M]
14
14
  };
15
15
  } ? Q : never;
16
16
  export interface VidJutsuMethods {
17
- /** Create account Auth required. */
17
+ /** Create account Bearer API key required. */
18
18
  createAccount(body: ReqBody<"/v1/accounts", "post">): ReturnType<Client["POST"]>;
19
- /** Update account Auth required. */
19
+ /** Update account Bearer API key required. */
20
20
  updateAccount(body: ReqBody<"/v1/accounts", "put">, query?: QueryParams<"/v1/accounts", "put">): ReturnType<Client["PUT"]>;
21
- /** List accounts or get by ID Auth required. */
21
+ /** List accounts or get by ID Bearer API key required. */
22
22
  listOrGetAccounts(query?: QueryParams<"/v1/accounts", "get">): ReturnType<Client["GET"]>;
23
- /** Delete account Auth required. */
23
+ /** Delete account Bearer API key required. */
24
24
  deleteAccount(query?: QueryParams<"/v1/accounts", "delete">): ReturnType<Client["DELETE"]>;
25
25
  /** Recover API key Public endpoint. */
26
26
  recoverApiKey(body: ReqBody<"/v1/api_keys/recover", "post">): ReturnType<Client["POST"]>;
27
- /** Rotate API key Auth required. */
27
+ /** Rotate API key Bearer API key required. */
28
28
  rotateApiKey(): ReturnType<Client["POST"]>;
29
- /** Create asset from existing URL Auth required. */
29
+ /** Create asset from existing URL Bearer API key required. */
30
30
  createAsset(body: ReqBody<"/v1/assets", "post">): ReturnType<Client["POST"]>;
31
- /** Update asset metadata Auth required. */
31
+ /** Update asset metadata Bearer API key required. */
32
32
  updateAsset(body: ReqBody<"/v1/assets", "put">, query?: QueryParams<"/v1/assets", "put">): ReturnType<Client["PUT"]>;
33
- /** List assets or get by ID Auth required. */
33
+ /** List assets or get by ID Bearer API key required. */
34
34
  listOrGetAssets(query?: QueryParams<"/v1/assets", "get">): ReturnType<Client["GET"]>;
35
- /** Delete asset (soft) Auth required. */
35
+ /** Delete asset (soft) Bearer API key required. */
36
36
  deleteAsset(query?: QueryParams<"/v1/assets", "delete">): ReturnType<Client["DELETE"]>;
37
- /** Agent registration (auth.md) */
37
+ /** Agent registration (auth.md) Public endpoint. */
38
38
  signupAgent(body: ReqBody<"/v1/auth/agent", "post">): ReturnType<Client["POST"]>;
39
- /** Initiate claim flow */
39
+ /** Initiate claim flow Public endpoint. */
40
40
  agentClaim(body: ReqBody<"/v1/auth/agent/claim", "post">): ReturnType<Client["POST"]>;
41
- /** Complete claim flow */
41
+ /** Complete claim flow Public endpoint. */
42
42
  agentClaimComplete(body: ReqBody<"/v1/auth/agent/claim/complete", "post">): ReturnType<Client["POST"]>;
43
- /** Revoke credential */
43
+ /** Revoke credential Public endpoint. */
44
44
  agentRevoke(): ReturnType<Client["POST"]>;
45
45
  /** Confirm email verification code Public endpoint. */
46
46
  confirmVerification(body: ReqBody<"/v1/auth/verify/confirm", "post">): ReturnType<Client["POST"]>;
47
47
  /** Request email verification code Public endpoint. */
48
48
  requestVerification(body: ReqBody<"/v1/auth/verify/request", "post">): ReturnType<Client["POST"]>;
49
- /** Create a reusable, persisted character image */
49
+ /** Create a reusable, persisted character image Active subscription and daily quota required. */
50
50
  createCharacter(body: ReqBody<"/v1/characters", "post">): ReturnType<Client["POST"]>;
51
- /** List your persisted characters */
51
+ /** List your persisted characters Bearer API key required. */
52
52
  listCharacters(): ReturnType<Client["GET"]>;
53
- /** Fetch a persisted character by id */
53
+ /** Fetch a persisted character by id Bearer API key required. */
54
54
  getCharacter(id: string): ReturnType<Client["GET"]>;
55
- /** Check spec Auth required. 5 credits. */
55
+ /** Check spec Active subscription and daily quota required. */
56
56
  checkSpec(body: ReqBody<"/v1/check", "post">): ReturnType<Client["POST"]>;
57
- /** Get check rules Auth required. */
57
+ /** Get check rules Bearer API key required. */
58
58
  getCheckRules(): ReturnType<Client["GET"]>;
59
- /** Update check rules Auth required. */
59
+ /** Update check rules Bearer API key required. */
60
60
  updateCheckRules(body: ReqBody<"/v1/check/rules", "put">): ReturnType<Client["PUT"]>;
61
- /** Evaluate whether a source video can be cloned reliably */
61
+ /** Evaluate whether a source video can be cloned reliably Active subscription and daily quota required. */
62
62
  cloneCheck(body: ReqBody<"/v1/clones/check", "post">): ReturnType<Client["POST"]>;
63
- /** Create a character-swapped starting frame */
63
+ /** Create a character-swapped starting frame Active subscription and daily quota required. */
64
64
  cloneStartingImage(body: ReqBody<"/v1/clones/starting-image", "post">): ReturnType<Client["POST"]>;
65
- /** Clone source motion with Kling motion control or Seedance */
65
+ /** Clone source motion with Kling 3.0 Motion Control Active subscription and daily quota required. */
66
66
  cloneVideo(body: ReqBody<"/v1/clones/video", "post">): ReturnType<Client["POST"]>;
67
- /** Poll a clone video task */
67
+ /** Poll a clone video task Bearer API key required. */
68
68
  getCloneVideo(id: string): ReturnType<Client["GET"]>;
69
- /** Check prompt compliance */
69
+ /** Check prompt compliance Active subscription and daily quota required. */
70
70
  checkCompliancePrompt(body: ReqBody<"/v1/compliance/prompt", "post">): ReturnType<Client["POST"]>;
71
- /** Check video compliance */
71
+ /** Check video compliance Active subscription and daily quota required. */
72
72
  checkComplianceVideo(body: ReqBody<"/v1/compliance/video", "post">): ReturnType<Client["POST"]>;
73
73
  /** Check checkout status Public endpoint. */
74
74
  getCheckoutStatus(query?: QueryParams<"/v1/credits/status", "get">): ReturnType<Client["GET"]>;
75
- /** Burn fine-print disclaimer onto video */
75
+ /** Burn fine-print disclaimer onto video Active subscription and daily quota required. */
76
76
  createDisclaimer(body: ReqBody<"/v1/disclaimer", "post">): ReturnType<Client["POST"]>;
77
- /** Inspect a distribution job Auth required. */
78
- getDistributionJob(query?: QueryParams<"/v1/distribution/jobs", "get">): ReturnType<Client["GET"]>;
79
- /** Extract from media Auth required. 5 credits. */
77
+ /** Extract from media Active subscription and daily quota required. */
80
78
  extractMedia(body: ReqBody<"/v1/extract", "post">): ReturnType<Client["POST"]>;
81
79
  /** API info Public endpoint. */
82
80
  getInfo(): ReturnType<Client["GET"]>;
83
- /** Inspect a durable VidJutsu media job Auth required. */
84
- getJob(query?: QueryParams<"/v1/jobs", "get">): ReturnType<Client["GET"]>;
85
- /** Burn text overlay onto video Auth required. 5 credits. */
81
+ /** Burn text overlay onto video Active subscription and daily quota required. */
86
82
  createOverlay(body: ReqBody<"/v1/overlay", "post">): ReturnType<Client["POST"]>;
87
- /** Create post Auth required. */
83
+ /** Create post Bearer API key required. */
88
84
  createPost(body: ReqBody<"/v1/posts", "post">): ReturnType<Client["POST"]>;
89
- /** Update post Auth required. */
85
+ /** Update post Bearer API key required. */
90
86
  updatePost(body: ReqBody<"/v1/posts", "put">, query?: QueryParams<"/v1/posts", "put">): ReturnType<Client["PUT"]>;
91
- /** List posts or get by ID Auth required. */
87
+ /** List posts or get by ID Bearer API key required. */
92
88
  listOrGetPosts(query?: QueryParams<"/v1/posts", "get">): ReturnType<Client["GET"]>;
93
- /** Delete post Auth required. */
89
+ /** Delete post Bearer API key required. */
94
90
  deletePost(query?: QueryParams<"/v1/posts", "delete">): ReturnType<Client["DELETE"]>;
95
91
  /** Get pricing Public endpoint. */
96
92
  getPricing(): ReturnType<Client["GET"]>;
97
- /** Create editor project */
93
+ /** Create editor project Bearer API key required. */
98
94
  createEditorProject(body: ReqBody<"/v1/projects", "post">): ReturnType<Client["POST"]>;
99
- /** Update editor project */
95
+ /** Update editor project Bearer API key required. */
100
96
  updateEditorProject(body: ReqBody<"/v1/projects", "put">, query?: QueryParams<"/v1/projects", "put">): ReturnType<Client["PUT"]>;
101
- /** List projects or get by ID */
97
+ /** List projects or get by ID Bearer API key required. */
102
98
  listOrGetEditorProjects(query?: QueryParams<"/v1/projects", "get">): ReturnType<Client["GET"]>;
103
- /** Archive editor project (soft) */
99
+ /** Archive editor project (soft) Bearer API key required. */
104
100
  deleteEditorProject(query?: QueryParams<"/v1/projects", "delete">): ReturnType<Client["DELETE"]>;
105
- /** Create reference Auth required. */
101
+ /** Create reference Bearer API key required. */
106
102
  createReference(body: ReqBody<"/v1/references", "post">): ReturnType<Client["POST"]>;
107
- /** Update reference Auth required. */
103
+ /** Update reference Bearer API key required. */
108
104
  updateReference(body: ReqBody<"/v1/references", "put">, query?: QueryParams<"/v1/references", "put">): ReturnType<Client["PUT"]>;
109
- /** List references or get by ID Auth required. */
105
+ /** List references or get by ID Bearer API key required. */
110
106
  listOrGetReferences(query?: QueryParams<"/v1/references", "get">): ReturnType<Client["GET"]>;
111
- /** Delete reference Auth required. */
107
+ /** Delete reference Bearer API key required. */
112
108
  deleteReference(query?: QueryParams<"/v1/references", "delete">): ReturnType<Client["DELETE"]>;
113
- /** Scrape Instagram post or reel Auth required. 1 credits. */
109
+ /** Scrape Instagram post or reel Active subscription and daily quota required. */
114
110
  scrapeInstagramPost(body: ReqBody<"/v1/scrape/instagram/post", "post">): ReturnType<Client["POST"]>;
115
- /** Scrape Instagram post comments Auth required. 1 credits. */
111
+ /** Scrape Instagram post comments Active subscription and daily quota required. */
116
112
  scrapeInstagramPostComments(body: ReqBody<"/v1/scrape/instagram/post/comments", "post">): ReturnType<Client["POST"]>;
117
- /** Scrape Instagram profile Auth required. 1 credits. */
113
+ /** Scrape Instagram profile Active subscription and daily quota required. */
118
114
  scrapeInstagramProfile(body: ReqBody<"/v1/scrape/instagram/profile", "post">): ReturnType<Client["POST"]>;
119
- /** Scrape Instagram user posts Auth required. 1 credits. */
115
+ /** Scrape Instagram user posts Active subscription and daily quota required. */
120
116
  scrapeInstagramUserPosts(body: ReqBody<"/v1/scrape/instagram/user/posts", "post">): ReturnType<Client["POST"]>;
121
- /** Scrape Instagram user reels Auth required. 1 credits. */
117
+ /** Scrape Instagram user reels Active subscription and daily quota required. */
122
118
  scrapeInstagramUserReels(body: ReqBody<"/v1/scrape/instagram/user/reels", "post">): ReturnType<Client["POST"]>;
123
- /** Scrape TikTok profile Auth required. 1 credits. */
119
+ /** Scrape TikTok profile Active subscription and daily quota required. */
124
120
  scrapeTikTokProfile(body: ReqBody<"/v1/scrape/tiktok/profile", "post">): ReturnType<Client["POST"]>;
125
- /** Scrape TikTok profile videos Auth required. 1 credits. */
121
+ /** Scrape TikTok profile videos Active subscription and daily quota required. */
126
122
  scrapeTikTokProfileVideos(body: ReqBody<"/v1/scrape/tiktok/profile/videos", "post">): ReturnType<Client["POST"]>;
127
- /** Scrape TikTok user search Auth required. 1 credits. */
123
+ /** Scrape TikTok user search Active subscription and daily quota required. */
128
124
  scrapeTikTokSearchUsers(body: ReqBody<"/v1/scrape/tiktok/search/users", "post">): ReturnType<Client["POST"]>;
129
- /** Scrape TikTok trending feed Auth required. 1 credits. */
125
+ /** Scrape TikTok trending feed Active subscription and daily quota required. */
130
126
  scrapeTikTokTrending(body: ReqBody<"/v1/scrape/tiktok/trending", "post">): ReturnType<Client["POST"]>;
131
- /** Scrape TikTok video Auth required. 1 credits. */
127
+ /** Scrape TikTok video Active subscription and daily quota required. */
132
128
  scrapeTikTokVideo(body: ReqBody<"/v1/scrape/tiktok/video", "post">): ReturnType<Client["POST"]>;
133
- /** Scrape TikTok video comments Auth required. 1 credits. */
129
+ /** Scrape TikTok video comments Active subscription and daily quota required. */
134
130
  scrapeTikTokVideoComments(body: ReqBody<"/v1/scrape/tiktok/video/comments", "post">): ReturnType<Client["POST"]>;
135
- /** Scrape TikTok video transcript Auth required. 1 credits. */
131
+ /** Scrape TikTok video transcript Active subscription and daily quota required. */
136
132
  scrapeTikTokVideoTranscript(body: ReqBody<"/v1/scrape/tiktok/video/transcript", "post">): ReturnType<Client["POST"]>;
137
133
  /** Create subscription Public endpoint. */
138
134
  createSubscription(body: ReqBody<"/v1/subscribe", "post">): ReturnType<Client["POST"]>;
139
- /** Transcribe media Auth required. 10 credits. */
135
+ /** Transcribe media Active subscription and daily quota required. */
140
136
  transcribeMedia(body: ReqBody<"/v1/transcribe", "post">): ReturnType<Client["POST"]>;
141
- /** Upload file Auth required. */
142
- uploadFile(): ReturnType<Client["POST"]>;
143
- /** Upload from URL Auth required. */
137
+ /** Upload file Bearer API key required. */
138
+ uploadFile(body: Blob | ArrayBuffer | ReadableStream): ReturnType<Client["POST"]>;
139
+ /** Upload from URL Bearer API key required. */
144
140
  uploadFromUrl(body: ReqBody<"/v1/upload/url", "post">): ReturnType<Client["POST"]>;
145
- /** Get daily usage */
141
+ /** Get daily usage Bearer API key required. */
146
142
  getUsage(): ReturnType<Client["GET"]>;
147
- /** Add a video from an uploaded asset or direct MP4 URL Auth required. */
148
- addVideo(body: ReqBody<"/v1/videos/add", "post">): ReturnType<Client["POST"]>;
149
- /** Import an Instagram video into VidJutsu Auth required. 1 credits. */
143
+ /** Import an Instagram video into VidJutsu Active subscription and daily quota required. */
150
144
  downloadInstagramVideo(body: ReqBody<"/v1/videos/download/instagram", "post">): ReturnType<Client["POST"]>;
151
- /** Import a TikTok video into VidJutsu Auth required. 1 credits. */
145
+ /** Import a TikTok video into VidJutsu Active subscription and daily quota required. */
152
146
  downloadTikTokVideo(body: ReqBody<"/v1/videos/download/tiktok", "post">): ReturnType<Client["POST"]>;
153
- /** Watch media Auth required. 10 credits. */
147
+ /** Watch media Active subscription and daily quota required. */
154
148
  watchMedia(body: ReqBody<"/v1/watch", "post">): ReturnType<Client["POST"]>;
155
149
  }
156
150
  export declare function bindMethods(client: Client): VidJutsuMethods;
package/dist/methods.js CHANGED
@@ -90,18 +90,12 @@ export function bindMethods(client) {
90
90
  createDisclaimer(body) {
91
91
  return client.POST("/v1/disclaimer", { body });
92
92
  },
93
- getDistributionJob(query) {
94
- return client.GET("/v1/distribution/jobs", { params: { query } });
95
- },
96
93
  extractMedia(body) {
97
94
  return client.POST("/v1/extract", { body });
98
95
  },
99
96
  getInfo() {
100
97
  return client.GET("/v1/info", {});
101
98
  },
102
- getJob(query) {
103
- return client.GET("/v1/jobs", { params: { query } });
104
- },
105
99
  createOverlay(body) {
106
100
  return client.POST("/v1/overlay", { body });
107
101
  },
@@ -186,8 +180,12 @@ export function bindMethods(client) {
186
180
  transcribeMedia(body) {
187
181
  return client.POST("/v1/transcribe", { body });
188
182
  },
189
- uploadFile() {
190
- return client.POST("/v1/upload", {});
183
+ uploadFile(body) {
184
+ return client.POST("/v1/upload", {
185
+ body: body,
186
+ bodySerializer: (b) => b,
187
+ headers: { "Content-Type": "application/octet-stream" },
188
+ });
191
189
  },
192
190
  uploadFromUrl(body) {
193
191
  return client.POST("/v1/upload/url", { body });
@@ -195,9 +193,6 @@ export function bindMethods(client) {
195
193
  getUsage() {
196
194
  return client.GET("/v1/usage", {});
197
195
  },
198
- addVideo(body) {
199
- return client.POST("/v1/videos/add", { body });
200
- },
201
196
  downloadInstagramVideo(body) {
202
197
  return client.POST("/v1/videos/download/instagram", { body });
203
198
  },
package/install.sh CHANGED
@@ -43,45 +43,6 @@ detect_platform() {
43
43
  info "Detected platform: ${BOLD}${PLATFORM}${RESET}"
44
44
  }
45
45
 
46
- # --- Prerequisites ---
47
-
48
- ensure_node() {
49
- if command -v node >/dev/null 2>&1; then
50
- success "Node.js $(node --version) found"
51
- return
52
- fi
53
-
54
- error "Node.js is required. Install it from https://nodejs.org"
55
- }
56
-
57
- ensure_bun() {
58
- if command -v bun >/dev/null 2>&1; then
59
- success "Bun $(bun --version) found"
60
- return
61
- fi
62
-
63
- info "Installing Bun..."
64
- curl -fsSL https://bun.sh/install | bash
65
- export BUN_INSTALL="$HOME/.bun"
66
- export PATH="$BUN_INSTALL/bin:$PATH"
67
- success "Bun installed: $(bun --version)"
68
- }
69
-
70
- ensure_typescript() {
71
- if command -v tsc >/dev/null 2>&1; then
72
- success "TypeScript $(tsc --version) found"
73
- return
74
- fi
75
-
76
- info "Installing TypeScript globally..."
77
- if command -v bun >/dev/null 2>&1; then
78
- bun add -g typescript
79
- elif command -v npm >/dev/null 2>&1; then
80
- npm install -g typescript
81
- fi
82
- success "TypeScript installed"
83
- }
84
-
85
46
  # --- Version Detection ---
86
47
 
87
48
  get_version() {
@@ -180,9 +141,6 @@ main() {
180
141
  printf "\n${BOLD} VidJutsu CLI Installer${RESET}\n\n"
181
142
 
182
143
  detect_platform
183
- ensure_node
184
- ensure_bun
185
- ensure_typescript
186
144
  get_version
187
145
  install_binary
188
146
  verify
@@ -190,7 +148,7 @@ main() {
190
148
  printf "\n${GREEN}${BOLD} ✓ VidJutsu CLI v${VERSION} installed successfully${RESET}\n"
191
149
  printf "\n Get started:\n"
192
150
  printf " ${CYAN}vidjutsu auth${RESET} — authenticate your account\n"
193
- printf " ${CYAN}vidjutsu generate${RESET} generate AI video\n"
151
+ printf " ${CYAN}vidjutsu clone --help${RESET} clone a staged short video\n"
194
152
  printf " ${CYAN}vidjutsu --help${RESET} — see all commands\n\n"
195
153
  }
196
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidjutsu",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "VidJutsu — video intelligence API. SDK + CLI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,6 +20,8 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "generate": "bun scripts/generate-schema.ts && bun scripts/generate-methods.ts",
23
+ "test": "bun test",
24
+ "check:release": "bun scripts/check-release.ts",
23
25
  "build": "bun run build:sdk && bun run build:cli",
24
26
  "build:sdk": "tsc",
25
27
  "build:cli": "bun build src/cli/index.ts --outfile dist/cli/index.mjs --target node",
@@ -29,7 +31,7 @@
29
31
  "build:binary:linux-x64": "bun build src/cli/index.ts --compile --target=bun-linux-x64 --outfile dist/vidjutsu-linux-x64",
30
32
  "build:binary:linux-arm64": "bun build src/cli/index.ts --compile --target=bun-linux-arm64 --outfile dist/vidjutsu-linux-arm64",
31
33
  "dev": "bun run src/cli/index.ts",
32
- "prepublishOnly": "bun run generate && bun run build"
34
+ "prepublishOnly": "bun run generate && bun run check:release && bun test && bun run build"
33
35
  },
34
36
  "dependencies": {
35
37
  "citty": "^0.1",
@@ -1,23 +0,0 @@
1
- import type createFetchClient from "openapi-fetch";
2
- import type { components, paths } from "./schema.js";
3
- type FetchClient = ReturnType<typeof createFetchClient<paths>>;
4
- type Job = components["schemas"]["DistributionJob"];
5
- type AddVideoRequest = components["schemas"]["AddVideoRequest"];
6
- export interface MutationOptions {
7
- idempotencyKey?: string;
8
- }
9
- export interface WaitOptions {
10
- timeoutMs?: number;
11
- pollIntervalMs?: number;
12
- }
13
- export interface DistributionNamespaces {
14
- videos: {
15
- add(body: AddVideoRequest, options?: MutationOptions): Promise<Job>;
16
- };
17
- jobs: {
18
- get(jobId: string): Promise<Job>;
19
- waitForResult(jobId: string, options?: WaitOptions): Promise<Job>;
20
- };
21
- }
22
- export declare function bindDistribution(client: FetchClient): DistributionNamespaces;
23
- export {};
@@ -1,56 +0,0 @@
1
- export function bindDistribution(client) {
2
- const jobs = {
3
- async get(jobId) {
4
- const result = await client.GET("/v1/distribution/jobs", {
5
- params: { query: { id: jobId } },
6
- });
7
- return requireData(result, "get distribution job");
8
- },
9
- async waitForResult(jobId, options = {}) {
10
- const timeoutMs = options.timeoutMs ?? 10 * 60_000;
11
- const pollIntervalMs = options.pollIntervalMs ?? 1_000;
12
- const deadline = Date.now() + timeoutMs;
13
- while (true) {
14
- const job = await jobs.get(jobId);
15
- if (job.status === "completed")
16
- return job;
17
- if (job.status === "failed" || job.status === "cancelled") {
18
- throw new Error(job.error?.message ?? `Distribution job ${job.status}`);
19
- }
20
- if (Date.now() >= deadline)
21
- throw new Error(`Timed out waiting for distribution job ${jobId}`);
22
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
23
- }
24
- },
25
- };
26
- return {
27
- videos: {
28
- async add(body, options) {
29
- const result = await client.POST("/v1/videos/add", {
30
- body,
31
- params: { header: idempotencyHeader(options) },
32
- });
33
- return requireData(result, "add video");
34
- },
35
- },
36
- jobs,
37
- };
38
- }
39
- function idempotencyHeader(options) {
40
- return options?.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : {};
41
- }
42
- function requireData(result, operation) {
43
- if (result.data !== undefined)
44
- return result.data;
45
- const message = extractErrorMessage(result.error) ?? `Vidjutsu failed to ${operation} (HTTP ${result.response.status})`;
46
- throw new Error(message);
47
- }
48
- function extractErrorMessage(error) {
49
- if (!error || typeof error !== "object")
50
- return undefined;
51
- if ("message" in error && typeof error.message === "string")
52
- return error.message;
53
- if ("error" in error && typeof error.error === "string")
54
- return error.error;
55
- return undefined;
56
- }
package/dist/jobs.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import type createFetchClient from "openapi-fetch";
2
- import type { components, paths } from "./schema.js";
3
- import type { WaitOptions } from "./distribution.js";
4
- type FetchClient = ReturnType<typeof createFetchClient<paths>>;
5
- type MediaJob = components["schemas"]["MediaJob"];
6
- export interface JobsNamespaces {
7
- mediaJobs: {
8
- get(jobId: string): Promise<MediaJob>;
9
- awaitJob(jobId: string, options?: WaitOptions): Promise<MediaJob>;
10
- };
11
- awaitJob(jobId: string, options?: WaitOptions): Promise<MediaJob>;
12
- }
13
- export declare function bindJobs(client: FetchClient): JobsNamespaces;
14
- export {};
package/dist/jobs.js DELETED
@@ -1,50 +0,0 @@
1
- export function bindJobs(client) {
2
- const mediaJobs = {
3
- async get(jobId) {
4
- const result = await client.GET("/v1/jobs", {
5
- params: { query: { id: jobId } },
6
- });
7
- return requireData(result, "get media job");
8
- },
9
- async awaitJob(jobId, options = {}) {
10
- const timeoutMs = options.timeoutMs ?? 10 * 60_000;
11
- const pollIntervalMs = options.pollIntervalMs ?? 1_000;
12
- if (timeoutMs <= 0 || pollIntervalMs <= 0) {
13
- throw new Error("timeoutMs and pollIntervalMs must be positive");
14
- }
15
- const deadline = Date.now() + timeoutMs;
16
- while (true) {
17
- const job = await mediaJobs.get(jobId);
18
- if (job.status === "completed")
19
- return job;
20
- if (job.status === "failed" || job.status === "cancelled") {
21
- throw new Error(job.error?.message ?? `Media job ${job.status}`);
22
- }
23
- if (Date.now() >= deadline) {
24
- throw new Error(`Timed out waiting for media job ${jobId}`);
25
- }
26
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
27
- }
28
- },
29
- };
30
- return {
31
- mediaJobs,
32
- awaitJob: mediaJobs.awaitJob,
33
- };
34
- }
35
- function requireData(result, operation) {
36
- if (result.data !== undefined)
37
- return result.data;
38
- const message = extractErrorMessage(result.error) ??
39
- `VidJutsu failed to ${operation} (HTTP ${result.response.status})`;
40
- throw new Error(message);
41
- }
42
- function extractErrorMessage(error) {
43
- if (!error || typeof error !== "object")
44
- return undefined;
45
- if ("message" in error && typeof error.message === "string")
46
- return error.message;
47
- if ("error" in error && typeof error.error === "string")
48
- return error.error;
49
- return undefined;
50
- }