@starreel/mcp 0.1.66 → 0.1.67
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/SKILL.md +31 -0
- package/dist/tools/produce.js +148 -0
- package/openapi.json +2957 -1393
- package/package.json +1 -1
- package/server.json +2 -2
package/SKILL.md
CHANGED
|
@@ -624,6 +624,37 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
|
|
|
624
624
|
shots in one call)
|
|
625
625
|
- **Identity & consistency**: `generate_character_portraits`, `upload_image`,
|
|
626
626
|
`set_character_portrait`, `generate_character_sheet`, `extract_visual_lock`
|
|
627
|
+
- **Wardrobe**: `list/create/get/update/delete_wardrobe`,
|
|
628
|
+
`regenerate_wardrobe_image`, and the two-step timeline
|
|
629
|
+
`build_wardrobe_timeline` (LLM, **billed** — computes per-shot change
|
|
630
|
+
suggestions, changes nothing) → walk the diff past the user → `apply_wardrobe_timeline`
|
|
631
|
+
(or `apply_wardrobe_appearances` when several characters share a frame).
|
|
632
|
+
**What an outfit actually is here is a structured binding**
|
|
633
|
+
(`active_wardrobe_id` / `appearances[].wardrobeAssetId`), not prose in the
|
|
634
|
+
shot description — editing the text via `update_shot` and leaving the binding
|
|
635
|
+
alone leaves the character in the old outfit.
|
|
636
|
+
- **Continuity ledger**: `get_world_state` (which props / garments / injuries
|
|
637
|
+
are tracked, and where two shots disagree), `rebuild_world_state` (LLM,
|
|
638
|
+
**billed** — rerun it when `stale=true`), `add/delete_world_state_event`
|
|
639
|
+
(explain away a conflict the AI mis-read instead of editing shots),
|
|
640
|
+
`link_world_state_scene_variant`, `derive_pose_chain` (LLM, **billed** — fills
|
|
641
|
+
only the shots whose poses are blank, so cuts connect).
|
|
642
|
+
- **Era contract**: `set_era_contract` / `get_era_contract` — see the project
|
|
643
|
+
settings tier above. Non-contemporary projects must set this before frames.
|
|
644
|
+
- **Lengthen a finished cut**: `quote_extend_final` (free, quote first) →
|
|
645
|
+
`extend_final` (**billed** per round) → `get_extend_final` → `apply_extend_final`.
|
|
646
|
+
The extension is *not* the episode cut until you apply it, and applying
|
|
647
|
+
**replaces** the current cut. A 403 means the drama's video engine isn't
|
|
648
|
+
eligible — don't retry.
|
|
649
|
+
- **Recover an older asset**: `get_asset_recovery` lists cuts and candidate
|
|
650
|
+
frames that were superseded. Reach for it when the user says "the previous
|
|
651
|
+
version was better". Apply them **one at a time, only after the user confirms
|
|
652
|
+
each**: `apply_recovered_final`, or `apply_recovered_candidate` (which
|
|
653
|
+
requires an explicit `target_storyboard_id` — the platform will not guess
|
|
654
|
+
which shot a recovered frame belongs to). `rollback_asset_recovery` undoes one.
|
|
655
|
+
- **Marketing export**: `export_sheet_compare` / `get_sheet_compare` — the
|
|
656
|
+
character-sheet-vs-frame comparison sheet. **Off by default per drama**; a 403
|
|
657
|
+
or `enabled: false` means the user has to switch it on in the drama settings.
|
|
627
658
|
- **Shots → video**: `quote/generate_storyboards`, `get_storyboards`,
|
|
628
659
|
`quote/generate_frames`, `chain_frames`, `quote/generate_videos`
|
|
629
660
|
- **Audio**: `generate_tts` (required before final cut), `clone_voice`,
|
package/dist/tools/produce.js
CHANGED
|
@@ -629,6 +629,154 @@ export function registerProduceTools(server, client) {
|
|
|
629
629
|
server.tool('get_final_cut', '查某一集成片状态与下载链接。status=completed 时返回 download_url(我方 COS 直链,可直接下载)。免费。' +
|
|
630
630
|
'★bgm_stale=true 表示配乐在成片之后生成/改动、尚未进成片:重新 compose_episode(免费)即可,别用 re-render。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/final-cut`)));
|
|
631
631
|
server.tool('get_export', '查某一集导出/母版状态(成片终拼后的可下载母版)。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/export`)));
|
|
632
|
+
server.tool('get_asset_recovery', '列本集可恢复的**历史产物**(免费·只读):以前生成过、后来被覆盖掉的成片与候选帧。'
|
|
633
|
+
+ '\n用途:客户说「上一版那个好」「前天那张图比现在这张好」时,从这里找回来。'
|
|
634
|
+
+ '\n★这一步只看不改。把候选逐条讲给客户,**客户逐条点头**之后再 apply。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/asset-recovery`)));
|
|
635
|
+
server.tool('apply_recovered_final', '把某个**历史终拼**设为本集当前成片(免费,指针切换)。'
|
|
636
|
+
+ '\n★这会**覆盖**本集现在对外的那一版成片。先用 get_asset_recovery 列出来、'
|
|
637
|
+
+ '把是哪一版(时间/时长)讲清楚,**客户逐条确认**再调——不要凭「看起来更好」自己决定。'
|
|
638
|
+
+ '\n★之后重新 compose_episode(终拼)会再把它覆盖回去。', {
|
|
639
|
+
episode_id: z.number().int().positive(),
|
|
640
|
+
merge_id: z.number().int().positive().describe('取自 get_asset_recovery 的 finals[].id'),
|
|
641
|
+
}, async ({ episode_id, merge_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/asset-recovery/finals/${merge_id}/apply`, {})));
|
|
642
|
+
server.tool('apply_recovered_candidate', '把某张**历史候选帧**回填到指定镜头(免费)。'
|
|
643
|
+
+ '\n★**必须显式指定 target_storyboard_id**:这张图要回到哪一镜只有客户知道,平台不替你猜——'
|
|
644
|
+
+ '猜错目标镜比不做更糟(会把另一镜正在用的帧换掉)。'
|
|
645
|
+
+ '\n★同样是**逐条确认**:一次只回填一张,先把「哪张图、回到第几镜」讲给客户听。', {
|
|
646
|
+
episode_id: z.number().int().positive(),
|
|
647
|
+
generation_id: z.number().int().positive().describe('取自 get_asset_recovery 的 candidates[].id'),
|
|
648
|
+
target_storyboard_id: z.number().int().positive().describe('回填到哪一镜(必填,不猜)'),
|
|
649
|
+
}, async ({ episode_id, generation_id, target_storyboard_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/asset-recovery/candidates/${generation_id}/apply`, { target_storyboard_id })));
|
|
650
|
+
server.tool('rollback_asset_recovery', '把一次已应用的恢复**退回去**(免费)。apply 错了就用它撤销,恢复到应用之前的状态。', {
|
|
651
|
+
episode_id: z.number().int().positive(),
|
|
652
|
+
event_id: z.number().int().positive().describe('取自 get_asset_recovery 的恢复事件 id'),
|
|
653
|
+
}, async ({ episode_id, event_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/asset-recovery/events/${event_id}/rollback`, {})));
|
|
654
|
+
server.tool('list_wardrobe', '列本剧的**服装资产库**(免费)。每件服装是一个可被镜头引用的结构化资产,'
|
|
655
|
+
+ '不是分镜里的文字描述。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/wardrobe`)));
|
|
656
|
+
server.tool('create_wardrobe', '新建一件服装资产(免费,纯文本写库)。建好之后要**绑到镜头上**才会影响出图——'
|
|
657
|
+
+ '用 apply_wardrobe_timeline / apply_wardrobe_appearances,或在官网逐镜挑。', {
|
|
658
|
+
drama_id: z.number().int().positive(),
|
|
659
|
+
name: z.string().describe('这件服装叫什么(角色在剧里的某一身)'),
|
|
660
|
+
character_id: z.number().int().positive().optional().describe('属于哪个角色;通用服装可不填'),
|
|
661
|
+
episode_id: z.number().int().positive().optional(),
|
|
662
|
+
description: z.string().optional().describe('款式/材质/颜色等外观描述'),
|
|
663
|
+
palette_hint: z.any().optional().describe('配色提示'),
|
|
664
|
+
reference_image_urls: z.array(z.string()).optional().describe('参考图直链'),
|
|
665
|
+
notes: z.string().optional(),
|
|
666
|
+
product_id: z.number().int().positive().optional().describe('带货场景:关联商品'),
|
|
667
|
+
sort_order: z.number().int().optional(),
|
|
668
|
+
}, async (args) => jsonResult(await client.producePost('/wardrobe', args)));
|
|
669
|
+
server.tool('get_wardrobe', '读一件服装资产的详情(免费)。', { wardrobe_id: z.number().int().positive() }, async ({ wardrobe_id }) => jsonResult(await client.produceGet(`/wardrobe/${wardrobe_id}`)));
|
|
670
|
+
server.tool('update_wardrobe', '改服装资产(免费)。改完**不会**自动重出已生成的图:要让画面跟上得再调 regenerate_wardrobe_image,'
|
|
671
|
+
+ '已出的镜头帧还要逐镜重出。', {
|
|
672
|
+
wardrobe_id: z.number().int().positive(),
|
|
673
|
+
name: z.string().optional(),
|
|
674
|
+
character_id: z.number().int().positive().optional(),
|
|
675
|
+
episode_id: z.number().int().positive().optional(),
|
|
676
|
+
description: z.string().optional(),
|
|
677
|
+
palette_hint: z.any().optional(),
|
|
678
|
+
reference_image_urls: z.array(z.string()).optional(),
|
|
679
|
+
notes: z.string().optional(),
|
|
680
|
+
product_id: z.number().int().positive().optional(),
|
|
681
|
+
sort_order: z.number().int().optional(),
|
|
682
|
+
}, async ({ wardrobe_id, ...fields }) => jsonResult(await client.producePut(`/wardrobe/${wardrobe_id}`, fields)));
|
|
683
|
+
server.tool('regenerate_wardrobe_image', '重出这件服装的资产图(**收费**,图片步按用量后付)。改完 description/参考图之后用它让图跟上。', { wardrobe_id: z.number().int().positive() }, async ({ wardrobe_id }) => jsonResult(await client.producePost(`/wardrobe/${wardrobe_id}/regenerate`, {})));
|
|
684
|
+
server.tool('delete_wardrobe', '软删一件服装资产(免费)。已绑定它的镜头不会被自动改,先确认没有镜头还在用。', { wardrobe_id: z.number().int().positive() }, async ({ wardrobe_id }) => jsonResult(await client.produceDelete(`/wardrobe/${wardrobe_id}`)));
|
|
685
|
+
server.tool('build_wardrobe_timeline', '算**服装时间线**:从剧本与分镜推出每个角色在各镜应该穿哪一身,并给出逐镜建议 diff。'
|
|
686
|
+
+ '**要花钱**(走 LLM)。这是第①步,**只算不改**。\n'
|
|
687
|
+
+ '★两段式:算完把 diff **逐条讲给客户**(哪一镜、从哪身换成哪身、为什么),'
|
|
688
|
+
+ '客户勾选后再用 apply_wardrobe_timeline 落库。别替客户全盘应用。\n'
|
|
689
|
+
+ '★**这一步解决的是「文字改了不生效」**:决定出图穿什么的是结构化绑定'
|
|
690
|
+
+ '(active_wardrobe_id / appearances[].wardrobeAssetId),不是 update_shot 里的文字描述。'
|
|
691
|
+
+ '只改文字、不改绑定,出图照旧穿原来那身。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/wardrobe-timeline`, {})));
|
|
692
|
+
server.tool('apply_wardrobe_timeline', '把客户勾选的换装建议落库(第②步,免费)。改的是每镜的 **active_wardrobe_id 结构化绑定**,'
|
|
693
|
+
+ '不是文字描述——这正是「改了 update_shot 的文字却不生效」那个坑的正解。\n'
|
|
694
|
+
+ '★只传客户点头的那几条;一次最多 200 条。\n'
|
|
695
|
+
+ '★多角色同框的镜头单列装不下,那些走 apply_wardrobe_appearances。', {
|
|
696
|
+
episode_id: z.number().int().positive(),
|
|
697
|
+
items: z.array(z.any()).describe('build_wardrobe_timeline 给的建议,只放客户勾选的那几条'),
|
|
698
|
+
}, async ({ episode_id, items }) => jsonResult(await client.producePost(`/episodes/${episode_id}/wardrobe-timeline/apply`, { items })));
|
|
699
|
+
server.tool('apply_wardrobe_appearances', '逐角色回填 appearances 的服装绑定(第②步的多角色版,免费)。'
|
|
700
|
+
+ '用于**多角色同框**的镜头:单个 active_wardrobe_id 装不下几个人各穿各的,'
|
|
701
|
+
+ '这条按角色逐个绑。同样只传客户勾选的那几条,一次最多 200 条。', {
|
|
702
|
+
episode_id: z.number().int().positive(),
|
|
703
|
+
items: z.array(z.any()).describe('build_wardrobe_timeline 给的建议,只放客户勾选的那几条'),
|
|
704
|
+
}, async ({ episode_id, items }) => jsonResult(await client.producePost(`/episodes/${episode_id}/wardrobe-timeline/apply-appearances`, { items })));
|
|
705
|
+
server.tool('get_world_state', '读本集的**世界状态账本**:被追踪的实体(道具/服装/伤痕一类)在各镜之间的状态,'
|
|
706
|
+
+ '以及对不上的地方(conflicts,带命中的两个镜号)。免费。'
|
|
707
|
+
+ '\n★stale=true = 分镜改过但账本没重建,先 rebuild_world_state 再看,否则看的是旧账。'
|
|
708
|
+
+ '\n★账本目前只是**报表**:不拦生成、也不自动注入出图。看到 conflicts 要自己决定'
|
|
709
|
+
+ '改分镜(update_shot)还是加一条手工事件说明(add_world_state_event)。'
|
|
710
|
+
+ '\n逐条事件的文字详情在官网面板看,这里给结构化概览。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/world-state`)));
|
|
711
|
+
server.tool('rebuild_world_state', '重建世界状态账本(**要花钱**:走 LLM 从分镜里抽取实体与状态变化)。'
|
|
712
|
+
+ '\n改完分镜、或 get_world_state 回 stale=true 时才需要重建;没改过就别重复跑。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/world-state/rebuild`, {})));
|
|
713
|
+
server.tool('add_world_state_event', '往账本里加一条**手工事件**(免费)。用途:AI 没抽到、或抽错了的状态变化,由你补一条说明。'
|
|
714
|
+
+ '\n典型场景:conflicts 报「同一件道具在两镜之间变了」,但剧情里确实发生过交接/损坏——'
|
|
715
|
+
+ '补一条手工事件,冲突即被解释掉,不必去改分镜。', {
|
|
716
|
+
episode_id: z.number().int().positive(),
|
|
717
|
+
entity_key: z.string().describe('实体标识(与账本里的 entity_key 对齐)'),
|
|
718
|
+
shot_number: z.number().int().positive().optional().describe('这条事件发生在第几镜'),
|
|
719
|
+
description: z.string().optional().describe('发生了什么(给人看的说明)'),
|
|
720
|
+
}, async ({ episode_id, ...body }) => jsonResult(await client.producePost(`/episodes/${episode_id}/world-state/manual`, body)));
|
|
721
|
+
server.tool('delete_world_state_event', '删掉一条手工事件(免费)。只能删手工加的,AI 抽取出来的要靠 rebuild_world_state 重算。', {
|
|
722
|
+
episode_id: z.number().int().positive(),
|
|
723
|
+
event_id: z.number().int().positive(),
|
|
724
|
+
}, async ({ episode_id, event_id }) => jsonResult(await client.produceDelete(`/episodes/${episode_id}/world-state/manual/${event_id}`)));
|
|
725
|
+
server.tool('link_world_state_scene_variant', '把账本里的某条事件关联到一个**场景变体**(免费)。'
|
|
726
|
+
+ '用途:同一场景在事件前后长得不一样(灯亮/灯灭、完好/破损),关联之后出图会取对应那版场景图。', {
|
|
727
|
+
episode_id: z.number().int().positive(),
|
|
728
|
+
event_id: z.number().int().positive(),
|
|
729
|
+
variant_id: z.number().int().positive().nullable().describe('传 null 解除关联'),
|
|
730
|
+
}, async ({ episode_id, event_id, variant_id }) => jsonResult(await client.producePut(`/episodes/${episode_id}/world-state/events/${event_id}/scene-variant`, { variant_id })));
|
|
731
|
+
server.tool('derive_pose_chain', '派生**姿态链**(Action Graph):从相邻镜的动作推出每镜的起止姿态,让镜间衔接不跳。'
|
|
732
|
+
+ '**要花钱**(走 LLM)。'
|
|
733
|
+
+ '\n★只填**空着**的那些镜,已经写了姿态的不动;黑场镜自动跳过。'
|
|
734
|
+
+ '\n出图前做,能减少「上一镜站着下一镜突然坐着」这类接不上的情况。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/derive-pose-chain`, {})));
|
|
735
|
+
server.tool('quote_extend_final', '★成片续写**报价**(免费·无副作用):在已有成片后面再接几轮 AI 生成的片段,把片子拉长。'
|
|
736
|
+
+ '回 rounds(轮数)/duration_per_round(每轮秒数)/max_rounds(上限)/estimated_points(预估点数)。'
|
|
737
|
+
+ '\n★**必须先报价、把点数讲给客户、客户确认后再调 extend_final**——每轮都真扣厂商的钱。'
|
|
738
|
+
+ '\n★回 403 = 本剧没开这个能力(要求剧集视频引擎是 2.5 系)。这不是调用错误,别重试;'
|
|
739
|
+
+ '告诉客户换引擎或走别的加长方式。', {
|
|
740
|
+
episode_id: z.number().int().positive(),
|
|
741
|
+
rounds: z.number().int().positive().describe('续写几轮'),
|
|
742
|
+
duration: z.number().positive().describe('每轮秒数'),
|
|
743
|
+
model: z.string().optional().describe('临时覆盖视频模型;不传用剧集设置'),
|
|
744
|
+
}, async ({ episode_id, rounds, duration, model }) => {
|
|
745
|
+
const qs = new URLSearchParams({ rounds: String(rounds), duration: String(duration) });
|
|
746
|
+
if (model)
|
|
747
|
+
qs.set('model', model);
|
|
748
|
+
return jsonResult(await client.produceGet(`/episodes/${episode_id}/extend-final/quote?${qs.toString()}`));
|
|
749
|
+
});
|
|
750
|
+
server.tool('extend_final', '发起成片续写(**收费**,每轮走视频生成扣费链)。后台串行跑,立即返回会话 id,用 get_extend_final 轮询。'
|
|
751
|
+
+ '\n★调之前**必须**先 quote_extend_final 报价并让客户确认点数——不要替客户决定花钱。'
|
|
752
|
+
+ '\n★续写只是「在成片后面接片段」,不改前面已有的内容;接出来的片段也**不会**自动成为本集成片,'
|
|
753
|
+
+ '满意之后要显式调 apply_extend_final 才生效。'
|
|
754
|
+
+ '\n★403 = 本剧没开(引擎需 2.5 系),别重试。', {
|
|
755
|
+
episode_id: z.number().int().positive(),
|
|
756
|
+
rounds: z.number().int().positive(),
|
|
757
|
+
duration: z.number().positive(),
|
|
758
|
+
model: z.string().optional(),
|
|
759
|
+
}, async ({ episode_id, ...body }) => jsonResult(await client.producePost(`/episodes/${episode_id}/extend-final`, body)));
|
|
760
|
+
server.tool('get_extend_final', '查本集所有续写会话的进度与产物。免费。'
|
|
761
|
+
+ '每条会话回 status(pending/processing/completed/partial/failed)、rounds_completed、'
|
|
762
|
+
+ 'final_url(产物直链)、applied(该产物**当前是不是**本集成片)、error_msg。'
|
|
763
|
+
+ '\n★status=processing 时耐心轮询,别重复发起 extend_final(那会再扣一次钱)。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/extend-final`)));
|
|
764
|
+
server.tool('apply_extend_final', '把某个续写产物**设为本集成片**(免费,纯指针切换)。'
|
|
765
|
+
+ '\n★这一步会**覆盖**本集当前的成片链接:之前的成片不再是对外那一版。'
|
|
766
|
+
+ '先用 get_extend_final 看 final_url、确认客户满意再调。'
|
|
767
|
+
+ '\n★之后若重新 compose_episode(终拼),成片会被终拼结果覆盖回去,applied 自动变 false。', {
|
|
768
|
+
episode_id: z.number().int().positive(),
|
|
769
|
+
session_id: z.number().int().positive().describe('取自 get_extend_final 返回的 sessions[].id'),
|
|
770
|
+
}, async ({ episode_id, session_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/extend-final/${session_id}/apply`, {})));
|
|
771
|
+
server.tool('export_sheet_compare', '导出**设定图对照版**(角色设定图与成片画面的对照拼版,营销/交付用)。触发后台合成,用 get_sheet_compare 轮询。'
|
|
772
|
+
+ '\n★这个能力**默认关**:它是剧集级开关(不是所有片子都允许这样对外输出),'
|
|
773
|
+
+ '关着时回 **403**。看到 403 别重试、也别当成失败——告诉客户去官网的**剧集设置**里打开它。'
|
|
774
|
+
+ '\n不传 character_id 就导出整集;传了只导这一个角色。', {
|
|
775
|
+
episode_id: z.number().int().positive(),
|
|
776
|
+
character_id: z.number().int().positive().optional().describe('只导这一个角色;不传=整集'),
|
|
777
|
+
}, async ({ episode_id, character_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/export/sheet-compare`, character_id ? { character_id } : {})));
|
|
778
|
+
server.tool('get_sheet_compare', '轮询设定图对照版的导出状态。免费。回 enabled(剧集开关是否打开)/processing/result/error。'
|
|
779
|
+
+ '\n★enabled=false 就是那个剧集开关没开——不是出错,去官网剧集设置里打开再导。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/export/sheet-compare`)));
|
|
632
780
|
// ========== 项目设定(建后可改)==========
|
|
633
781
|
server.tool('update_project_settings', '建剧后修改项目设定。除画幅/分辨率/世界观Brief/族裔/题材/导演风格外,现覆盖★整剧视觉一致性锚' +
|
|
634
782
|
'(摄影DNA cinematography_prompt/美术圣经 art_bible/视觉锁定 visual_lock/视频风格正负向词/视觉母题 motifs)、' +
|