clipugc 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +249 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +31 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/account.d.ts +12 -0
  6. package/dist/commands/account.js +48 -0
  7. package/dist/commands/account.js.map +1 -0
  8. package/dist/commands/auth.d.ts +14 -0
  9. package/dist/commands/auth.js +152 -0
  10. package/dist/commands/auth.js.map +1 -0
  11. package/dist/commands/characters.d.ts +3 -0
  12. package/dist/commands/characters.js +261 -0
  13. package/dist/commands/characters.js.map +1 -0
  14. package/dist/commands/config.d.ts +5 -0
  15. package/dist/commands/config.js +69 -0
  16. package/dist/commands/config.js.map +1 -0
  17. package/dist/commands/credits.d.ts +2 -0
  18. package/dist/commands/credits.js +117 -0
  19. package/dist/commands/credits.js.map +1 -0
  20. package/dist/commands/hooks.d.ts +2 -0
  21. package/dist/commands/hooks.js +32 -0
  22. package/dist/commands/hooks.js.map +1 -0
  23. package/dist/commands/images.d.ts +3 -0
  24. package/dist/commands/images.js +257 -0
  25. package/dist/commands/images.js.map +1 -0
  26. package/dist/commands/videos.d.ts +2 -0
  27. package/dist/commands/videos.js +272 -0
  28. package/dist/commands/videos.js.map +1 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +24 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/services/api.d.ts +40 -0
  33. package/dist/services/api.js +139 -0
  34. package/dist/services/api.js.map +1 -0
  35. package/dist/services/characters.service.d.ts +135 -0
  36. package/dist/services/characters.service.js +276 -0
  37. package/dist/services/characters.service.js.map +1 -0
  38. package/dist/services/hooks.service.d.ts +9 -0
  39. package/dist/services/hooks.service.js +23 -0
  40. package/dist/services/hooks.service.js.map +1 -0
  41. package/dist/services/images.service.d.ts +95 -0
  42. package/dist/services/images.service.js +191 -0
  43. package/dist/services/images.service.js.map +1 -0
  44. package/dist/services/upload.service.d.ts +46 -0
  45. package/dist/services/upload.service.js +134 -0
  46. package/dist/services/upload.service.js.map +1 -0
  47. package/dist/services/user.service.d.ts +80 -0
  48. package/dist/services/user.service.js +35 -0
  49. package/dist/services/user.service.js.map +1 -0
  50. package/dist/services/videos.service.d.ts +100 -0
  51. package/dist/services/videos.service.js +187 -0
  52. package/dist/services/videos.service.js.map +1 -0
  53. package/dist/types/index.d.ts +47 -0
  54. package/dist/types/index.js +18 -0
  55. package/dist/types/index.js.map +1 -0
  56. package/dist/utils/config.d.ts +18 -0
  57. package/dist/utils/config.js +65 -0
  58. package/dist/utils/config.js.map +1 -0
  59. package/dist/utils/errors.d.ts +52 -0
  60. package/dist/utils/errors.js +74 -0
  61. package/dist/utils/errors.js.map +1 -0
  62. package/dist/utils/logger.d.ts +11 -0
  63. package/dist/utils/logger.js +28 -0
  64. package/dist/utils/logger.js.map +1 -0
  65. package/dist/utils/output.d.ts +16 -0
  66. package/dist/utils/output.js +53 -0
  67. package/dist/utils/output.js.map +1 -0
  68. package/dist/utils/poll.d.ts +37 -0
  69. package/dist/utils/poll.js +94 -0
  70. package/dist/utils/poll.js.map +1 -0
  71. package/dist/utils/prompt.d.ts +4 -0
  72. package/dist/utils/prompt.js +55 -0
  73. package/dist/utils/prompt.js.map +1 -0
  74. package/dist/version.d.ts +1 -0
  75. package/dist/version.js +12 -0
  76. package/dist/version.js.map +1 -0
  77. package/package.json +49 -0
