minimal-agent 0.1.8 → 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 (51) hide show
  1. package/README.md +405 -122
  2. package/dist/main.js +423 -941
  3. package/package.json +5 -2
  4. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +186 -0
  5. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +9 -0
  6. package/plugins/ralph-wiggum/README.md +179 -0
  7. package/plugins/ralph-wiggum/commands/cancel-ralph.md +18 -0
  8. package/plugins/ralph-wiggum/commands/help.md +126 -0
  9. package/plugins/ralph-wiggum/commands/ralph-loop.md +59 -0
  10. package/plugins/ralph-wiggum/hooks/hooks.json +15 -0
  11. package/plugins/ralph-wiggum/hooks/stop-hook.sh +191 -0
  12. package/plugins/ralph-wiggum/plugin.ts +275 -0
  13. package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +203 -0
  14. package/plugins/ralph-wiggum/src/goalState.ts +310 -0
  15. package/plugins/ralph-wiggum/src/sentinels.ts +24 -0
  16. package/plugins/ralph-wiggum/src/stopHookRunner.ts +136 -0
  17. package/plugins/ralph-wiggum/src/verificationGate.ts +252 -0
  18. package/plugins/ralph-wiggum/test/goalState.test.ts +410 -0
  19. package/plugins/ralph-wiggum/test/verificationGate.test.ts +122 -0
  20. package/plugins/workflow-runner/.claude-plugin/plugin.json +5 -0
  21. package/plugins/workflow-runner/commands/workflow.md +15 -0
  22. package/plugins/workflow-runner/commands/workflows.md +8 -0
  23. package/plugins/workflow-runner/plugin.ts +42 -0
  24. package/plugins/workflow-runner/src/expressions.ts +371 -0
  25. package/plugins/workflow-runner/src/index.ts +194 -0
  26. package/plugins/workflow-runner/src/loader.ts +193 -0
  27. package/plugins/workflow-runner/src/runner.ts +313 -0
  28. package/plugins/workflow-runner/src/stepExecutors/assert.ts +30 -0
  29. package/plugins/workflow-runner/src/stepExecutors/llm.ts +54 -0
  30. package/plugins/workflow-runner/src/stepExecutors/skill.ts +115 -0
  31. package/plugins/workflow-runner/src/stepExecutors/tool.ts +41 -0
  32. package/plugins/workflow-runner/src/types.ts +183 -0
  33. package/plugins/workflow-runner/src/workflowState.ts +65 -0
  34. package/plugins/workflow-runner/test/cli.e2e.test.ts +114 -0
  35. package/plugins/workflow-runner/test/e2e.test.ts +268 -0
  36. package/plugins/workflow-runner/test/expressions.test.ts +140 -0
  37. package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +27 -0
  38. package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +49 -0
  39. package/plugins/workflow-runner/test/graceful.test.ts +139 -0
  40. package/plugins/workflow-runner/test/loader.test.ts +216 -0
  41. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +230 -0
  42. package/plugins/workflow-runner/test/runner.test.ts +511 -0
  43. package/skills/config/SKILL.md +27 -1
  44. package/skills/image-gen-openrouter/SKILL.md +121 -0
  45. package/skills/subtitle-srt/SKILL.md +134 -0
  46. package/skills/tts-zh/SKILL.md +137 -0
  47. package/skills/video-compose/SKILL.md +139 -0
  48. package/workflows/book-review-short.yaml +99 -0
  49. package/workflows/e2e-write-greet.yaml +27 -0
  50. package/workflows/schema.json +74 -0
  51. package/workflows/youtube-shorts.yaml +171 -0
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: subtitle-srt
3
+ description: 当用户说"生成字幕"、"做 SRT"、"字幕文件"、"subtitle-srt",或在 workflow 里要把分段文本 + 时长信息转成标准 SRT 字幕文件时使用。给定 cue 列表(每条带文本和起止时间或时长),生成符合 SRT 规范的字幕文件。
4
+ ---
5
+
6
+ # subtitle-srt —— SRT 字幕生成
7
+
8
+ > 通用 skill,不绑定语种 / 不绑定视频比例 / 不绑定字体(字体是视频合成阶段的事)。
9
+
10
+ ## 输入约定
11
+
12
+ 支持两种输入形态:
13
+
14
+ ### 形态 A:cues_json(一次性给所有 cue)
15
+
16
+ ```
17
+ cues_json=<JSON 数组> output=./subs.srt [encoding=utf-8]
18
+ ```
19
+
20
+ `cues_json` 是合法 JSON 数组,每个元素至少有 `text`,时间可以两种方式给:
21
+
22
+ ```json
23
+ [
24
+ {"text": "开场白", "start": 0.0, "end": 3.5},
25
+ {"text": "第二幕", "start": 3.5, "end": 7.7}
26
+ ]
27
+ ```
28
+
29
+ 或者只给时长,由本 skill 自动累加:
30
+
31
+ ```json
32
+ [
33
+ {"text": "开场白", "duration": 3.5},
34
+ {"text": "第二幕", "duration": 4.2}
35
+ ]
36
+ ```
37
+
38
+ ### 形态 B:texts + durations(两个并列数组)
39
+
40
+ ```
41
+ texts=<JSON 字符串数组> durations=<逗号分隔秒数> output=./subs.srt
42
+ ```
43
+
44
+ 例:
45
+ ```
46
+ texts=["开场白","第二幕","第三幕"] durations=3.5,4.2,3.0 output=./subs.srt
47
+ ```
48
+
49
+ > 形态 B 比形态 A 易写,适合 workflow 里用 capture 拼出的简单场景。
50
+
51
+ ## 标准执行流程
52
+
53
+ ### 1. 解析输入
54
+
55
+ 判断是哪种形态:含 `cues_json=` 字段走 A,含 `texts=` + `durations=` 走 B。两者都不满足 → 报错。
56
+
57
+ ### 2. 计算每条 cue 的起止时间戳
58
+
59
+ 如果给了 `start` + `end`(形态 A 第一种),直接用。
60
+
61
+ 否则按 `duration` 累加:
62
+ - cue 1: start=0.0, end=duration[0]
63
+ - cue 2: start=end[0], end=start[1]+duration[1]
64
+ - ...
65
+
66
+ ### 3. 时间戳格式转换
67
+
68
+ SRT 时间戳格式:`HH:MM:SS,mmm`(毫秒前**用逗号不用点**)。
69
+
70
+ 公式:
71
+ ```
72
+ total_ms = round(seconds * 1000)
73
+ hh = total_ms // 3600000
74
+ mm = (total_ms % 3600000) // 60000
75
+ ss = (total_ms % 60000) // 1000
76
+ ms = total_ms % 1000
77
+ formatted = f"{hh:02d}:{mm:02d}:{ss:02d},{ms:03d}"
78
+ ```
79
+
80
+ ### 4. 拼 SRT 内容
81
+
82
+ 每条 cue 4 行(序号 / 时间戳 / 文本 / 空行):
83
+
84
+ ```
85
+ 1
86
+ 00:00:00,000 --> 00:00:03,500
87
+ 开场白
88
+
89
+ 2
90
+ 00:00:03,500 --> 00:00:07,700
91
+ 第二幕
92
+
93
+ ```
94
+
95
+ > ⚠️ 最后一条 cue 末尾**也要有空行**,否则部分播放器会吞掉最后一句。
96
+
97
+ ### 5. 写入文件
98
+
99
+ 用 Write 工具:
100
+ - `file_path = OUTPUT`
101
+ - `content = <上面拼好的 SRT 字符串>`
102
+
103
+ 或者用 Bash + heredoc(注意编码,Windows 上要保证 UTF-8 无 BOM):
104
+ ```bash
105
+ mkdir -p "$(dirname "$OUTPUT")"
106
+ cat > "$OUTPUT" <<'EOF'
107
+ 1
108
+ 00:00:00,000 --> 00:00:03,500
109
+ 开场白
110
+
111
+ EOF
112
+ ```
113
+
114
+ ### 6. 自检(可选)
115
+
116
+ 如果机器有 ffprobe,跑一下确认字幕可读:
117
+ ```bash
118
+ ffprobe -v error -loglevel error -i "$OUTPUT" 2>&1 || echo "ffprobe 验证未通过:$?"
119
+ ```
120
+
121
+ ffprobe 失败不算硬错(部分版本对 SRT 解析比较宽松),但要打印 warning。
122
+
123
+ ### 7. 收尾输出
124
+
125
+ ```
126
+ saved: <OUTPUT> (cues=<N>, duration=<TOTAL_SEC>s)
127
+ ```
128
+
129
+ ## 注意事项
130
+
131
+ - 长文本自动换行:单 cue 文本超过 ~30 字时,在自然标点处插入换行(`,` / `。` / `?` / `!`)。SRT 单条多行用真实换行符即可
132
+ - **不打印用户文本内容到日志**(隐私)
133
+ - 字符编码强制 UTF-8 无 BOM;Windows PowerShell 写文件时显式 `-Encoding utf8`
134
+ - 不假设语种 —— 中英日韩都按字符长度判断换行(中文 1 字符 ≈ 英文 2 字符的视觉宽度,可按 30 字符切)
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: tts-zh
3
+ description: 当用户说"中文配音"、"中文 TTS"、"语音合成"、"文字转语音"、"tts-zh",或在 workflow 里要把中文文本转成 mp3 时使用。优先用 MiniMax T2A API,失败时 fallback 到 edge-tts 离线方案,输出 mp3 到指定路径。
4
+ ---
5
+
6
+ # tts-zh —— 中文 TTS(minimax 主 + edge-tts 兜底)
7
+
8
+ ## 输入约定
9
+
10
+ 调用方传一行 `key=value` 字段串:
11
+
12
+ ```
13
+ text=今天天气真好 output=./assets/audio_0.mp3 voice=female-shaonv speed=1.0
14
+ ```
15
+
16
+ 字段:
17
+ - **text**(必填):要合成的中文文本(短句,≤200 字)
18
+ - **output**(必填):目标 mp3 路径
19
+ - **voice**(可选,默认 `female-shaonv`):minimax 嗓音 id;edge-tts 走 `zh-CN-XiaoxiaoNeural` 不受 voice 字段影响
20
+ - **speed**(可选,默认 `1.0`):语速 0.5–2.0
21
+
22
+ ## 标准执行流程
23
+
24
+ ### 1. 解析输入
25
+
26
+ 仔细取 text / output / voice / speed。text 可能包含空格甚至标点,按"剩余串"策略提取。
27
+
28
+ ### 2. 优先尝试 MiniMax T2A
29
+
30
+ #### 2.1 取 key + group id
31
+
32
+ ```bash
33
+ KEY="$MINIMAX_API_KEY"
34
+ GROUP="$MINIMAX_GROUP_ID"
35
+ ```
36
+
37
+ 如果两个都不空,进入 2.2;任一为空,**直接跳到第 3 步(edge-tts fallback)**,不算错误。
38
+
39
+ #### 2.2 调 API
40
+
41
+ minimax T2A v2 接口:
42
+
43
+ ```bash
44
+ jq -n --arg t "$TEXT" --arg v "$VOICE" --argjson s "$SPEED" \
45
+ '{model:"speech-01", text:$t, voice_setting:{voice_id:$v, speed:$s}, audio_setting:{format:"mp3"}}' \
46
+ | curl -sS "https://api.minimax.chat/v1/t2a_v2?GroupId=$GROUP" \
47
+ -H "Authorization: Bearer $KEY" \
48
+ -H "Content-Type: application/json" \
49
+ --data-binary @- \
50
+ -o /tmp/minimax_resp.json
51
+ ```
52
+
53
+ > ⚠️ MINIMAX_BASE_URL 可能不是 `api.minimax.chat`(用户也许配了自定义网关)。如果 env 里有 `MINIMAX_BASE_URL`,用它替换 `https://api.minimax.chat`,否则用默认。
54
+
55
+ #### 2.3 解析响应
56
+
57
+ minimax 返回里音频是 hex 字符串:
58
+ ```
59
+ .data.audio → "ff f3 e4 ..."(hex 编码的 mp3 字节流)
60
+ ```
61
+
62
+ 解码到文件:
63
+ ```bash
64
+ mkdir -p "$(dirname "$OUTPUT")"
65
+ jq -r '.data.audio' /tmp/minimax_resp.json | xxd -r -p > "$OUTPUT"
66
+ ```
67
+
68
+ > 如果 `xxd` 不可用(部分 Windows),用 PowerShell:
69
+ > ```powershell
70
+ > $resp = Get-Content /tmp/minimax_resp.json -Raw | ConvertFrom-Json
71
+ > $hex = $resp.data.audio
72
+ > $bytes = New-Object byte[] ($hex.Length / 2)
73
+ > for ($i = 0; $i -lt $bytes.Length; $i++) { $bytes[$i] = [Convert]::ToByte($hex.Substring($i*2, 2), 16) }
74
+ > [IO.File]::WriteAllBytes('<OUTPUT>', $bytes)
75
+ > ```
76
+
77
+ #### 2.4 校验
78
+
79
+ ```bash
80
+ SIZE=$(stat -c%s "$OUTPUT" 2>/dev/null || stat -f%z "$OUTPUT")
81
+ ```
82
+
83
+ - `SIZE > 1000` → 成功,输出 `saved: <OUTPUT> (provider=minimax, size=<SIZE>)`,结束
84
+ - `SIZE < 1000` 或文件不存在 → 视为失败,**进入第 3 步 edge-tts fallback**
85
+
86
+ ### 3. Fallback 到 edge-tts
87
+
88
+ #### 3.1 确认 edge-tts 可用
89
+
90
+ ```bash
91
+ pip show edge-tts >/dev/null 2>&1 || pip install --user edge-tts
92
+ which edge-tts || python -m edge_tts --help >/dev/null 2>&1 || true
93
+ ```
94
+
95
+ 如果 pip / python 都没有,**报错退出**:"tts-zh 失败:minimax 不可用且本机无 Python,无法 fallback 到 edge-tts。请配置 MINIMAX_API_KEY + MINIMAX_GROUP_ID 或安装 Python + edge-tts"
96
+
97
+ #### 3.2 跑 edge-tts
98
+
99
+ ```bash
100
+ edge-tts --voice zh-CN-XiaoxiaoNeural --rate "+0%" --text "$TEXT" --write-media "$OUTPUT"
101
+ # 或:
102
+ python -m edge_tts --voice zh-CN-XiaoxiaoNeural --text "$TEXT" --write-media "$OUTPUT"
103
+ ```
104
+
105
+ > 语速换算:minimax 的 speed=1.0 ↔ edge-tts 的 `--rate "+0%"`;speed=1.2 ↔ `--rate "+20%"`;speed=0.8 ↔ `--rate "-20%"`。
106
+ > 公式:`rate = ((speed - 1.0) * 100)` 取整,前面加 `+` 或 `-`。
107
+
108
+ #### 3.3 校验
109
+
110
+ 同 2.4 步骤;size > 1000 → 成功,输出 `saved: <OUTPUT> (provider=edge-tts, size=<SIZE>)`。
111
+
112
+ 仍然失败 → 报错 "tts-zh 失败:minimax 和 edge-tts 都不工作"
113
+
114
+ ## 嗓音速查
115
+
116
+ ### MiniMax 常用 voice_id
117
+
118
+ | voice_id | 描述 |
119
+ |---|---|
120
+ | `female-shaonv` | 少女音(默认,自然清亮) |
121
+ | `male-qn-jingying` | 精英男声(沉稳) |
122
+ | `female-tianmei` | 甜美女声 |
123
+ | `audiobook_male_1` | 有声书男声 |
124
+
125
+ ### edge-tts(兜底,固定)
126
+
127
+ | voice | 描述 |
128
+ |---|---|
129
+ | `zh-CN-XiaoxiaoNeural` | 默认女声(自然) |
130
+ | `zh-CN-YunxiNeural` | 男声 |
131
+
132
+ ## 注意事项
133
+
134
+ - **不打印 API key / GroupId 到任何日志**
135
+ - text 含特殊 shell 字符(`$ " '` 等)时务必走 jq 拼 JSON,**不要直接拼字符串到 curl 命令里**
136
+ - 输出 mp3 时长会因为文本长度而变 —— 不要硬假设时长,下游 ffprobe 实际测量
137
+ - 一次只合成一句;若调用方传超过 200 字,让它分多次调用
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: video-compose
3
+ description: 当用户说"合成视频"、"做 mp4"、"图片转视频"、"ffmpeg 拼视频"、"video-compose",或在 workflow 里要把图片序列 + 可选音轨 + 可选字幕合成成 mp4 时使用。本 skill 是 ffmpeg 的 declarative 封装,所有参数(分辨率、字体、码率、转场)都可配置,不绑定特定视频格式。
4
+ ---
5
+
6
+ # video-compose —— 通用 ffmpeg 视频合成
7
+
8
+ > 通用 skill,可被任何 workflow 调用。无场景假设(不绑定竖屏/横屏,不绑定语言)。
9
+
10
+ ## 输入约定
11
+
12
+ 调用方传一行 `key=value` 字段串,必填项 + 可选项混合:
13
+
14
+ ```
15
+ images_glob=./assets/img_*.png audio=./assets/audio.mp3 srt=./assets/subs.srt output=./out/video.mp4 width=1080 height=1920 per_image_duration=3 fps=30 font=Microsoft YaHei font_size=42
16
+ ```
17
+
18
+ ### 必填
19
+
20
+ - **images_glob**:图片 glob 表达式(用 Glob 工具枚举,按文件名排序)
21
+ - **output**:目标 mp4 路径
22
+
23
+ ### 可选
24
+
25
+ | 字段 | 默认 | 说明 |
26
+ |---|---|---|
27
+ | **audio** | (无) | 音轨 mp3/wav;不传则纯视频无声 |
28
+ | **srt** | (无) | 字幕文件;不传则不烧入字幕 |
29
+ | **width** × **height** | `1080` × `1920` | 输出分辨率(默认竖屏;横屏传 `1920` × `1080`) |
30
+ | **per_image_duration** | `3` | 每张图片显示秒数(如果给 audio,可能会被覆盖 —— 见下方"对齐音轨") |
31
+ | **fps** | `30` | 帧率 |
32
+ | **font** | `Microsoft YaHei`(Win)/ `PingFang SC`(Mac)/ `Noto Sans CJK SC`(Linux) | 字幕字体 |
33
+ | **font_size** | `42` | 字幕字号 |
34
+ | **font_color** | `FFFFFF` | 字幕颜色(RGB hex) |
35
+ | **align_audio** | `auto` | `auto`=按音轨长度等分图片时长 / `fixed`=固定 `per_image_duration` / `cut`=超出部分截断 |
36
+ | **crf** | `23` | x264 质量(18–28,越低越清) |
37
+
38
+ ## 标准执行流程
39
+
40
+ ### 1. 前置检查
41
+
42
+ ```bash
43
+ which ffmpeg >/dev/null || { echo "缺少 ffmpeg,请安装:winget install Gyan.FFmpeg / brew install ffmpeg / apt install ffmpeg"; exit 1; }
44
+ which ffprobe >/dev/null || { echo "缺少 ffprobe(通常和 ffmpeg 一起装)"; exit 1; }
45
+ ```
46
+
47
+ ### 2. 收集图片列表
48
+
49
+ 用 Glob 工具按 `images_glob` 找文件 → 按文件名 ASCII 排序。如果只有 0 或 1 张图,**报错退出**(视频至少要 2 帧才有意义)。
50
+
51
+ ### 3. 决定每张图时长
52
+
53
+ - 如果**有 audio** 且 `align_audio=auto`(默认):
54
+ - 用 `ffprobe -v error -show_entries format=duration -of csv=p=0 <audio>` 拿音轨时长 `T`
55
+ - 单图时长 = `T / 图片数`
56
+ - 否则:每张图固定 `per_image_duration` 秒
57
+
58
+ ### 4. 生成 concat 列表
59
+
60
+ ffmpeg concat demuxer 用法:
61
+
62
+ ```bash
63
+ mkdir -p "$(dirname "$OUTPUT")"
64
+ LIST=/tmp/video_compose_list.txt
65
+ > "$LIST"
66
+ for f in $(ls $IMAGES_GLOB | sort); do
67
+ echo "file '$f'" >> "$LIST"
68
+ echo "duration $PER_IMG" >> "$LIST"
69
+ done
70
+ # ffmpeg quirk:最后一张图片要再列一次(不带 duration)才会被显示完整时长
71
+ LAST=$(ls $IMAGES_GLOB | sort | tail -n1)
72
+ echo "file '$LAST'" >> "$LIST"
73
+ ```
74
+
75
+ ### 5. 合成视频流(可选烧字幕)
76
+
77
+ 无字幕版本:
78
+ ```bash
79
+ ffmpeg -y -f concat -safe 0 -i "$LIST" \
80
+ -vf "scale=${WIDTH}:${HEIGHT}:force_original_aspect_ratio=increase,crop=${WIDTH}:${HEIGHT},fps=${FPS}" \
81
+ -c:v libx264 -crf ${CRF} -pix_fmt yuv420p /tmp/video_only.mp4
82
+ ```
83
+
84
+ 带字幕版本(filter 链尾加 subtitles 滤镜):
85
+ ```bash
86
+ ffmpeg -y -f concat -safe 0 -i "$LIST" \
87
+ -vf "scale=${WIDTH}:${HEIGHT}:force_original_aspect_ratio=increase,crop=${WIDTH}:${HEIGHT},fps=${FPS},subtitles=${SRT}:force_style='Fontname=${FONT},Fontsize=${FONT_SIZE},PrimaryColour=&H00${FONT_COLOR}&,Outline=2,BorderStyle=1'" \
88
+ -c:v libx264 -crf ${CRF} -pix_fmt yuv420p /tmp/video_only.mp4
89
+ ```
90
+
91
+ > ⚠️ Windows 路径里反斜杠 `\` 会被 ffmpeg subtitles 滤镜当转义字符吃掉。**字幕路径强制用正斜杠 `/`**,或者 `\\:` 转义盘符冒号。
92
+
93
+ ### 6. 合入音轨(如果有)
94
+
95
+ ```bash
96
+ if [ -n "$AUDIO" ]; then
97
+ ffmpeg -y -i /tmp/video_only.mp4 -i "$AUDIO" \
98
+ -c:v copy -c:a aac -b:a 192k \
99
+ -shortest "$OUTPUT"
100
+ else
101
+ cp /tmp/video_only.mp4 "$OUTPUT"
102
+ fi
103
+ ```
104
+
105
+ ### 7. 校验产物
106
+
107
+ ```bash
108
+ ffprobe -v error -show_entries stream=codec_type,duration -of default=noprint_wrappers=1 "$OUTPUT"
109
+ SIZE=$(stat -c%s "$OUTPUT" 2>/dev/null || stat -f%z "$OUTPUT")
110
+ ```
111
+
112
+ - 文件存在且 size > 100KB
113
+ - 至少有 1 个 video 流;如果传了 audio 还应该有 1 个 audio 流
114
+ - 总时长 > 1 秒
115
+
116
+ 任一项不满足 → 报错并打印 ffprobe 输出供 debug。
117
+
118
+ ### 8. 收尾输出
119
+
120
+ ```
121
+ saved: <OUTPUT> (resolution=<WIDTH>x<HEIGHT>, duration=<DUR>s, size=<MB>MB)
122
+ ```
123
+
124
+ ## 字体路径速查
125
+
126
+ | OS | 字体名(filter 用) | 实际文件 |
127
+ |---|---|---|
128
+ | Windows | `Microsoft YaHei` 或 `SimHei` | `C:\Windows\Fonts\msyh.ttc` |
129
+ | macOS | `PingFang SC` | `/System/Library/Fonts/PingFang.ttc` |
130
+ | Linux | `Noto Sans CJK SC` | `/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc` |
131
+
132
+ 如果 force_style 找不到字体,会 fallback 到 Arial(中文会变方块),用 `fc-list :lang=zh-cn` 看本机有哪些中文字体。
133
+
134
+ ## 注意事项
135
+
136
+ - **ffmpeg 滤镜里的引号嵌套很难写**:双引号包围整个 `-vf` 值,里面用单引号;force_style 的 key=value 用 `=` 不要用空格
137
+ - subtitles 滤镜路径必须**用正斜杠**,盘符冒号转义成 `\\:`(Win 上 `C:/path/x.srt` → `C\\:/path/x.srt`)
138
+ - 不接受**纯静态图片**作输入 —— 至少 2 张才合理;如果调用方只有 1 张图,让它用 ffmpeg `-loop 1` 单独处理(不走本 skill)
139
+ - 不假设视频用途(短视频 / 长视频 / 横屏 / 竖屏 / 方形)—— 全部走参数
@@ -0,0 +1,99 @@
1
+ name: book-review-short
2
+ description: |
3
+ 从书名生成 YouTube Shorts 书评短视频(60 秒):
4
+ 调研 → 60 秒口播脚本 → 缩略图 prompt → meta.json。
5
+ P0 跑通 steps 1/2/3/5/6/7(step 4 loop/钩子标题 P1 启用)。
6
+ version: "0.1"
7
+
8
+ inputs:
9
+ - name: book
10
+ type: string
11
+ required: true
12
+ description: "书名(中英都行)"
13
+ - name: angle
14
+ type: enum
15
+ values: [insight, contrarian, beginner]
16
+ default: insight
17
+ - name: lang
18
+ type: enum
19
+ values: [zh, en]
20
+ default: zh
21
+
22
+ steps:
23
+ # 1. 调研(确定性工具)
24
+ - id: research
25
+ tool: WebSearch
26
+ args:
27
+ query: "${inputs.book} 核心观点 书评 ${inputs.lang}"
28
+ capture:
29
+ content: research_data
30
+
31
+ # 2. 60 秒短视频脚本(llm 单轮,不开工具)
32
+ - id: script
33
+ llm: |
34
+ 你是一名 YouTube Shorts 创作者。基于以下调研,为书 "${inputs.book}" 写一段
35
+ 60 秒口播脚本,视角是 ${inputs.angle}(insight=洞察、contrarian=逆向、beginner=入门)。
36
+ 语言:${inputs.lang}。
37
+ 要求:
38
+ - 开头 3 秒强钩子(一个反直觉论断 / 数字 / 提问)
39
+ - 中段 40 秒展开 2-3 个核心要点,每点配一句金句
40
+ - 结尾 10 秒行动号召
41
+ - 全文 ≤ 180 字(中文)/ 150 词(英文)
42
+ 只输出脚本本体,不要注释/标题/分段标记。
43
+
44
+ 调研材料:
45
+ ${research_data}
46
+ capture:
47
+ text: script_text
48
+
49
+ # 3. 写脚本到文件(确定性工具)
50
+ - id: write_script
51
+ tool: Write
52
+ args:
53
+ file_path: "videos/${inputs.book}/script.txt"
54
+ content: "${script_text}"
55
+
56
+ # 4. 生成 3 个钩子标题(loop + llm)—— P1 启用
57
+ # - id: gen_hooks
58
+ # type: loop
59
+ # over: "[1, 2, 3]"
60
+ # as: idx
61
+ # do:
62
+ # - id: hook_${idx}
63
+ # llm: |
64
+ # 为 "${inputs.book}" 书评 Shorts 生成第 ${idx} 个标题(≤20 字 / ≤8 words)。
65
+ # 风格序号 1=金句、2=反问、3=数字承诺。
66
+ # 只输出标题本体。
67
+ # capture:
68
+ # text: hook_${idx}
69
+
70
+ # 5. 缩略图 prompt(llm 单轮)
71
+ - id: thumbnail
72
+ llm: |
73
+ 为书 "${inputs.book}" 的 YouTube Shorts 缩略图生成英文文生图 prompt:
74
+ - 主体:书 + 一个隐喻物
75
+ - 色调:高饱和对比
76
+ - 构图:竖屏 9:16,主体居中
77
+ - 风格:editorial, photorealistic
78
+ ≤100 词,单段。
79
+ capture:
80
+ text: thumb_prompt
81
+
82
+ # 6. 元数据汇总(确定性工具)
83
+ - id: meta
84
+ tool: Write
85
+ args:
86
+ file_path: "videos/${inputs.book}/meta.json"
87
+ content: |
88
+ {
89
+ "book": "${inputs.book}",
90
+ "angle": "${inputs.angle}",
91
+ "lang": "${inputs.lang}",
92
+ "thumbnail_prompt": "${thumb_prompt}"
93
+ }
94
+
95
+ # 7. 守门 assert
96
+ - id: verify
97
+ type: assert
98
+ condition: 'fileExists("videos/${inputs.book}/script.txt") && fileExists("videos/${inputs.book}/meta.json")'
99
+ onFail: "脚本或 meta 未写盘,请检查 Write 工具"
@@ -0,0 +1,27 @@
1
+ name: e2e-write-greet
2
+ description: E2E 极简 workflow,证明 /workflow CLI 端到端通(无 LLM 无网络)
3
+ version: "0.1"
4
+
5
+ inputs:
6
+ - name: topic
7
+ type: string
8
+ required: true
9
+
10
+ steps:
11
+ - id: write_note
12
+ tool: Write
13
+ args:
14
+ file_path: "out/${inputs.topic}/note.txt"
15
+ content: "topic=${inputs.topic} ok"
16
+ capture:
17
+ args.content: note_body
18
+
19
+ - id: verify_file
20
+ type: assert
21
+ condition: 'fileExists("out/${inputs.topic}/note.txt")'
22
+ onFail: "note.txt 未落盘"
23
+
24
+ - id: verify_capture
25
+ type: assert
26
+ condition: 'length(note_body) > 0'
27
+ onFail: "capture 没绑到 note_body"
@@ -0,0 +1,74 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/billsoft/kakadeai/minimal-agent/workflows/schema.json",
4
+ "title": "minimal-agent workflow",
5
+ "description": "Workflow YAML schema, v0.1 — see docs/workflow-plugin-plan.md",
6
+ "type": "object",
7
+ "required": ["name", "description", "steps"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "name": { "type": "string", "minLength": 1 },
11
+ "description": { "type": "string", "minLength": 1 },
12
+ "version": { "type": "string" },
13
+ "inputs": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["name"],
18
+ "properties": {
19
+ "name": { "type": "string", "minLength": 1 },
20
+ "type": { "enum": ["string", "number", "enum"] },
21
+ "required": { "type": "boolean" },
22
+ "default": {},
23
+ "values": { "type": "array", "items": { "type": "string" } },
24
+ "description": { "type": "string" }
25
+ }
26
+ }
27
+ },
28
+ "steps": {
29
+ "type": "array",
30
+ "minItems": 1,
31
+ "items": { "$ref": "#/definitions/step" }
32
+ },
33
+ "__meta": { "type": "object" }
34
+ },
35
+ "definitions": {
36
+ "step": {
37
+ "type": "object",
38
+ "required": ["id"],
39
+ "properties": {
40
+ "id": { "type": "string", "minLength": 1 },
41
+ "tool": { "type": "string" },
42
+ "skill": { "type": "string" },
43
+ "llm": { "type": "string" },
44
+ "type": { "enum": ["assert", "branch", "loop", "pause"] },
45
+ "args": { "type": "object" },
46
+ "input": { "type": "string" },
47
+ "capture": {
48
+ "type": "object",
49
+ "additionalProperties": { "type": "string" }
50
+ },
51
+ "when": { "type": "string" },
52
+ "onError": { "enum": ["halt", "continue"] },
53
+ "maxTurns": { "type": "number" },
54
+ "condition": { "type": "string" },
55
+ "onFail": { "type": "string" },
56
+ "then": {
57
+ "type": "array",
58
+ "items": { "$ref": "#/definitions/step" }
59
+ },
60
+ "else": {
61
+ "type": "array",
62
+ "items": { "$ref": "#/definitions/step" }
63
+ },
64
+ "over": { "type": "string" },
65
+ "as": { "type": "string" },
66
+ "do": {
67
+ "type": "array",
68
+ "items": { "$ref": "#/definitions/step" }
69
+ },
70
+ "prompt": { "type": "string" }
71
+ }
72
+ }
73
+ }
74
+ }