ugcinc 4.26.0 → 4.27.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 +220 -0
- package/dist/automations/nodes/create-dm.d.ts +7 -2
- package/dist/automations/nodes/create-dm.js +26 -2
- package/dist/automations/nodes/index.d.ts +6 -3
- package/dist/automations/nodes/remotion-composition.d.ts +6 -6
- package/dist/cli.js +49 -8
- package/dist/content-skills.d.ts +1 -0
- package/dist/content-skills.js +10 -0
- package/dist/imessage-conversation.d.ts +907 -0
- package/dist/imessage-conversation.js +331 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -3
- package/dist/media-analysis.d.ts +272 -272
- package/dist/media.d.ts +11 -0
- package/dist/media.js +12 -0
- package/dist/render/compositions/IMessageDmComposition/types.d.ts +20 -20
- package/dist/render/types/remotion-composition.d.ts +16 -16
- package/dist/skills.js +2 -61
- package/dist/types.d.ts +1 -0
- package/package.json +8 -3
- package/skills/content-automation/SKILL.md +82 -0
- package/skills/content-automation/references/build-review.md +66 -0
- package/skills/media/SKILL.md +83 -0
- package/skills/media/references/tools.md +64 -0
package/README.md
CHANGED
|
@@ -357,6 +357,195 @@ Both operations require an organization and `content:analyze` for scoped keys.
|
|
|
357
357
|
Errors may include `errorCode` and `retryable`. Existing scraper credit/rate safeguards
|
|
358
358
|
apply. Storage/provider errors do not imply that a post or profile was deleted.
|
|
359
359
|
|
|
360
|
+
## Native iMessage conversations and PNG snapshots (staged)
|
|
361
|
+
|
|
362
|
+
The native conversation API is implemented behind a rollout gate and is **not yet
|
|
363
|
+
enabled on the public service**. Until rollout, submission returns a retryable 503.
|
|
364
|
+
The existing still-image DM renderer remains available separately.
|
|
365
|
+
|
|
366
|
+
Quick reference: `client.media.renderIMessageConversation(input)` queues a video by default;
|
|
367
|
+
`client.media.getIMessageConversation({jobId})` polls its status.
|
|
368
|
+
`client.media.cancelIMessageConversation({jobId})` requests cancellation and native cleanup.
|
|
369
|
+
`compileIMessageConversation(input)` previews its deterministic timing plan locally.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import {UGCClient, compileIMessageConversation, type IMessageConversationInput} from 'ugcinc';
|
|
373
|
+
|
|
374
|
+
const client = new UGCClient({apiKey: 'YOUR_API_KEY', orgId: 'YOUR_ORG_ID'});
|
|
375
|
+
const input = {
|
|
376
|
+
idempotencyKey: 'conversation-v1',
|
|
377
|
+
contact: {name: 'Alex'},
|
|
378
|
+
theme: 'dark',
|
|
379
|
+
statusBar: {time: '12:34', wifiBars: 3, cellularBars: 4, batteryPercent: 85},
|
|
380
|
+
unreadCount: 12,
|
|
381
|
+
timestampText: 'Today 12:34 PM',
|
|
382
|
+
seed: 42,
|
|
383
|
+
timing: {charactersPerSecond: 7, pauseBeforeMs: 500, pauseBeforeSendMs: 200, incomingTypingDurationMs: 2400},
|
|
384
|
+
messages: [
|
|
385
|
+
{sender: 'recipient', text: 'Ready?', initial: true},
|
|
386
|
+
{sender: 'user', text: 'On my way 👋'},
|
|
387
|
+
{sender: 'recipient', text: 'See you soon!', timing: {incomingTypingDurationMs: 3200}},
|
|
388
|
+
],
|
|
389
|
+
} satisfies IMessageConversationInput;
|
|
390
|
+
const plan = compileIMessageConversation(input);
|
|
391
|
+
const submitted = await client.media.renderIMessageConversation(input);
|
|
392
|
+
if (submitted.ok) {
|
|
393
|
+
const status = await client.media.getIMessageConversation({jobId: submitted.data.jobId});
|
|
394
|
+
if (status.ok && status.data.status === 'completed') console.log(status.data.url);
|
|
395
|
+
}
|
|
396
|
+
// A lossless frame from the same native scene. Omit atMs for its final frame.
|
|
397
|
+
const still = await client.media.renderIMessageConversation({
|
|
398
|
+
...input, idempotencyKey: 'conversation-png-v1', output: {kind: 'png', atMs: 500},
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
API reference:
|
|
403
|
+
|
|
404
|
+
- `POST /media/render/imessage`: authenticated organization-scoped submission.
|
|
405
|
+
- `POST /media/render/imessage/status`: `{jobId}`; only jobs owned by that
|
|
406
|
+
organization can be queried.
|
|
407
|
+
- `POST /media/render/imessage/cancel`: `{jobId}`; requests cancellation for an
|
|
408
|
+
owned job. Poll status until `cancelled` or another terminal status. A pending
|
|
409
|
+
request returns `cancelRequested: true`; it does not claim native cleanup has
|
|
410
|
+
finished. Completed jobs retain their result if cancellation arrives too late.
|
|
411
|
+
Scoped keys need `content:render` for all three routes.
|
|
412
|
+
- These return the normal `ApiResponse`. Job data includes `jobId`,
|
|
413
|
+
`status: pending | processing | completed | failed | cancelled`, `progress`, `fps: 60`,
|
|
414
|
+
`durationMs` (planned timeline duration) and `durationInFrames`. Completed jobs
|
|
415
|
+
include an owned HTTPS `url`, `output` settings and `contentType` (`video/mp4`
|
|
416
|
+
or `image/png`). Failed jobs include a safe error and retryability.
|
|
417
|
+
Submission does not wait for completion.
|
|
418
|
+
- Output uses the pinned `iphone-xs-ios18.6.2` UI, native text/emoji and native
|
|
419
|
+
conversation motion. Video is 60fps H264; it is not frame-interpolated.
|
|
420
|
+
Native frames are 1125×2436; MP4 adds one background-colored column for an
|
|
421
|
+
even 1126-pixel codec width. This profile does not represent newer iOS designs.
|
|
422
|
+
- `output` defaults to `{kind: 'video'}`. `{kind: 'png', atMs: 500}` returns the
|
|
423
|
+
original 1125×2436 native full-frame PNG bytes at that exact millisecond timestamp;
|
|
424
|
+
omitting `atMs` selects the final authored frame. Times outside the planned
|
|
425
|
+
conversation reject. PNG uses the same native scene and transitions, with no
|
|
426
|
+
resizing, separate text renderer or lossy video-frame extraction. For PNG jobs,
|
|
427
|
+
`durationInFrames` is 1 while `durationMs` still describes the authored timeline;
|
|
428
|
+
the renderer completes outstanding native actions privately for clean teardown.
|
|
429
|
+
- `contact.name` is the other person's display name/username. Optional
|
|
430
|
+
`contact.avatarPngBase64` is a PNG without a data-URL prefix, at most 512×512
|
|
431
|
+
and 65536 base64 characters; the native ContactsUI component crops it. It is
|
|
432
|
+
an in-memory render input and does not create or save a real contact.
|
|
433
|
+
- `statusBar` supports `time` (H:mm/HH:mm, default `9:41`), `wifiBars` (0–3,
|
|
434
|
+
default 3), `cellularBars` (0–4, default 4), `wifiEnabled`/`cellularEnabled`
|
|
435
|
+
(default true), `batteryPercent` (0–100, default 100), `charging` and
|
|
436
|
+
`lowPowerMode` (default false). Capacity controls the native battery fill;
|
|
437
|
+
it does not turn on a numerical percentage label. These configure render
|
|
438
|
+
models, not the host phone's clock, network or battery settings.
|
|
439
|
+
- `unreadCount` (0–9999, default 0) controls the native back-button badge.
|
|
440
|
+
The number of conversation messages is simply `messages.length`; there is
|
|
441
|
+
no second message-count field to keep in sync. Optional `timestampText`
|
|
442
|
+
replaces native date-header text, independently of the status-bar clock.
|
|
443
|
+
- Provide `contact: {name}` or `group` (described below). `theme` defaults to `dark`, `keyboard` to `true`,
|
|
444
|
+
`fps` to `60` (the only supported rate), `seed` to `0`, and `endHoldMs` to `1500`
|
|
445
|
+
(350–10000). `showIncomingTyping` defaults to `true`.
|
|
446
|
+
- Messages require a `sender` ID (`user | recipient` for direct chats, or a group participant ID) and either nonempty `text` (up to 2000
|
|
447
|
+
UTF-16 code units) or `imagePngBase64`. Image captions and mixed text/image
|
|
448
|
+
messages are rejected. An image must be a single-frame PNG, without a data URL
|
|
449
|
+
prefix, at most 2048×2048 and 8 MiB of base64 characters. The full request is
|
|
450
|
+
limited to 16 MiB; aggregate message image dimensions cannot exceed 16,777,216
|
|
451
|
+
pixels. Outgoing images enter the native composer before send; the plan reserves
|
|
452
|
+
its native attachment transition plus `pauseBeforeSendMs`. Initial and incoming
|
|
453
|
+
images do not open the local composer. `user` is outgoing. Optional IDs must be unique; otherwise
|
|
454
|
+
IDs are generated. `initial: true` supplies preexisting history and must be a
|
|
455
|
+
prefix. Incoming messages may override `showTyping`; their text is hidden
|
|
456
|
+
until arrival. Outgoing text appears in the composer.
|
|
457
|
+
- Outgoing messages accept `receipt: none | delivered | read` (default `none`).
|
|
458
|
+
Read state requires `readAt`, an ISO timestamp with timezone. Animated receipts
|
|
459
|
+
appear `receiptDelayMs` after arrival (0–30000, default 500); history receipts
|
|
460
|
+
are present initially. As in Messages, the visible receipt belongs to the
|
|
461
|
+
latest outgoing message; older receipts disappear when a newer one is sent.
|
|
462
|
+
Delayed receipts extend the render duration so they are not truncated.
|
|
463
|
+
- Default `timing`: `charactersPerSecond: 8` (0.5–60), `pauseBeforeMs: 450`
|
|
464
|
+
(0–30000), `pauseBeforeSendMs: 180` (0–10000), `punctuationPauseMs: 160`
|
|
465
|
+
(0–2000), `variation: 0.15` (0–0.75). Each message can override any setting.
|
|
466
|
+
Pauses use milliseconds. Typing speed counts complete visible grapheme clusters,
|
|
467
|
+
including a whole family emoji or combining-accent sequence as one character.
|
|
468
|
+
Variation uses the supplied seed. The plan reserves native transition completion
|
|
469
|
+
before starting the next message, then applies `pauseBeforeMs` before typing.
|
|
470
|
+
- Outgoing emoji input is represented explicitly in the authoring timeline:
|
|
471
|
+
open the native emoji keyboard, focus an emoji, select it, then return to the
|
|
472
|
+
letter keyboard when subsequent text needs it. `timing.keyboardSwitchPauseMs`
|
|
473
|
+
(350–10000, default 450) controls the pause after switching keyboards;
|
|
474
|
+
`timing.emojiSelectionDelayMs` (100–10000, default 350) controls the pause before
|
|
475
|
+
selecting a focused emoji. These can be overridden per message. Complete
|
|
476
|
+
grapheme sequences are preserved. Incoming emoji text never opens the local
|
|
477
|
+
keyboard. Outgoing emoji interactions require `keyboard: true`. Emoji that require
|
|
478
|
+
an unsupported long-press skin-tone or family picker reject instead of being
|
|
479
|
+
inserted through an invisible shortcut.
|
|
480
|
+
- Optional `timing.incomingTypingDurationMs` (0–30000) sets the exact interval
|
|
481
|
+
from the other person's typing indicator starting until their message arrives.
|
|
482
|
+
Set it globally or override it in an incoming message's `timing` (for example
|
|
483
|
+
2400 globally and 3200 for a longer reply). `pauseBeforeMs` still controls when
|
|
484
|
+
typing starts. This replaces the incoming duration calculated from character
|
|
485
|
+
speed, punctuation and the pre-send pause; outgoing composer speed is unchanged.
|
|
486
|
+
Omit it to retain the calculated duration. Zero is supported. With
|
|
487
|
+
`showTyping: false`, the same response delay applies without a visible indicator.
|
|
488
|
+
Setting this field on an outgoing message rejects.
|
|
489
|
+
- `replyTo` on a message names an earlier message ID. Native thread identity,
|
|
490
|
+
quoted context and reply grouping are derived on the render host. Replies to
|
|
491
|
+
replies remain in their original thread, while the visible reply gesture targets
|
|
492
|
+
the exact message named by `replyTo`. For an animated local reply, the compiler
|
|
493
|
+
emits `reply.begin`, long-presses that message, opens its native menu, selects
|
|
494
|
+
**Reply**, and waits for the reply view before typing and sending inside it.
|
|
495
|
+
It emits `reply.end` after sending and closes the
|
|
496
|
+
reply view before the next local action. `pauseBeforeMs` occurs before the
|
|
497
|
+
gesture; the compiled `typingStartMs` records when typing actually starts after
|
|
498
|
+
the opening transition. Incoming replies and initial history show their reply
|
|
499
|
+
associations without operating the local reply controls. Animated local replies
|
|
500
|
+
require `keyboard: true`; incoming replies and history also work without it.
|
|
501
|
+
- Use `contact` for a direct chat, or `group: {name, localParticipantId,
|
|
502
|
+
participants: [{id, name}]}` for a group. Provide exactly one. A group has
|
|
503
|
+
3–20 participants including the local participant; IDs are unique. Message
|
|
504
|
+
`sender` and reaction `actor` use those IDs. For direct chats they remain
|
|
505
|
+
`user` and `recipient`. Group names, sender labels, avatars and bubble
|
|
506
|
+
grouping use the native group conversation UI. Optional `group.avatarPngBase64`
|
|
507
|
+
and `participants[].avatarPngBase64` accept the same bounded PNG format as
|
|
508
|
+
`contact.avatarPngBase64`.
|
|
509
|
+
- `statusBar.time` controls only the clock. Top-level `timestampText` controls
|
|
510
|
+
the first conversation separator independently. Optional message `sentAt`
|
|
511
|
+
sets the actual conversation date (ISO 8601 with timezone); provide it for
|
|
512
|
+
every message in chronological order when used. Native date-gap rules create
|
|
513
|
+
later separators. A message's optional `timestampText` forces a separator
|
|
514
|
+
before it and sets its displayed text, such as `Today 3:10 PM`. These dates
|
|
515
|
+
do not affect typing speed or make the rendered video wait through the gap.
|
|
516
|
+
- A message's optional `reactions` array schedules reactions relative to that
|
|
517
|
+
message's arrival (or video start for initial history). Each entry takes an
|
|
518
|
+
`actor`, `delayMs` (0–60000, default 700), and either a classic `reaction:
|
|
519
|
+
love | like | dislike | laugh | emphasize | question` or one `emoji`.
|
|
520
|
+
Local actions open the native reaction picker, hold it for `menuDurationMs`
|
|
521
|
+
(400–10000, default 700), then select the reaction. They reserve the local
|
|
522
|
+
interaction timeline until dismissal completes before further typing or
|
|
523
|
+
another local reaction. `delayMs` is the earliest picker-open time for local
|
|
524
|
+
actions, and the arrival time for remote reactions. Remote reactions do not
|
|
525
|
+
show a local picker. Custom emoji currently work for remote participants;
|
|
526
|
+
local actions accept the six classic picker options. `action: remove`
|
|
527
|
+
selects the actor's current reaction again through the picker, and takes no
|
|
528
|
+
reaction/emoji value. Setting again replaces the actor's previous reaction.
|
|
529
|
+
At most 20 reaction events per message are accepted.
|
|
530
|
+
- Optional `threads: [{messageId, openAtMs, durationMs}]` opens and closes the
|
|
531
|
+
native expanded reply view for reading. These explicit thread views run after
|
|
532
|
+
the conversation and its native transitions settle; typing/sending during
|
|
533
|
+
these read-only views is not supported. Animated local `replyTo` messages use
|
|
534
|
+
their own reply-composition flow described above. Duration is 600–30000ms, at most ten views, without
|
|
535
|
+
overlap. The original conversation returns when the view closes.
|
|
536
|
+
- Maximum 30 messages, 6000 animated visible characters and a planned duration of
|
|
537
|
+
120 seconds. Invalid or excessive requests reject before device use. The local
|
|
538
|
+
compiler exposes message arrival times, events and frame count.
|
|
539
|
+
- Optional `idempotencyKey` (1–128 characters) deduplicates submissions within an
|
|
540
|
+
organization. Reuse the same body/key to recover the same job; changing the body
|
|
541
|
+
returns 409. To retry a failed render, use a new key or omit it. Job ownership
|
|
542
|
+
records are retained for seven days.
|
|
543
|
+
- This authoring API currently supports text/emoji, initial history and sequential
|
|
544
|
+
conversations with configurable chrome, receipts, reactions and replies. Image attachments,
|
|
545
|
+
carrier-name/network-type selection, numerical
|
|
546
|
+
battery-percentage display and arbitrary timeline seeking are not accepted
|
|
547
|
+
by this contract. Unknown input fields reject rather than silently approximating them.
|
|
548
|
+
|
|
360
549
|
## Video inspection
|
|
361
550
|
|
|
362
551
|
```ts
|
|
@@ -484,3 +673,34 @@ Inputs: HTTPS image URL and integer display-oriented pixel `box` with a small pl
|
|
|
484
673
|
Fitting runs locally through the installed UGC ImageEditorComposition, searching the supported TikTok Sans, SF Pro and Arial-stack choices, weight, size, spacing, line height, alignment and outline. Fill/background/outline colors are estimated from the crop. Arial may use a system fallback; original font identity is always `unverified`. No custom font upload, gradients, perspective, mixed styles, emoji or shadow setting is inferred. Busy borders, blank crops and clipped foreground return explicit unavailable results. These checks do not prove that every remaining crop is suitable: inspect residuals and preview.
|
|
485
674
|
|
|
486
675
|
Returns up to three candidates, renderable settings, `maskIoU`, `foregroundMae` (mean absolute RGB error on the union of reference/rendered ink, scaled0–1), and an owned reference/render/difference preview plus JSON manifest. `quality: 'close'` means IoU>=0.9 and foreground MAE<=0.1; otherwise `approximate`. These are image-fit measures, not font-recognition probabilities or content-quality scores. Single-line alignment/padding cannot be uniquely recovered; returned geometry reproduces visible positioning. Render count, installed UGC version and bundled font hashes are included. Search is capped at320 renders and a480-second search budget within existing job limits; resource failures are explicit failed jobs. The renderer bundle is reused unchanged, with no hosted inference or new font downloads.
|
|
676
|
+
|
|
677
|
+
## Agent media inspection and content workflows
|
|
678
|
+
|
|
679
|
+
Install or refresh the bundled Claude Code instructions in the agent's project:
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
npx ugcinc@4.26.1 init --update media content-automation automations
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
`init` preserves existing files by default. `--update` writes changed skill files
|
|
686
|
+
and backs up their previous contents under the project's `.ugcinc/skill-backups/`.
|
|
687
|
+
Optional skill names limit installation/update to those skills. New `media` and
|
|
688
|
+
`content-automation` skills include linked tool/reference guides; no account,
|
|
689
|
+
schedule or publishing changes are performed by installation.
|
|
690
|
+
|
|
691
|
+
Use `UGC_API_KEY` for an existing org-scoped key. When that key already lives in
|
|
692
|
+
an app env file, set `UGC_ENV_FILE` to its path instead of copying the file/key.
|
|
693
|
+
Set `UGC_ORG_ID` in that file or the process environment to select the intended
|
|
694
|
+
organization; a JSON `orgId` overrides that default.
|
|
695
|
+
Environment credentials take precedence over the explicit env file, then saved
|
|
696
|
+
UGC CLI configuration and existing `.env` lookup. Acquisition and analysis need
|
|
697
|
+
`content:analyze` for restricted keys. The CLI can discover tool names with
|
|
698
|
+
`npx ugcinc --list`; JSON parameters/limits are documented in the media skill.
|
|
699
|
+
|
|
700
|
+
The media skill covers all eleven shipped acquisition/inspection operations,
|
|
701
|
+
including text-style fitting and the supporting analysis-status helper. The
|
|
702
|
+
content-automation skill covers reusable workflow inputs, real-reference review,
|
|
703
|
+
layout/readability, asset identity, procedural animation, CTA and audio handling.
|
|
704
|
+
It distinguishes measured facts from inference and requires verified public
|
|
705
|
+
HTTPS previews. SAM-dependent capabilities remain deferred and are not advertised
|
|
706
|
+
as available tools. A successful API call alone does not certify content quality.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ImageValue, TextValue, BooleanValue } from '../types';
|
|
1
|
+
import type { ImageValue, VideoValue, TextValue, BooleanValue } from '../types';
|
|
2
|
+
import type { IMessageConversationInput } from '../../imessage-conversation';
|
|
2
3
|
import { type InputType, type OutputMode, type SelectionMode } from './types';
|
|
3
4
|
/** Create DM message structure */
|
|
4
5
|
export interface CreateDmMessage {
|
|
@@ -8,6 +9,7 @@ export interface CreateDmMessage {
|
|
|
8
9
|
}
|
|
9
10
|
/** Create DM inputs - optional based on inputType settings */
|
|
10
11
|
export interface CreateDmNodeInputs {
|
|
12
|
+
conversation?: IMessageConversationInput;
|
|
11
13
|
platform?: TextValue;
|
|
12
14
|
'light-mode'?: BooleanValue;
|
|
13
15
|
username?: TextValue;
|
|
@@ -21,12 +23,15 @@ export interface CreateDmNodeInputs {
|
|
|
21
23
|
}
|
|
22
24
|
/** Create DM has a fixed 'output' port. */
|
|
23
25
|
export interface CreateDmNodeOutputs {
|
|
24
|
-
output: ImageValue;
|
|
26
|
+
output: ImageValue | VideoValue;
|
|
25
27
|
}
|
|
26
28
|
export type DmPlatform = 'imessage' | 'instagram';
|
|
27
29
|
declare const definition: import("./types").NodeDefinition<"create-dm", "generator", {
|
|
28
30
|
platform: DmPlatform;
|
|
29
31
|
platformInputType: InputType;
|
|
32
|
+
imessageRenderMode: "still" | "conversation";
|
|
33
|
+
imessageConversationInputType: InputType;
|
|
34
|
+
imessageConversation: IMessageConversationInput | undefined;
|
|
30
35
|
lightMode: boolean;
|
|
31
36
|
lightModeInputType: InputType;
|
|
32
37
|
username: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const imessage_conversation_1 = require("../../imessage-conversation");
|
|
3
4
|
const types_1 = require("./types");
|
|
4
5
|
// =============================================================================
|
|
5
6
|
// Node Definition
|
|
@@ -7,8 +8,8 @@ const types_1 = require("./types");
|
|
|
7
8
|
const definition = (0, types_1.defineNode)({
|
|
8
9
|
nodeId: 'create-dm',
|
|
9
10
|
label: 'Create DM',
|
|
10
|
-
description: 'Create
|
|
11
|
-
guide: '
|
|
11
|
+
description: 'Create DM screenshots or native iMessage videos and snapshots',
|
|
12
|
+
guide: 'Still mode renders legacy iMessage or Instagram screenshots with optional dynamic cropping. Conversation mode renders a native 60fps iMessage video with visible outgoing typing, incoming typing indicators, replies, reactions, receipts and group participants. Set imessageRenderMode to conversation and provide imessageConversation using the public IMessageConversationInput schema; alternatively connect the conversation object input. Native output defaults to video; output {kind: png, atMs: 500} exports a lossless native frame at 500ms, or omit atMs for the final frame. Static native PNG settings expose an image port; dynamic conversation inputs expose image|video. It requires platform imessage with static platform selection. Legacy still mode remains unchanged.',
|
|
12
13
|
type: 'generator',
|
|
13
14
|
category: 'Generation',
|
|
14
15
|
outputModes: ['per-input', 'single'],
|
|
@@ -16,6 +17,9 @@ const definition = (0, types_1.defineNode)({
|
|
|
16
17
|
defaults: {
|
|
17
18
|
platform: 'instagram',
|
|
18
19
|
platformInputType: 'static',
|
|
20
|
+
imessageRenderMode: 'still',
|
|
21
|
+
imessageConversationInputType: 'static',
|
|
22
|
+
imessageConversation: undefined,
|
|
19
23
|
lightMode: true,
|
|
20
24
|
lightModeInputType: 'static',
|
|
21
25
|
username: '',
|
|
@@ -48,7 +52,27 @@ const definition = (0, types_1.defineNode)({
|
|
|
48
52
|
outputMode: 'per-input',
|
|
49
53
|
selectionMode: null,
|
|
50
54
|
},
|
|
55
|
+
validate: config => {
|
|
56
|
+
if (config.imessageRenderMode !== 'conversation')
|
|
57
|
+
return [];
|
|
58
|
+
if (config.platform !== 'imessage' || config.platformInputType === 'variable') {
|
|
59
|
+
return ['Conversation video mode requires iMessage with static platform selection.'];
|
|
60
|
+
}
|
|
61
|
+
if (config.imessageConversationInputType === 'variable')
|
|
62
|
+
return [];
|
|
63
|
+
const parsed = imessage_conversation_1.iMessageConversationSchema.safeParse(config.imessageConversation);
|
|
64
|
+
return parsed.success ? [] : parsed.error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`);
|
|
65
|
+
},
|
|
51
66
|
computePorts: ({ config }) => {
|
|
67
|
+
if (config?.imessageRenderMode === 'conversation') {
|
|
68
|
+
return {
|
|
69
|
+
inputs: config.imessageConversationInputType === 'variable'
|
|
70
|
+
? [{ id: 'conversation', type: 'object', isArray: false, required: true }]
|
|
71
|
+
: [],
|
|
72
|
+
outputs: [{ id: 'output', type: config.imessageConversationInputType === 'variable' ? ['image', 'video']
|
|
73
|
+
: config.imessageConversation?.output?.kind === 'png' ? 'image' : 'video', isArray: false, required: true }],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
52
76
|
const inputs = [];
|
|
53
77
|
if (config?.platformInputType === 'variable') {
|
|
54
78
|
inputs.push({ id: 'platform', type: 'text', isArray: false, required: true });
|
|
@@ -101,6 +101,9 @@ export declare const nodeDefinitions: {
|
|
|
101
101
|
readonly 'create-dm': NodeDefinition<"create-dm", "generator", {
|
|
102
102
|
platform: import("./create-dm").DmPlatform;
|
|
103
103
|
platformInputType: import("./types").InputType;
|
|
104
|
+
imessageRenderMode: "still" | "conversation";
|
|
105
|
+
imessageConversationInputType: import("./types").InputType;
|
|
106
|
+
imessageConversation: import("../..").IMessageConversationInput | undefined;
|
|
104
107
|
lightMode: boolean;
|
|
105
108
|
lightModeInputType: import("./types").InputType;
|
|
106
109
|
username: string;
|
|
@@ -333,6 +336,8 @@ export declare const nodeDefinitions: {
|
|
|
333
336
|
__TOutputs: import("./screenshot-animation").ScreenshotAnimationNodeOutputs;
|
|
334
337
|
};
|
|
335
338
|
readonly 'remotion-composition': NodeDefinition<"remotion-composition", "generator", {
|
|
339
|
+
fps: number;
|
|
340
|
+
durationInFrames: number;
|
|
336
341
|
width: number;
|
|
337
342
|
height: number;
|
|
338
343
|
source: string;
|
|
@@ -340,13 +345,11 @@ export declare const nodeDefinitions: {
|
|
|
340
345
|
selectionMode: null;
|
|
341
346
|
outputType: "video" | "image";
|
|
342
347
|
inputs: {
|
|
343
|
-
type: "number" | "boolean" | "object" | "
|
|
348
|
+
type: "number" | "boolean" | "object" | "text" | "video" | "image" | "audio";
|
|
344
349
|
id: string;
|
|
345
350
|
isArray: boolean;
|
|
346
351
|
required: boolean;
|
|
347
352
|
}[];
|
|
348
|
-
fps: number;
|
|
349
|
-
durationInFrames: number;
|
|
350
353
|
inputProps: Record<string, unknown>;
|
|
351
354
|
}, {
|
|
352
355
|
[x: string]: import("../types").PortValue | import("../types").PortValue[];
|
|
@@ -4,6 +4,8 @@ type Outputs = {
|
|
|
4
4
|
output: VideoValue;
|
|
5
5
|
};
|
|
6
6
|
declare const defaults: {
|
|
7
|
+
fps: number;
|
|
8
|
+
durationInFrames: number;
|
|
7
9
|
width: number;
|
|
8
10
|
height: number;
|
|
9
11
|
source: string;
|
|
@@ -11,16 +13,16 @@ declare const defaults: {
|
|
|
11
13
|
selectionMode: null;
|
|
12
14
|
outputType: "video" | "image";
|
|
13
15
|
inputs: {
|
|
14
|
-
type: "number" | "boolean" | "object" | "
|
|
16
|
+
type: "number" | "boolean" | "object" | "text" | "video" | "image" | "audio";
|
|
15
17
|
id: string;
|
|
16
18
|
isArray: boolean;
|
|
17
19
|
required: boolean;
|
|
18
20
|
}[];
|
|
19
|
-
fps: number;
|
|
20
|
-
durationInFrames: number;
|
|
21
21
|
inputProps: Record<string, unknown>;
|
|
22
22
|
};
|
|
23
23
|
declare const _default: import("./types").NodeDefinition<"remotion-composition", "generator", {
|
|
24
|
+
fps: number;
|
|
25
|
+
durationInFrames: number;
|
|
24
26
|
width: number;
|
|
25
27
|
height: number;
|
|
26
28
|
source: string;
|
|
@@ -28,13 +30,11 @@ declare const _default: import("./types").NodeDefinition<"remotion-composition",
|
|
|
28
30
|
selectionMode: null;
|
|
29
31
|
outputType: "video" | "image";
|
|
30
32
|
inputs: {
|
|
31
|
-
type: "number" | "boolean" | "object" | "
|
|
33
|
+
type: "number" | "boolean" | "object" | "text" | "video" | "image" | "audio";
|
|
32
34
|
id: string;
|
|
33
35
|
isArray: boolean;
|
|
34
36
|
required: boolean;
|
|
35
37
|
}[];
|
|
36
|
-
fps: number;
|
|
37
|
-
durationInFrames: number;
|
|
38
38
|
inputProps: Record<string, unknown>;
|
|
39
39
|
}, Inputs, Outputs, false> & {
|
|
40
40
|
__TInputs: Inputs;
|
package/dist/cli.js
CHANGED
|
@@ -72,6 +72,11 @@ function saveConfig(config) {
|
|
|
72
72
|
function loadEnvVar(name) {
|
|
73
73
|
if (process.env[name])
|
|
74
74
|
return process.env[name];
|
|
75
|
+
if (process.env.UGC_ENV_FILE) {
|
|
76
|
+
const value = checkEnvFile(path.resolve(process.env.UGC_ENV_FILE), name);
|
|
77
|
+
if (value)
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
75
80
|
// Check ~/.ugcinc/config.json
|
|
76
81
|
const config = loadConfig();
|
|
77
82
|
if (name === 'UGC_API_KEY' && config.apiKey)
|
|
@@ -104,10 +109,12 @@ function printUsage() {
|
|
|
104
109
|
console.log('');
|
|
105
110
|
console.log('Commands:');
|
|
106
111
|
console.log(' auth <api_key> [--admin] Save API key (use --admin for admin keys)');
|
|
107
|
-
console.log(' init
|
|
112
|
+
console.log(' init [--update] [skills...] Install skills; --update backs up changed files');
|
|
108
113
|
console.log(' <tool_name> [json_params] Run an API tool');
|
|
109
114
|
console.log(' --list List all available tools as JSON');
|
|
110
115
|
console.log(' --help Show this help message');
|
|
116
|
+
console.log(' UGC_ENV_FILE Read credentials from an existing env file');
|
|
117
|
+
console.log(' UGC_ORG_ID Default organization (JSON orgId takes priority)');
|
|
111
118
|
console.log('');
|
|
112
119
|
console.log('Available tools:');
|
|
113
120
|
for (const tool of tools_1.allTools) {
|
|
@@ -130,20 +137,47 @@ function runAuth(args) {
|
|
|
130
137
|
if (config.admin === 'true')
|
|
131
138
|
console.log('Mode: admin');
|
|
132
139
|
}
|
|
133
|
-
function runInit() {
|
|
140
|
+
function runInit(args) {
|
|
134
141
|
const cwd = process.cwd();
|
|
135
142
|
const skillsDir = path.join(cwd, '.claude', 'skills');
|
|
143
|
+
const update = args.includes('--update');
|
|
144
|
+
const requested = args.filter(arg => !arg.startsWith('--'));
|
|
145
|
+
const known = new Set(Object.keys(skills_1.skillFiles).map(name => name.split('/')[0] ?? ''));
|
|
146
|
+
const unknown = args.filter(arg => arg.startsWith('--') && arg !== '--update');
|
|
147
|
+
const invalid = requested.filter(name => !known.has(name));
|
|
148
|
+
if (unknown.length || invalid.length) {
|
|
149
|
+
console.error('Unknown skill or option: ' + [...unknown, ...invalid].join(', '));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
136
152
|
let created = 0;
|
|
137
153
|
let skipped = 0;
|
|
154
|
+
let updated = 0;
|
|
155
|
+
let backupDir;
|
|
138
156
|
for (const [skillPath, content] of Object.entries(skills_1.skillFiles)) {
|
|
157
|
+
if (requested.length && !requested.includes(skillPath.split('/')[0] ?? ''))
|
|
158
|
+
continue;
|
|
139
159
|
const fullPath = path.join(skillsDir, skillPath);
|
|
140
160
|
const dir = path.dirname(fullPath);
|
|
141
161
|
if (!fs.existsSync(dir)) {
|
|
142
162
|
fs.mkdirSync(dir, { recursive: true });
|
|
143
163
|
}
|
|
144
164
|
if (fs.existsSync(fullPath)) {
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
if (!update || fs.readFileSync(fullPath, 'utf-8') === content) {
|
|
166
|
+
skipped++;
|
|
167
|
+
console.log(` skip ${skillPath} (already exists${update ? '; unchanged' : ''})`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!backupDir) {
|
|
171
|
+
const base = path.join(cwd, '.ugcinc', 'skill-backups');
|
|
172
|
+
fs.mkdirSync(base, { recursive: true });
|
|
173
|
+
backupDir = fs.mkdtempSync(path.join(base, 'update-'));
|
|
174
|
+
}
|
|
175
|
+
const backup = path.join(backupDir, skillPath);
|
|
176
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
177
|
+
fs.copyFileSync(fullPath, backup, fs.constants.COPYFILE_EXCL);
|
|
178
|
+
fs.writeFileSync(fullPath, content);
|
|
179
|
+
updated++;
|
|
180
|
+
console.log(` update ${skillPath} (previous copy backed up)`);
|
|
147
181
|
}
|
|
148
182
|
else {
|
|
149
183
|
fs.writeFileSync(fullPath, content);
|
|
@@ -152,7 +186,9 @@ function runInit() {
|
|
|
152
186
|
}
|
|
153
187
|
}
|
|
154
188
|
console.log('');
|
|
155
|
-
console.log(`Done! Created ${created}
|
|
189
|
+
console.log(`Done! Created ${created}, updated ${updated}, skipped ${skipped} skill files.`);
|
|
190
|
+
if (backupDir)
|
|
191
|
+
console.log('Backups: ' + backupDir);
|
|
156
192
|
console.log('');
|
|
157
193
|
console.log('Next steps:');
|
|
158
194
|
console.log(' 1. Set your API key: npx ugcinc auth <your-api-key>');
|
|
@@ -162,6 +198,7 @@ function runInit() {
|
|
|
162
198
|
console.log(' /accounts Manage accounts, warmup, and social profiles');
|
|
163
199
|
console.log(' /posts Schedule, update, and manage posts');
|
|
164
200
|
console.log(' /media Manage media library and social audio');
|
|
201
|
+
console.log(' /content-automation Develop and review reusable content workflows');
|
|
165
202
|
console.log(' /automations Run and manage automation workflows');
|
|
166
203
|
console.log(' /stats View account and post statistics');
|
|
167
204
|
}
|
|
@@ -191,8 +228,12 @@ async function runTool(toolName, rawParams) {
|
|
|
191
228
|
delete parsed.orgId;
|
|
192
229
|
}
|
|
193
230
|
const config = loadConfig();
|
|
194
|
-
const
|
|
195
|
-
const
|
|
231
|
+
const explicitEnvKey = process.env.UGC_API_KEY;
|
|
232
|
+
const explicitFileKey = process.env.UGC_ENV_FILE
|
|
233
|
+
? checkEnvFile(path.resolve(process.env.UGC_ENV_FILE), 'UGC_API_KEY') : undefined;
|
|
234
|
+
const usesSavedKey = !explicitEnvKey && !explicitFileKey && apiKey === config.apiKey;
|
|
235
|
+
const isAdmin = usesSavedKey && config.admin === 'true';
|
|
236
|
+
const client = new client_1.UGCClient({ apiKey, orgId: orgId ?? loadEnvVar('UGC_ORG_ID') ?? (usesSavedKey ? config.orgId : undefined), admin: isAdmin });
|
|
196
237
|
let params = undefined;
|
|
197
238
|
if (parsed) {
|
|
198
239
|
const parseResult = tool.schema.safeParse(parsed);
|
|
@@ -239,7 +280,7 @@ async function main() {
|
|
|
239
280
|
process.exit(0);
|
|
240
281
|
}
|
|
241
282
|
if (args[0] === 'init') {
|
|
242
|
-
runInit();
|
|
283
|
+
runInit(args.slice(1));
|
|
243
284
|
process.exit(0);
|
|
244
285
|
}
|
|
245
286
|
await runTool(args[0], args[1]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const contentSkillFiles: Record<string, string>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.contentSkillFiles = void 0;
|
|
4
|
+
// Generated by scripts/build-skills.cjs; edit skills/** instead.
|
|
5
|
+
exports.contentSkillFiles = {
|
|
6
|
+
"media/SKILL.md": "---\nname: media\ndescription: Acquire TikTok and Instagram posts and inspect images, video and audio with UGC Inc tools. Use for reference analysis, OCR, frames, scenes, transcription, colors, layout, text-style fitting, or media-library management.\nallowed-tools: Bash, Read\n---\n\nUse the installed UGC Inc CLI:\n\n```bash\nnpx ugcinc --list\nnpx ugcinc <tool_name> '<json_params>'\n```\n\nCredentials come from `UGC_API_KEY` or existing UGC configuration. Use the user's\norganization and a key with `content:analyze` for acquisition/analysis. If using\nan admin key, pass the authorized `orgId` in the JSON parameters. Do not print\ncredentials, copy an administrator key to another user's machine, or change\norganizations to bypass a denial. Confirm access with one small tool call.\nIf credentials already live in an app env file, set `UGC_ENV_FILE` to that file's\npath so the CLI reads the existing key without duplicating its contents. Set\n`UGC_ORG_ID` in that file or the process environment to scope calls to the user's\norganization; an explicit JSON `orgId` takes priority.\n\nRead [references/tools.md](references/tools.md) for request shapes, result fields\nand limits. The CLI lists names/descriptions; the reference explains parameters.\n\n## Choose the input and tools\n\n- A profile URL: `list_profile_posts` discovers a bounded page; follow its cursor\n only as needed. Listing does not download media or guarantee complete history.\n- A post URL: `download_social_post` ingests its ordered assets into owned media.\n Use returned asset URLs for analysis; the TikTok/Instagram HTML URL is not a\n video file. Check complete/partial/failed status and failed assets.\n- A direct media URL: use the corresponding inspection tool directly.\n- Video: `detect_scenes`, `extract_frames`, `read_video_text`; audio tools accept\n video too. Inspect multiple moments, including changes near the beginning/end.\n- Image: `read_image_text`, `sample_colors`, `measure_layout` using supplied\n boxes/masks, and `match_text_style` for a specified text crop.\n- Audio: `transcribe_audio` and `measure_audio_levels`.\n\nThe SDK/CLI waits for processing automatically. Check both the outer response's\n`ok` and the returned job's `status`. Analysis is usable when\n`data.status === \"completed\"`; its content is `data.result`. If still pending or\nprocessing, keep its job ID and use `get_media_analysis` rather than resubmitting.\nUse bounded retries for retryable failures; report persistent errors/partial\ncoverage. A zero process exit code alone does not establish API success.\n\n## Interpret evidence accurately\n\n- Coordinates are display-oriented source pixels; times are source-relative\n milliseconds. Use returned decoded timestamps, dimensions and coverage.\n- OCR is English, may miss stylized text, and can be empty on unreadable input.\n `fast` video OCR leaves uninspected intervals. `frame_scan` is bounded and\n cannot inspect an arbitrarily long clip in one request.\n- Speech timestamps are estimates; speaker labels/forced alignment are absent.\n A transcript does not establish music, emotion, delivery or sound effects.\n- Color sampling measures decoded sRGB pixels, not guaranteed design hex values.\n Layout measurement does not detect objects or recover original layer files.\n- Text-style matching returns supported UGC settings and visual residuals. Font\n identity is unverified; inspect the preview before calling a fit accurate.\n It has a restricted ASCII/font/style surface; the native TikTok renderer is a\n separate bounded rendering capability, not proof that matching is exact.\n- SAM/object segmentation, automatic overlay detection, element/mask tracking,\n motion measurement, extraction, media comparison/alignment and semantic search\n are not available through this tool set. Do not invent those calls or install\n SAM. Existing OCR text tracks remain available.\n\n## Review and share media\n\nOpen returned image/contact-sheet previews and review the relevant sequence.\nWhen the agent runtime can consume audio/video, use it. Otherwise use the\navailable transcript, audio preview and level measurements and explicitly state\nwhat was not listened to or inspected. Do not claim to have watched/listened\nbecause an analysis request succeeded.\n\nShare only verified browser-accessible HTTPS artifacts. Fetch without auth and\ncheck status, content type and decoding; for video also check byte-range support.\nUse the returned owned-media URLs. Local paths, localhost/private addresses and\nexpiring upstream CDN links are not deliverable previews. If an artifact needs\nuploading, use the supported owned storage path; do not claim a file is hosted\nbecause a media record was created for an arbitrary URL.\n\nFor building a reusable content format, use the `content-automation` skill.\n",
|
|
7
|
+
"media/references/tools.md": "# UGC media tool requests and results\n\nRun `npx ugcinc TOOL 'JSON'`. Shell quoting varies on Windows; if the shell\nchanges the JSON, use a small Node script with `UGCClient` and its `media`\nmethods instead of repeatedly sending malformed requests. For example:\n\n```js\nconst { UGCClient } = require('ugcinc');\nconst client = new UGCClient({\n apiKey: process.env.UGC_API_KEY,\n orgId: process.env.UGC_ORG_ID,\n});\n// Use admin:true only with an authorized administrator credential.\n```\n\nRequest URLs must be HTTPS. Analysis source URLs must not contain credentials\nor fragments. Image operations support still PNG/JPEG/WebP, at most4096pixels\nper side; the shared download limit is100MiB. Geometry must fit the actual\ndisplay-oriented source. Respect more restrictive operation limits below.\n\n## Acquisition\n\n| CLI tool / SDK method | Parameters | Result |\n| --- | --- | --- |\n| `list_profile_posts` / `media.listProfilePosts` | `{url, limit?, cursor?}`; TikTok/Instagram profile; limit1–50 | `data.posts`, profile metadata, continuation cursor. Follow the actual returned cursor, not a fabricated page number. |\n| `download_social_post` / `media.downloadSocialPost` | `{url}`; canonical TikTok/Instagram post URL | `data.status`, `assets[]` in order, `failedAssets[]`, `manifestUrl`, post/sound metadata. Each asset has `url`, `mediaId`, dimensions, type, duration, audio presence and hash. Repeat the same URL to resume retryable partial imports. |\n\nEmbedded video audio remains attached. Photo assets have no embedded soundtrack;\nsound attribution does not mean audio bytes were downloaded. Preserve sound\nID/link separately; never replace a post excerpt with the full sound-page track\nand call them identical. Partial imports are not a complete carousel.\n\n## Analysis\n\nThese return `{ok, data:{jobId,status,operation,result?,error?}}`. Use the completed\n`data.result` fields listed below. Failure can occur after successful submission.\n\n| CLI tool / SDK method | Parameters | Result content |\n| --- | --- | --- |\n| `extract_frames` / `media.extractFrames` | `{url,timestampsMs:[0,1500,3000],selection:\"nearest\",contactSheet:true}` OR `{url,range:{startMs,endMs,intervalMs}}`; choose exactly one selector, at most48samples | `frames[]` with requested/decoded time, source PTS, dimensions and URL; optional `contactSheet` with individual frame links; source metadata. |\n| `detect_scenes` / `media.detectScenes` | `{url}` | `scenes[]` with start/end milliseconds, frame indexes, source PTS; no split MP4 exports. |\n| `read_image_text` / `media.readImageText` | `{url}` | `regions[]` with text/rawText, polygons/boxes/confidence; inferred `lines[]`/reading order; `previewUrl`. |\n| `read_video_text` / `media.readVideoText` | `{url,mode:\"fast\",intervalMs:1000,range?:{startMs,endMs}}` OR `{url,mode:\"frame_scan\",range?:{startMs,endMs}}` | `frames[]`, text `tracks[]` with observations/positions; explicit `coverage.inspected` and `uninspected`. Maximum240inspected frames and120seconds per request. `intervalMs` only applies to fast mode. |\n| `transcribe_audio` / `media.transcribeAudio` | `{url,range?:{startMs,endMs},language?:\"auto\",timing?:\"word\",channel?:0}` | `status`, `text`, timed `segments[]` and estimated words, warnings, `audioPreviewUrl`. Range at most120seconds; timing `word` or `segment`; optional channel0/1. Empty/no-audio/no-speech states are distinct. |\n| `measure_audio_levels` / `media.measureAudioLevels` | `{url,range?:{startMs,endMs},windowMs?:100,silenceThresholdDbfs?:-50}` | Native-channel RMS/sample peaks, loudness timeline/LUFS, silence, possible clipping, `previewUrl`. Windows20–5000ms, threshold−100to−10dBFS, range at most120seconds. |\n| `sample_colors` / `media.sampleColors` | `{url,maxColors?:8,point?:{x,y},box?:{x,y,width,height},mask?}`; choose at most one selection | Observed RGBA point or palette/proportions, omitted mass, declared color policy, `previewUrl`. Palette1–32colors. |\n| `measure_layout` / `media.measureLayout` | `{url,elements:[{id,box:{x,y,width,height}}]}`; each element may instead have `mask` | Margins, centers, spacing/alignment deltas, intersections/IoU and `previewUrl`.1–32unique IDs. Boxes are supplied, not automatically detected. |\n| `match_text_style` / `media.matchTextStyle` | `{url,box:{x,y,width,height},text?}`; integer crop16–1536wide,16–512high; optional supplied text | `status`, candidates with real image-editor `element`/`previewConfig`, `maskIoU`, `foregroundMae`, `quality`, previews/comparison and warnings. Up to160printable ASCII characters/four lines; supported fonts Arial/TikTok/Apple. Missing/unsupported styles may be unavailable or approximations. |\n| `get_media_analysis` / `media.getMediaAnalysis` | `{jobId}` | Current status/result in the current organization; use when automatic waiting returned pending/processing. |\n\nBoxes are half-open source-pixel rectangles. For masks used by colors/layout:\n`{width,height,counts}` uses row-major binary RLE, alternating background then\nforeground. Counts must sum to width×height. This is not COCO's column-major\nconvention. Supply only masks you actually have; automatic segmentation is off.\n\n## Existing media library\n\n- `list_media({ids?,tag?})`, `list_social_audio({ids?,tag?})`.\n- `create_media_from_urls({urls,names?,tag?,sourceIds?,useName?})` registers URLs;\n it does not turn an inaccessible local file into a public upload.\n- `import_text({texts:[{content,name?,tag?}]})`.\n- `create_social_audio({url,tag?})` imports social audio separately; do not use it\n as proof of the exact soundtrack/mix/offset in a downloaded reference video.\n- `delete_media({ids})` removes media/storage; only use when deletion is requested.\n",
|
|
8
|
+
"content-automation/SKILL.md": "---\nname: content-automation\ndescription: Turn reference social posts or a rough content idea into a reusable UGC Inc workflow, with editable inputs and reviewed visual/audio output. Use for developing and validating content formats rather than scheduling existing finished posts.\nallowed-tools: Bash, Read\n---\n\nBuild the format the user wants and collaborate at the level they requested.\nWhen they want to discuss, compare or review a specific artifact, do that before\nmaking additional format decisions. Keep questions concise and ask only when a\nmissing answer prevents useful work. Do not impose a new per-video reference input\nwhen the user wants a fixed reusable visual format.\n\nUse the `media` skill for acquisition/inspection and the `automations` skill for\ncurrent workflow tool calls. Read [references/build-review.md](references/build-review.md)\nwhen constructing or reviewing a workflow. Use only shipped tools/nodes discovered\nthrough `npx ugcinc --list`, `list_node_types` and `get_node_type`.\n\n## Understand and specify the format\n\nAcquire a bounded set of real references and inspect full frames and changes over\ntime, including audio using available modalities. A contact sheet or transcript\nalone cannot establish all animation/pacing/music details. State uninspected\nparts instead of claiming playback. Distinguish reusable formats from variants;\nseveral posts may use the same structure with different assets, copy or timing.\n\nWrite a compact spec: what the viewer sees first, the sequence/reveal, required\ninputs, reusable assets/components, changing copy, audio source/timing and final\naction/CTA if required by this format. Separate measured facts from inference\nand user choices. Share a verified HTTPS reference/preview when the user needs\nto identify the parts they want changed.\n\nClassify assets by replacement behavior. A recognizable Mii, Wojak or other exact\nmeme image may need a static asset; an ordinary or newly generated face need not\nbe static just because it looks meme-like. Preserve a headshot's identity across\nvariants when requested, while making intended differences visibly meaningful.\n\n## Build reusable production behavior\n\nSeparate layout, data/assets and motion. Expose the inputs a normal run needs:\nasset pools/overrides, text pools, contact/destination, duration/timing and seed\nwhere appropriate. Keep canonical outputs independent of an operator's laptop.\nImplement procedural animations as reusable components rendered on demand;\ndo not substitute one pre-rendered clip when each run should vary. Randomness\nmust respect the intended visual roles and remain replayable from saved inputs.\n\nReuse existing nodes/compositions and actual public schemas. Analysis tools are\navailable as CLI/SDK operations; do not assume every one is a workflow node.\nValidate the graph before executing. Prefer a reviewable output over publishing\nor enabling recurring runs unless the user authorized those actions.\n\n## Make the content work\n\n- Keep the hierarchy clear: readable hook, focal content, then the intended CTA.\n Give characters/subjects enough visual weight; align visible content within\n its section instead of trusting transparent image bounds alone.\n- Measure margins, spacing, text wrapping and overlap. Check at phone viewing\n size, not only on a large canvas. Keep important text away from platform UI;\n inspect the relevant target presentation rather than assuming universal margins.\n- Use sampled reference colors and real render previews. Judge font, stroke,\n kerning, line height and emoji spacing together. Treat text-style fitting as a\n measured candidate, not proof of an exact font or pixel-perfect output.\n- Match the reference post's actual audio excerpt, mix, start and duration when\n requested. Preserve attribution separately. Empty transcription can mean music\n or unrecognized speech, not silence. Keep visual reveals and audio timing coherent.\n- Keep required CTA/contact copy present and legible across every requested\n variant. Use the user's actual destination/formatting, not a number copied from\n an old example. Vary copy intentionally without altering the required action.\n- Test with real content and multiple asset/copy lengths. A composition that\n works with one face or one short headline is not a validated reusable format.\n Inspect early, changing and final states; a still cannot certify a full video.\n\n## Deliver and describe the boundary\n\nProvide the workflow, its normal inputs, replayable example parameters, verified\nHTTPS previews, and any observed visual/audio limits. For videos verify decoding,\nduration/dimensions/audio and byte-range playback; images need correct content\ntype and decoding. Do not use local/localhost/private URLs as deliverables.\n\nSAM and automatic segmentation/overlay tracking are deferred. Use supplied\nassets, user-supplied boxes/masks or honest visual analysis; do not claim recovered\nsource layers or automatic clean cutouts. If the runtime cannot inspect a media\nmodality, record that limit and do not certify the uninspected property.\n",
|
|
9
|
+
"content-automation/references/build-review.md": "# Construct and review a reusable workflow\n\n## Discover the real interface\n\n`npx ugcinc list_node_types '{}'` lists current node metadata.\nUse `get_node_type '{\"nodeType\":\"...\"}'` for the nodes you choose and read the\nbundled `automations` skill for current tool parameters. These tools still need\na configured CLI key even when their own operation is local.\n\nConnections live on the target node, not a separate edges array:\n\n```json\n{\n \"nodes\": [\n {\n \"id\": \"target\",\n \"type\": \"node-type-from-discovery\",\n \"config\": {},\n \"inputs\": {\n \"actual-input-port\": {\n \"sourceNodeId\": \"source\",\n \"sourceOutputId\": \"actual-output-port\"\n }\n }\n }\n ]\n}\n```\n\nUse actual IDs/configuration returned by discovery, not these placeholders.\nConfigure dynamic interfaces before wiring them. Re-check ports after config\nchanges. Current validation requires a terminal outcome; inspect pass-through,\nsave-to-media and posting nodes to choose the intended behavior. Publishing is\nnot required just to make a reviewable workflow output.\n\nFor reusable sub-workflows, configure their input/output contracts explicitly\nand inspect the Compose Workflow node's actual schema. For custom Remotion,\ninspect `remotion-composition`; use its supported composition/props/import\ncontract. Do not invent an upload/register API or rely on arbitrary packages.\n\nValidate using `validate_workflow` with the shape described by the installed\nautomation skill/tool. Execute only the intended single test scope, inspect its\nstatus/output, and retain resolved inputs and seed. Saving a template or receiving\na run ID alone does not prove the output renders correctly.\n\n## Review a representative run\n\nCheck the viewer's actual sequence: hook visibility, subject size, changing state,\ntext wrapping/CTA, transitions and finish. Compare the full frame before zooming\ninto details. Inspect multiple points around animation/text changes. Re-test a\nlonger caption and a different asset where those vary in normal runs.\n\nDistinguish observed pixels/timestamps from estimates. Examples: sRGB sampling\ndoes not recover design colors from compression, transcript word timestamps are\nestimated, sampled OCR leaves gaps, and bounding boxes are not exact ink bounds.\nManually supplied boxes are allowed; describe how they were determined.\n\nPreserve both soundtrack bytes and source attribution. Check whether music is\nembedded or only attributed, whether the output repeats/truncates it, and whether\nvisual changes coincide with the intended moments. Do not invent beat detection,\naudio separation, universal media comparison or other unavailable tools.\n\nWhen revising content, name the concrete issue and inspect the changed output.\nKeep reusable parameters instead of baking a correction into one reference\nimage. Report remaining defects plainly; avoid calling a draft production-ready\nbased only on successful API execution.\n"
|
|
10
|
+
};
|