@@ -0,0 +1,139 @@
1
+ import { APP_CODES } from '../types/index.js';
2
+ import { ApiError, AuthError, InsufficientCreditsError, NetworkError, NotFoundError, PremiumRequiredError, ValidationError, } from '../utils/errors.js';
3
+ import { resolveApiBaseUrl, resolveApiKey } from '../utils/config.js';
4
+ /**
5
+ * Thin fetch client for the ClipUGC REST API.
6
+ * Unwraps the {statusCode, errorMessage, data, message} envelope and throws
7
+ * typed errors — it never exits the process.
8
+ */
9
+ export class ApiClient {
10
+ baseUrl;
11
+ apiKey;
12
+ constructor(baseUrl, apiKey) {
13
+ this.baseUrl = baseUrl;
14
+ this.apiKey = apiKey;
15
+ }
16
+ async get(path, options = {}) {
17
+ return (await this.request('GET', path, options)).data;
18
+ }
19
+ async post(path, options = {}) {
20
+ return (await this.request('POST', path, options)).data;
21
+ }
22
+ /** POST multipart/form-data (file uploads). fetch sets the boundary header itself. */
23
+ async postForm(path, form) {
24
+ return (await this.request('POST', path, { form })).data;
25
+ }
26
+ async patch(path, options = {}) {
27
+ return (await this.request('PATCH', path, options)).data;
28
+ }
29
+ async delete(path, options = {}) {
30
+ return (await this.request('DELETE', path, options)).data;
31
+ }
32
+ /** Full request returning both data and the envelope message. */
33
+ async request(method, path, options = {}) {
34
+ const url = this.buildUrl(path, options.query);
35
+ const headers = {
36
+ Accept: 'application/json',
37
+ };
38
+ if (this.apiKey)
39
+ headers.Authorization = `Bearer ${this.apiKey}`;
40
+ // For FormData, fetch sets Content-Type (with boundary) itself.
41
+ if (options.body !== undefined && options.form === undefined)
42
+ headers['Content-Type'] = 'application/json';
43
+ const body = options.form ?? (options.body !== undefined ? JSON.stringify(options.body) : undefined);
44
+ // Retry idempotent reads on transient failures (network blips, rate limits,
45
+ // gateway/5xx while the server is briefly saturated).
46
+ const retryable = method === 'GET' || method === 'HEAD';
47
+ const transientStatuses = new Set([408, 425, 429, 500, 502, 503, 504]);
48
+ const maxAttempts = retryable ? 4 : 1;
49
+ let response;
50
+ let lastNetworkErr;
51
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
52
+ try {
53
+ response = await fetch(url, { method, headers, body });
54
+ }
55
+ catch (err) {
56
+ lastNetworkErr = err;
57
+ response = undefined;
58
+ }
59
+ if (response && !(retryable && transientStatuses.has(response.status)))
60
+ break;
61
+ if (attempt < maxAttempts) {
62
+ await new Promise((r) => setTimeout(r, 800 * attempt)); // 0.8s, 1.6s, 2.4s
63
+ }
64
+ }
65
+ if (!response) {
66
+ const reason = lastNetworkErr instanceof Error ? lastNetworkErr.message : String(lastNetworkErr);
67
+ throw new NetworkError(`Could not reach ClipUGC API at ${this.baseUrl} (${reason}). Is the server up? Check \`clipugc config get apiBaseUrl\`.`);
68
+ }
69
+ let envelope;
70
+ try {
71
+ envelope = (await response.json());
72
+ }
73
+ catch {
74
+ throw new ApiError(`Unexpected non-JSON response from API (HTTP ${response.status}).`, response.status);
75
+ }
76
+ if (envelope.statusCode === undefined) {
77
+ // Not the standard envelope (e.g. framework-level 401/422/500 JSON)
78
+ if (response.status === 401)
79
+ throw new AuthError();
80
+ if (response.status === 422) {
81
+ // Laravel validation error: { message, errors: { field: [msgs] } }
82
+ const raw = envelope;
83
+ const details = raw.errors ? Object.values(raw.errors).flat().join(' ') : '';
84
+ throw new ValidationError(details || raw.message || 'The request was invalid (HTTP 422).');
85
+ }
86
+ throw new ApiError(`Unexpected API response (HTTP ${response.status}).`, response.status);
87
+ }
88
+ return this.unwrap(envelope);
89
+ }
90
+ unwrap(envelope) {
91
+ const { statusCode, errorMessage, data, message } = envelope;
92
+ if (statusCode === APP_CODES.OK || statusCode === APP_CODES.CREATED) {
93
+ return { data, message: message ?? null };
94
+ }
95
+ const msg = errorMessage || message || `API error (code ${statusCode})`;
96
+ switch (statusCode) {
97
+ case APP_CODES.VALIDATION:
98
+ throw new ValidationError(msg);
99
+ case APP_CODES.UNAUTHORIZED:
100
+ throw new AuthError(errorMessage || undefined);
101
+ case APP_CODES.NOT_FOUND:
102
+ throw new NotFoundError(errorMessage || undefined);
103
+ case APP_CODES.PREMIUM_REQUIRED:
104
+ throw new PremiumRequiredError(errorMessage || undefined);
105
+ case APP_CODES.INSUFFICIENT_CREDITS: {
106
+ const detail = errorMessage ? ` ${errorMessage}` : '';
107
+ throw new InsufficientCreditsError(`Insufficient credits.${detail} Check your balance with \`clipugc credits\`.`);
108
+ }
109
+ default:
110
+ throw new ApiError(msg, statusCode);
111
+ }
112
+ }
113
+ buildUrl(path, query) {
114
+ const base = this.baseUrl.replace(/\/+$/, '');
115
+ const p = path.startsWith('/') ? path : `/${path}`;
116
+ const url = new URL(base + p);
117
+ if (query) {
118
+ for (const [key, value] of Object.entries(query)) {
119
+ if (value !== undefined && value !== null && value !== '') {
120
+ url.searchParams.set(key, String(value));
121
+ }
122
+ }
123
+ }
124
+ return url.toString();
125
+ }
126
+ }
127
+ /**
128
+ * Build a client from persisted config/env. Throws AuthError when no API key
129
+ * is available (unless allowAnonymous — used by `auth login` itself).
130
+ */
131
+ export async function createApiClient(opts = {}) {
132
+ const baseUrl = await resolveApiBaseUrl();
133
+ const apiKey = opts.apiKey ?? (await resolveApiKey());
134
+ if (!apiKey && !opts.allowAnonymous) {
135
+ throw new AuthError('Not logged in. Create an API key in your ClipUGC dashboard, then run `clipugc auth login`.');
136
+ }
137
+ return new ApiClient(baseUrl, apiKey);
138
+ }
139
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/services/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * AI characters service — request building, client-side validation and API
3
+ * calls for the `clipugc characters` command group.
4
+ *
5
+ * All validation lives here (unit-testable); command handlers stay thin.
6
+ * Throws typed errors from utils/errors.js — never exits the process.
7
+ */
8
+ import type { Pagination } from '../types/index.js';
9
+ /** An AI character record. Unknown/extra API fields are preserved. */
10
+ export interface AiCharacter {
11
+ id: number | string;
12
+ full_name?: string;
13
+ /** Server-resolved presentation name (preferred over full_name when present). */
14
+ display_name?: string;
15
+ age?: number;
16
+ gender?: string;
17
+ is_public?: boolean;
18
+ /** feed scope only: true when this public character is not yet unlocked. */
19
+ is_locked?: boolean;
20
+ status?: string;
21
+ video_count?: number;
22
+ picture_count?: number;
23
+ /** Nullable media object with clip url/thumbnail fields. */
24
+ preview_clip?: {
25
+ clip_url?: string;
26
+ url?: string;
27
+ clip_thumbnail_url?: string;
28
+ thumbnail_url?: string;
29
+ [key: string]: unknown;
30
+ } | null;
31
+ /** Staged first clip returned by create with make_video=true (nullable). */
32
+ character_video?: {
33
+ id: number | string;
34
+ status?: string;
35
+ [key: string]: unknown;
36
+ } | null;
37
+ created_at?: string;
38
+ [key: string]: unknown;
39
+ }
40
+ export declare const MAX_PER_PAGE = 50;
41
+ export declare const NAME_MIN = 2;
42
+ export declare const NAME_MAX = 120;
43
+ export declare const AGE_MIN = 18;
44
+ export declare const AGE_MAX = 99;
45
+ /** Keys the API might wrap a character list in (tolerant unwrapping). */
46
+ export declare const CHARACTER_LIST_KEYS: string[];
47
+ /**
48
+ * Tolerant list extraction: the API sometimes returns the array directly,
49
+ * sometimes wrapped (data.items, data.ai_characters, ...). Tries known keys
50
+ * then falls back to an empty list.
51
+ */
52
+ export declare function extractList<T>(data: unknown, keys: string[]): T[];
53
+ /** Pull the pagination block out of a list response, when present. */
54
+ export declare function extractPagination(data: unknown): Pagination | undefined;
55
+ /**
56
+ * Tolerant single-record extraction: response data may be the record itself
57
+ * or wrapped under a known key (e.g. data.ai_character).
58
+ */
59
+ export declare function extractRecord<T extends {
60
+ id?: unknown;
61
+ }>(data: unknown, keys: string[]): T;
62
+ /** Clamp per_page into [1, MAX_PER_PAGE]. Returns undefined when not given. */
63
+ export declare function clampPerPage(perPage: number | undefined): number | undefined;
64
+ /**
65
+ * Presentation name for a character: prefer the server's display_name, fall
66
+ * back to full_name (ignoring the "Unnamed" placeholder), then to a client
67
+ * default of "AI Influencer #<id>".
68
+ */
69
+ export declare function getDisplayName(character: AiCharacter): string;
70
+ /** Try known field names for a character's selected look id. */
71
+ export declare function getSelectedLookId(character: AiCharacter): string | number | undefined;
72
+ export interface ListCharactersOptions {
73
+ scope?: 'mine' | 'discover' | 'feed';
74
+ search?: string;
75
+ page?: number;
76
+ perPage?: number;
77
+ }
78
+ export interface CharacterListResult {
79
+ items: AiCharacter[];
80
+ pagination?: Pagination;
81
+ /** Raw response data (for --json passthrough). */
82
+ raw: unknown;
83
+ }
84
+ /** GET /ai-characters */
85
+ export declare function listCharacters(options?: ListCharactersOptions): Promise<CharacterListResult>;
86
+ export interface CreateCharacterInput {
87
+ /** Full name (required, 2-120 chars). */
88
+ /** Free-text description (primary path, like the web builder). 10-1000 chars. */
89
+ description?: string;
90
+ /** Optional scene/pose for the auto-generated first look. ≤600 chars. */
91
+ scene?: string;
92
+ /** Optional inspiration image file paths (≤6), uploaded multipart. */
93
+ inspirationFiles?: string[];
94
+ /** Full name (advanced/structured path; ignored when description is set). */
95
+ name?: string;
96
+ age?: number;
97
+ gender?: string;
98
+ isPublic?: boolean;
99
+ /** DNA fields from --dna-json (lowest precedence). */
100
+ dna?: Record<string, unknown>;
101
+ /** Appearance fields from explicit flags, already snake_cased (override dna). */
102
+ appearance?: Record<string, string | undefined>;
103
+ /** Also stage the character's first video clip (make_video, default false). */
104
+ makeVideo?: boolean;
105
+ /** Optional motion prompt for the staged first clip (requires makeVideo). */
106
+ motionPrompt?: string;
107
+ }
108
+ export declare const DESCRIPTION_MIN = 10;
109
+ export declare const DESCRIPTION_MAX = 1000;
110
+ export declare const CREATE_SCENE_MAX = 600;
111
+ export declare const INSPIRATION_MAX = 6;
112
+ /** Validate the character name (2-120 chars). Returns the trimmed name. */
113
+ export declare function validateCharacterName(name: string | undefined): string;
114
+ /** Validate the character age (integer 18-99). */
115
+ export declare function validateCharacterAge(age: number): number;
116
+ /**
117
+ * Build the POST /ai-characters payload.
118
+ * Merge order: dna-json fields first, explicit flags override.
119
+ */
120
+ export declare function buildCreateCharacterPayload(input: CreateCharacterInput): Record<string, unknown>;
121
+ /**
122
+ * Parse the --dna-json value: inline JSON when it starts with '{',
123
+ * otherwise a path to a JSON file. Must be a JSON object.
124
+ */
125
+ export declare function parseDnaJson(value: string): Promise<Record<string, unknown>>;
126
+ /** POST /ai-characters */
127
+ export declare function createCharacter(input: CreateCharacterInput): Promise<AiCharacter>;
128
+ /** GET /ai-characters/{id} */
129
+ export declare function getCharacter(id: string): Promise<AiCharacter>;
130
+ /** PATCH /ai-characters/{id} — rename (validates 2-120 chars). */
131
+ export declare function renameCharacter(id: string, name: string): Promise<AiCharacter>;
132
+ /** PATCH /ai-characters/{id} — publish/unpublish. */
133
+ export declare function setCharacterVisibility(id: string, isPublic: boolean): Promise<AiCharacter>;
134
+ /** DELETE /ai-characters/{id} */
135
+ export declare function deleteCharacter(id: string): Promise<unknown>;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * AI characters service — request building, client-side validation and API
3
+ * calls for the `clipugc characters` command group.
4
+ *
5
+ * All validation lives here (unit-testable); command handlers stay thin.
6
+ * Throws typed errors from utils/errors.js — never exits the process.
7
+ */
8
+ import fs from 'fs-extra';
9
+ import { createApiClient } from './api.js';
10
+ import { ValidationError } from '../utils/errors.js';
11
+ export const MAX_PER_PAGE = 50;
12
+ export const NAME_MIN = 2;
13
+ export const NAME_MAX = 120;
14
+ export const AGE_MIN = 18;
15
+ export const AGE_MAX = 99;
16
+ /** Keys the API might wrap a character list in (tolerant unwrapping). */
17
+ export const CHARACTER_LIST_KEYS = ['items', 'ai_characters', 'characters', 'data'];
18
+ /**
19
+ * Tolerant list extraction: the API sometimes returns the array directly,
20
+ * sometimes wrapped (data.items, data.ai_characters, ...). Tries known keys
21
+ * then falls back to an empty list.
22
+ */
23
+ export function extractList(data, keys) {
24
+ if (Array.isArray(data))
25
+ return data;
26
+ if (data && typeof data === 'object') {
27
+ const obj = data;
28
+ for (const key of keys) {
29
+ if (Array.isArray(obj[key]))
30
+ return obj[key];
31
+ }
32
+ }
33
+ return [];
34
+ }
35
+ /** Pull the pagination block out of a list response, when present. */
36
+ export function extractPagination(data) {
37
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
38
+ const pagination = data.pagination;
39
+ if (pagination && typeof pagination === 'object')
40
+ return pagination;
41
+ }
42
+ return undefined;
43
+ }
44
+ /**
45
+ * Tolerant single-record extraction: response data may be the record itself
46
+ * or wrapped under a known key (e.g. data.ai_character).
47
+ */
48
+ export function extractRecord(data, keys) {
49
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
50
+ const obj = data;
51
+ if (obj.id !== undefined)
52
+ return obj;
53
+ for (const key of keys) {
54
+ const nested = obj[key];
55
+ if (nested &&
56
+ typeof nested === 'object' &&
57
+ !Array.isArray(nested) &&
58
+ nested.id !== undefined) {
59
+ return nested;
60
+ }
61
+ }
62
+ }
63
+ return data;
64
+ }
65
+ /** Clamp per_page into [1, MAX_PER_PAGE]. Returns undefined when not given. */
66
+ export function clampPerPage(perPage) {
67
+ if (perPage === undefined)
68
+ return undefined;
69
+ return Math.min(Math.max(perPage, 1), MAX_PER_PAGE);
70
+ }
71
+ /**
72
+ * Presentation name for a character: prefer the server's display_name, fall
73
+ * back to full_name (ignoring the "Unnamed" placeholder), then to a client
74
+ * default of "AI Influencer #<id>".
75
+ */
76
+ export function getDisplayName(character) {
77
+ const display = character.display_name;
78
+ if (typeof display === 'string' && display.trim() !== '')
79
+ return display;
80
+ const fullName = character.full_name ?? (typeof character.name === 'string' ? character.name : undefined);
81
+ if (typeof fullName === 'string' && fullName.trim() !== '' && fullName.trim() !== 'Unnamed')
82
+ return fullName;
83
+ return `AI Influencer #${character.id}`;
84
+ }
85
+ /** Try known field names for a character's selected look id. */
86
+ export function getSelectedLookId(character) {
87
+ const direct = character.selected_image_id ??
88
+ character.selected_character_image_id ??
89
+ character.selected_reference_image_id ??
90
+ character.selected_look_id;
91
+ if (typeof direct === 'number' || typeof direct === 'string')
92
+ return direct;
93
+ const nested = character.selected_image ?? character.selected_look;
94
+ if (nested && typeof nested === 'object') {
95
+ const id = nested.id;
96
+ if (typeof id === 'number' || typeof id === 'string')
97
+ return id;
98
+ }
99
+ return undefined;
100
+ }
101
+ /** GET /ai-characters */
102
+ export async function listCharacters(options = {}) {
103
+ const api = await createApiClient();
104
+ const raw = await api.get('/ai-characters', {
105
+ query: {
106
+ scope: options.scope ?? 'mine',
107
+ search: options.search,
108
+ page: options.page,
109
+ per_page: clampPerPage(options.perPage),
110
+ },
111
+ });
112
+ return {
113
+ items: extractList(raw, CHARACTER_LIST_KEYS),
114
+ pagination: extractPagination(raw),
115
+ raw,
116
+ };
117
+ }
118
+ export const DESCRIPTION_MIN = 10;
119
+ export const DESCRIPTION_MAX = 1000;
120
+ export const CREATE_SCENE_MAX = 600;
121
+ export const INSPIRATION_MAX = 6;
122
+ /** Validate the character name (2-120 chars). Returns the trimmed name. */
123
+ export function validateCharacterName(name) {
124
+ const trimmed = (name ?? '').trim();
125
+ if (trimmed.length < NAME_MIN || trimmed.length > NAME_MAX) {
126
+ throw new ValidationError(`Character name must be ${NAME_MIN}-${NAME_MAX} characters (got ${trimmed.length}). Pass it with --name "Full Name".`);
127
+ }
128
+ return trimmed;
129
+ }
130
+ /** Validate the character age (integer 18-99). */
131
+ export function validateCharacterAge(age) {
132
+ if (!Number.isInteger(age) || age < AGE_MIN || age > AGE_MAX) {
133
+ throw new ValidationError(`Character age must be an integer between ${AGE_MIN} and ${AGE_MAX} (got ${age}).`);
134
+ }
135
+ return age;
136
+ }
137
+ /**
138
+ * Apply the make_video / motion_prompt create options to a payload.
139
+ * make_video defaults to false server-side, so it is only sent when true.
140
+ */
141
+ function applyMakeVideo(payload, input) {
142
+ const motion = input.motionPrompt?.trim();
143
+ if (motion && !input.makeVideo) {
144
+ throw new ValidationError('--motion-prompt only applies when staging a first clip — add --make-video.');
145
+ }
146
+ if (input.makeVideo) {
147
+ payload.make_video = true;
148
+ if (motion)
149
+ payload.motion_prompt = motion;
150
+ }
151
+ }
152
+ /**
153
+ * Build the POST /ai-characters payload.
154
+ * Merge order: dna-json fields first, explicit flags override.
155
+ */
156
+ export function buildCreateCharacterPayload(input) {
157
+ // Primary path (matches the web builder): one free-text description; the server
158
+ // extracts the DNA and generates the first look in the same action.
159
+ if (input.description !== undefined && input.description.trim() !== '') {
160
+ const description = input.description.trim();
161
+ if (description.length < DESCRIPTION_MIN || description.length > DESCRIPTION_MAX) {
162
+ throw new ValidationError(`Description must be ${DESCRIPTION_MIN}-${DESCRIPTION_MAX} characters (got ${description.length}).`);
163
+ }
164
+ const payload = { description };
165
+ if (input.scene !== undefined && input.scene.trim() !== '') {
166
+ const scene = input.scene.trim();
167
+ if (scene.length > CREATE_SCENE_MAX) {
168
+ throw new ValidationError(`Scene prompt must be at most ${CREATE_SCENE_MAX} characters (got ${scene.length}).`);
169
+ }
170
+ payload.scene_prompt = scene;
171
+ }
172
+ if (input.isPublic !== undefined)
173
+ payload.is_public = input.isPublic;
174
+ applyMakeVideo(payload, input);
175
+ return payload;
176
+ }
177
+ // Advanced/structured path (raw DNA fields).
178
+ const fullName = validateCharacterName(input.name);
179
+ if (input.age !== undefined)
180
+ validateCharacterAge(input.age);
181
+ const payload = { ...(input.dna ?? {}) };
182
+ for (const [key, value] of Object.entries(input.appearance ?? {})) {
183
+ if (value !== undefined && value !== '')
184
+ payload[key] = value;
185
+ }
186
+ payload.full_name = fullName;
187
+ if (input.age !== undefined)
188
+ payload.age = input.age;
189
+ if (input.gender !== undefined && input.gender !== '')
190
+ payload.gender = input.gender;
191
+ if (input.isPublic !== undefined)
192
+ payload.is_public = input.isPublic;
193
+ applyMakeVideo(payload, input);
194
+ return payload;
195
+ }
196
+ /**
197
+ * Parse the --dna-json value: inline JSON when it starts with '{',
198
+ * otherwise a path to a JSON file. Must be a JSON object.
199
+ */
200
+ export async function parseDnaJson(value) {
201
+ let parsed;
202
+ if (value.trimStart().startsWith('{')) {
203
+ try {
204
+ parsed = JSON.parse(value);
205
+ }
206
+ catch (err) {
207
+ const reason = err instanceof Error ? err.message : String(err);
208
+ throw new ValidationError(`--dna-json is not valid JSON: ${reason}`);
209
+ }
210
+ }
211
+ else {
212
+ try {
213
+ parsed = await fs.readJson(value);
214
+ }
215
+ catch (err) {
216
+ const reason = err instanceof Error ? err.message : String(err);
217
+ throw new ValidationError(`Could not read --dna-json file "${value}": ${reason}`);
218
+ }
219
+ }
220
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
221
+ throw new ValidationError('--dna-json must be a JSON object of DNA fields, e.g. {"hair_color": "black"}.');
222
+ }
223
+ return parsed;
224
+ }
225
+ /** POST /ai-characters */
226
+ export async function createCharacter(input) {
227
+ const payload = buildCreateCharacterPayload(input);
228
+ const api = await createApiClient();
229
+ const files = input.inspirationFiles ?? [];
230
+ if (files.length > 0) {
231
+ if (files.length > INSPIRATION_MAX) {
232
+ throw new ValidationError(`At most ${INSPIRATION_MAX} inspiration images are allowed (got ${files.length}).`);
233
+ }
234
+ const form = new FormData();
235
+ for (const [key, value] of Object.entries(payload)) {
236
+ form.append(key, typeof value === 'boolean' ? (value ? '1' : '0') : String(value));
237
+ }
238
+ for (const file of files) {
239
+ if (!(await fs.pathExists(file))) {
240
+ throw new ValidationError(`Inspiration image not found: ${file}`);
241
+ }
242
+ const bytes = await fs.readFile(file);
243
+ const name = file.split('/').pop() ?? 'inspiration.jpg';
244
+ form.append('inspiration_images[]', new Blob([bytes]), name);
245
+ }
246
+ const data = await api.postForm('/ai-characters', form);
247
+ return extractRecord(data, ['ai_character', 'character']);
248
+ }
249
+ const data = await api.post('/ai-characters', { body: payload });
250
+ return extractRecord(data, ['ai_character', 'character']);
251
+ }
252
+ /** GET /ai-characters/{id} */
253
+ export async function getCharacter(id) {
254
+ const api = await createApiClient();
255
+ const data = await api.get(`/ai-characters/${id}`);
256
+ return extractRecord(data, ['ai_character', 'character']);
257
+ }
258
+ /** PATCH /ai-characters/{id} — rename (validates 2-120 chars). */
259
+ export async function renameCharacter(id, name) {
260
+ const fullName = validateCharacterName(name);
261
+ const api = await createApiClient();
262
+ const data = await api.patch(`/ai-characters/${id}`, { body: { full_name: fullName } });
263
+ return extractRecord(data, ['ai_character', 'character']);
264
+ }
265
+ /** PATCH /ai-characters/{id} — publish/unpublish. */
266
+ export async function setCharacterVisibility(id, isPublic) {
267
+ const api = await createApiClient();
268
+ const data = await api.patch(`/ai-characters/${id}`, { body: { is_public: isPublic } });
269
+ return extractRecord(data, ['ai_character', 'character']);
270
+ }
271
+ /** DELETE /ai-characters/{id} */
272
+ export async function deleteCharacter(id) {
273
+ const api = await createApiClient();
274
+ return api.delete(`/ai-characters/${id}`);
275
+ }
276
+ //# sourceMappingURL=characters.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"characters.service.js","sourceRoot":"","sources":["../../src/services/characters.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ApiClient } from './api.js';
2
+ /**
3
+ * POST /character-videos/hook-suggestions — AI-generated hook text ideas.
4
+ * Returns both the parsed hooks list and the raw payload (for --json mode).
5
+ */
6
+ export declare function suggestHooks(api: ApiClient, context?: string): Promise<{
7
+ hooks: string[];
8
+ raw: unknown;
9
+ }>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * POST /character-videos/hook-suggestions — AI-generated hook text ideas.
3
+ * Returns both the parsed hooks list and the raw payload (for --json mode).
4
+ */
5
+ export async function suggestHooks(api, context) {
6
+ const body = {};
7
+ if (context !== undefined && context.trim() !== '') {
8
+ body.context = context;
9
+ }
10
+ const raw = await api.post('/character-videos/hook-suggestions', { body });
11
+ let hooks = [];
12
+ if (Array.isArray(raw)) {
13
+ hooks = raw.filter((h) => typeof h === 'string');
14
+ }
15
+ else if (raw && typeof raw === 'object') {
16
+ const candidate = raw.hooks;
17
+ if (Array.isArray(candidate)) {
18
+ hooks = candidate.filter((h) => typeof h === 'string');
19
+ }
20
+ }
21
+ return { hooks, raw };
22
+ }
23
+ //# sourceMappingURL=hooks.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.service.js","sourceRoot":"","sources":["../../src/services/hooks.service.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAc,EACd,OAAgB;IAEhB,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAU,oCAAoC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpF,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IAChE,CAAC;SAAM,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAI,GAA+B,CAAC,KAAK,CAAC;QACzD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACxB,CAAC"}