create-dwy 0.16.1 → 0.17.1

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 (105) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/index.js +23 -19
  3. package/package.json +1 -1
  4. package/src/cli-help.js +44 -0
  5. package/src/sync-all.js +303 -59
  6. package/src/sync-global-skills.js +167 -0
  7. package/src/sync.js +21 -11
  8. package/templates/ai-tools/CHANGELOG.md +134 -0
  9. package/templates/ai-tools/VERSION +1 -1
  10. package/templates/ai-tools/rules/Android/dwy-android-core.md +65 -1
  11. package/templates/ai-tools/rules/Python/dwy-python-core.md +47 -0
  12. package/templates/ai-tools/rules/Vue/dwy-vue-core.md +55 -0
  13. package/templates/ai-tools/rules/iOS/dwy-swift-core.md +46 -0
  14. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +128 -0
  15. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +65 -0
  16. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +43 -0
  17. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/api-workflow.md +17 -0
  18. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +16 -0
  19. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/screenshot-workflow.md +16 -0
  20. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/scripts/appstore_config.sh +140 -0
  21. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +11 -8
  22. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +2 -1
  23. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +2 -1
  24. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +24 -4
  25. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +2 -1
  26. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +2 -1
  27. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +82 -0
  28. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -1
  29. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +3 -2
  30. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/monitor-notify.md +2 -2
  31. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +1 -1
  32. package/templates/ai-tools/skills//345/237/272/347/241/200/345/272/223/dwy-eapi/references/tasks-integration-guide.md +2 -2
  33. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/README.md +57 -0
  34. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +147 -0
  35. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +43 -0
  36. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/config.example.yaml +33 -0
  37. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/global_config.py +89 -0
  38. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/setup_config.py +175 -0
  39. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +339 -0
  40. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +127 -0
  41. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/assets/cover-style-master-v1.png +0 -0
  42. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +37 -0
  43. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-posting-times.md +193 -0
  44. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/SKILL.md +42 -0
  45. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates/topic-backlog.template.md +50 -0
  46. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates//351/200/211/351/242/230/346/261/240.template.md +16 -0
  47. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/SKILL.md +106 -0
  48. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates/channel-profile.template.md +52 -0
  49. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates//344/272/247/345/223/201/345/215/241.template.md +132 -0
  50. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/SKILL.md +60 -0
  51. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/build_facts_html.py +430 -0
  52. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_html.py +1287 -0
  53. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_report.py +189 -0
  54. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/templates//344/272/213/345/256/236/350/265/204/346/226/231/345/272/223.template.md +55 -0
  55. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/SKILL.md +36 -0
  56. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates/README.md +10 -0
  57. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//344/272/213/345/256/236.template.md +82 -0
  58. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//345/215/225/351/233/206/346/226/207/346/241/243.template.md +175 -0
  59. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//350/204/232/346/234/254.template.md +69 -0
  60. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//351/252/250/346/236/266-v1.1.md +174 -0
  61. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0050-design/SKILL.md +55 -0
  62. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0060-assets/SKILL.md +29 -0
  63. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/SKILL.md +42 -0
  64. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/scripts/validate_shots.py +207 -0
  65. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/shots.schema.json +186 -0
  66. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/345/214/205.template.md +1 -0
  67. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/350/257/264/346/230/216.md +15 -0
  68. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//346/210/220/347/211/207/345/214/205.template.md +49 -0
  69. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0080-factory/SKILL.md +58 -0
  70. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/SKILL.md +424 -0
  71. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/evals.md +41 -0
  72. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/CONTRACT.md +123 -0
  73. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/SKILL.md +81 -0
  74. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow//346/211/247/350/241/214/346/270/205/345/215/225.template.md +60 -0
  75. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/SKILL.md +78 -0
  76. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/agents/openai.yaml +4 -0
  77. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/knowledge-town-paper-film-v1.json +80 -0
  78. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/AudioTracks.tsx +20 -0
  79. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CaptionLayer.tsx +18 -0
  80. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CueTimeline.ts +24 -0
  81. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/KnowledgeTownComposition.tsx +21 -0
  82. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/cinematic-direction.md +73 -0
  83. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/sticker-asset-production.md +30 -0
  84. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/style-guide.md +43 -0
  85. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/build_cue_timeline.py +75 -0
  86. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_cue_timeline.py +68 -0
  87. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_style_preset.py +22 -0
  88. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/SKILL.md +0 -511
  89. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-data.md +0 -126
  90. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-network.md +0 -88
  91. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-runtime.md +0 -195
  92. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/security-doc-search.md +0 -179
  93. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_base.sh +0 -39
  94. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_capacity.sh +0 -278
  95. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_db.sh +0 -147
  96. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_docker.sh +0 -230
  97. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_env.sh +0 -64
  98. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_https.sh +0 -60
  99. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_logs.sh +0 -209
  100. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_nginx.sh +0 -100
  101. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_resilience.sh +0 -157
  102. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_secrets.sh +0 -265
  103. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_services.sh +0 -94
  104. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_ssh.sh +0 -56
  105. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/run_all.sh +0 -105
@@ -18,8 +18,9 @@
18
18
  - `github-action` → 引 `../dwy-github-action-publish`(先过通用检查)
19
19
  - `local` → 缓存的本地构建/发布命令
20
20
 
21
- ## changelog / 安全检查 / 监控
21
+ ## changelog / tag / 安全检查 / 监控
22
22
 
23
23
  - changelog → `changelog.md`(写 `harmony/CHANGELOG.md`)
24
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
24
25
  - 安全检查 → `../dwy-sdk-spec`
25
26
  - 监控 + 结果 → `monitor-notify.md`
@@ -21,12 +21,13 @@
21
21
 
22
22
  按缓存 method 分流:
23
23
  - `testflight` → 用项目实际的 TestFlight 上传命令(fastlane lane / Transporter / altool)
24
- - `cocoapods` / `spm` → 用项目实际的 pod 发布 / git tag 命令(SPM 以 tag 为版本,tag 须等于 `CFBundleShortVersionString`)
24
+ - `cocoapods` / `spm` → 用项目实际的 pod 发布命令(SPM 消费 git tag;tag 走统一 `git-tag.md`,版本串 = changelog = `CFBundleShortVersionString`)
25
25
  - `github-action` → 引 `../dwy-github-action-publish`(先过其通用检查)
26
26
  - `local` → 缓存的本地命令
27
27
 
28
- ## changelog / 安全检查 / 监控
28
+ ## changelog / tag / 安全检查 / 监控
29
29
 
30
30
  - changelog → `changelog.md`(写 `ios/CHANGELOG.md`)
31
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
31
32
  - 安全检查 → `../dwy-sdk-spec`(SDK 发布尤其要查泄露)
32
33
  - 监控 + 结果 → `monitor-notify.md`
@@ -30,7 +30,7 @@ verify 失败,发布不算完成。
30
30
 
31
31
  发布终态后在对话里输出结果摘要,不接外部通知:
32
32
 
33
- - **成功**:平台 / 应用目录、版本号、产物、渠道、verify 结果
34
- - **失败**:失败阶段、关键报错、下一步建议(重发 / bump / 修配置)
33
+ - **成功**:平台 / 应用目录、版本号、**git tag 全名**、产物、渠道、verify 结果
34
+ - **失败**:失败阶段、关键报错、下一步建议(重发 / bump / 修配置 / 重打 tag)
35
35
 
36
36
  涉及删除远端 release / tag 的重发,必须先确认用户允许。
@@ -120,7 +120,7 @@ python3 scripts/bump.py 2.0.0-rc.1 major # -> 2.0.0 (预发布转
120
120
  ## 与 dwy-publish 的边界
121
121
 
122
122
  - **dwy-semver**(本 skill):只回答"该升哪一位、新号是多少"
123
- - **dwy-publish**:拿到版本号后执行发版流程(测试 → bump 版本文件 → changelog → build → publish → tag)
123
+ - **dwy-publish**:拿到版本号后执行发版流程(测试 → bump 版本文件 → changelog → 安全检查 → **打 tag(版本串与 changelog 标题一致)** → build → publish)
124
124
 
125
125
  发版时若不确定版本号,先用本 skill 决策,再交给 dwy-publish 落地。
126
126
 
@@ -143,7 +143,7 @@ curl -X POST http://localhost:8000/tasks \
143
143
 
144
144
  ```json
145
145
  {
146
- "code": 200,
146
+ "code": "SUCCESS",
147
147
  "message": "success",
148
148
  "data": {
149
149
  "id": "task_a1b2c3d4...",
@@ -184,7 +184,7 @@ curl "http://localhost:8000/tasks?task_type=process_data"
184
184
  curl -X POST http://localhost:8000/tasks/task_a1b2c3d4.../cancel
185
185
  ```
186
186
 
187
- 仅 `pending` 和 `running` 状态的任务可取消。已结束的任务返回 400。
187
+ 仅 `pending` 和 `running` 状态的任务可取消。已结束的任务抛 `BusinessError`,HTTP **422**(`TASK_ALREADY_FINISHED`)。
188
188
 
189
189
  ### 自定义路由前缀
190
190
 
@@ -0,0 +1,57 @@
1
+ # 自媒体 skills(uc-media)
2
+
3
+ ## 处理流程
4
+
5
+ ```text
6
+ 跨集 channel-profile · topic-backlog · design-defaults?
7
+
8
+ 有主题 ─────────────────────┐
9
+ 无主题 → 0005 ★人选 ────────┤
10
+ ▼
11
+ 【0010 产品卡 · 一步】
12
+ ① 竞品 + 差异化
13
+ ② AI 自动推荐演讲地图
14
+ (讲/不讲 · 内容柱 · 时长)
15
+ ★ 不做则停;可改 §3 地图
16
+ ▼
17
+ 【0030 事实】按内容柱扩展深库
18
+ ▼
19
+ 【0040 脚本】按地图蒸馏口播
20
+ ▼
21
+ 0050→0060→0070→0080→render
22
+ 可选 0090 包装
23
+ ```
24
+
25
+ **因果:** 主题 → **看清赛道并自动生成讲什么** → 为地图备事实 → 蒸馏成片。
26
+
27
+ | 编号 | 决定什么 | 主文档 |
28
+ |------|----------|--------|
29
+ | 0005 | 拍哪个主题(可选) | topic-backlog.md |
30
+ | **0010** | 竞品差在哪 + **AI 推荐讲什么** | `0010-product/产品卡-*.md` |
31
+ | 0030 | 按地图扩事实 | `0030-facts/事实-*.md` |
32
+ | 0040 | 口播分镜 | `0040-script/脚本-*.md` |
33
+ | 0050–0080 | 设计·素材·成片·工厂 | … |
34
+ | 0090 | 多平台包装(可选) | 0070-package/ |
35
+
36
+ | 模式 | 行为 |
37
+ |------|------|
38
+ | **fast** | 0010→0030 连跑后停;0040/0050 停;连 0060–0070;0080 停 |
39
+ | **standard** | 每步确认(含是否改 0010 地图) |
40
+
41
+ ## Skills
42
+
43
+ | Skill | 作用 |
44
+ |-------|------|
45
+ | uc-media-flow | 编排 |
46
+ | uc-media-0005-ideation | 选题发现 |
47
+ | **uc-media-0010-product** | **竞品 + AI 演讲地图(合并)** |
48
+ | uc-media-0030-facts | 按地图扩事实 |
49
+ | uc-media-0040-script … 0080-factory | 脚本与制作 |
50
+ | media-platform-packaging | 0090 可选 |
51
+ | dwy-doubao-tts | 口播配音(豆包声音复刻 2.0,可选) |
52
+
53
+ 契约:[`uc-media-flow/CONTRACT.md`](./uc-media-flow/CONTRACT.md)
54
+
55
+ ## 兼容
56
+
57
+ 旧分步 `0010-topic` + `0020-product` → 单一 `0010-product`;无 0020 目录。
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: dwy-doubao-tts
3
+ description: >
4
+ 豆包配音:用火山引擎豆包声音复刻 2.0(seed-icl-2.0)HTTP Chunked 单向流式接口,
5
+ 为自媒体口播生成 MP3 音频。API Key / 音色 ID 只读用户全局 ~/.dwy/config.yaml,
6
+ 禁止写入 skill 或任何项目目录。触发:豆包配音、口播配音、TTS、语音合成、
7
+ 生成口播音频、/doubao-tts、/dwy-doubao-tts。
8
+ ---
9
+
10
+ # 豆包配音(dwy-doubao-tts)
11
+
12
+ 自媒体口播:读全局密钥与音色 → 收口播文案 → 按语境选音色 → 流式合成 MP3 → 回报路径。
13
+
14
+ `SKILL_DIR` = 本 `SKILL.md` 所在目录(`dwy` 同步后随平台落在 `.claude/skills/dwy-doubao-tts` 或 `.agents/skills/dwy-doubao-tts`)。脚本一律用 `$SKILL_DIR/scripts/`,禁止写 `~/.grok/skills`。
15
+
16
+ ## 硬约束
17
+
18
+ 1. **私人配置唯一位置**:`~/.dwy/config.yaml`(权限 `600`),豆包段为顶层 key `doubao_tts`
19
+ 2. **禁止**把 `api_key`、音色 ID 写入 skill、项目、commit;禁止回显完整 Key
20
+ 3. 输出音频默认 `doubao_tts.output_dir`(默认 `~/Movies/doubao-tts`),不落到仓库内
21
+ 4. 模型资源:`X-Api-Resource-Id: seed-icl-2.0`
22
+ 5. 接口:`POST https://openspeech.bytedance.com/api/v3/tts/unidirectional`
23
+
24
+ ## 全局配置约定(`~/.dwy/config.yaml`)
25
+
26
+ 用户级统一配置文件,多业务共用,各业务独立 section:
27
+
28
+ ```yaml
29
+ version: 1
30
+
31
+ doubao_tts:
32
+ api_key: "<仅本机>"
33
+ resource_id: seed-icl-2.0
34
+ endpoint: https://openspeech.bytedance.com/api/v3/tts/unidirectional
35
+ default_voice: young_female
36
+ voices:
37
+ finance:
38
+ id: "<音色ID>"
39
+ label: 金融财经
40
+ young_female:
41
+ id: "<音色ID>"
42
+ label: 年轻女性
43
+ audio:
44
+ format: mp3
45
+ sample_rate: 24000
46
+ output_dir: ~/Movies/doubao-tts
47
+
48
+ # 其它业务可继续追加顶层 key,互不覆盖
49
+ # other_service:
50
+ # ...
51
+ ```
52
+
53
+ 结构说明见 `references/config.example.yaml`(示例无真实密钥)。
54
+
55
+ ## 启动流程(每次触发必做)
56
+
57
+ ### 1. 检查全局配置
58
+
59
+ ```bash
60
+ python3 -c "
61
+ from pathlib import Path
62
+ import sys
63
+ sys.path.insert(0, str(Path(r'$SKILL_DIR') / 'scripts'))
64
+ from global_config import CONFIG_PATH, SECTION_DOUBAO_TTS, get_section
65
+ c = get_section(SECTION_DOUBAO_TTS)
66
+ assert c.get('api_key') and c.get('voices'), 'missing'
67
+ print('ok', CONFIG_PATH)
68
+ "
69
+ ```
70
+
71
+ - 失败 → 「首次配置 / 迁移」,**不要**继续合成
72
+ - 成功 → 步骤 2
73
+
74
+ ### 2. 收集用户输入
75
+
76
+ 消息里若无完整口播正文,必须提问补齐,禁止瞎编:
77
+
78
+ | 字段 | 必选 | 说明 |
79
+ |------|------|------|
80
+ | `text` | 是 | 口播全文 |
81
+ | `voice` | 否 | `finance` / `young_female` / 配置其它 key;不传则自动选 |
82
+ | `output` | 否 | 输出绝对路径 |
83
+ | `speech_rate` / `loudness_rate` | 否 | `[-50,100]` |
84
+ | `context_texts` | 否 | 语音指令 |
85
+
86
+ ### 3. 自动选音色
87
+
88
+ 音色 ID **只从** `~/.dwy/config.yaml` 的 `doubao_tts.voices` 读,skill 内不硬编码真实 ID。
89
+
90
+ | 角色 key | 适用语境 |
91
+ |----------|----------|
92
+ | `finance` | 财经、股市、基金、理财、利率、宏观、投研 |
93
+ | `young_female` | 生活、种草、日常、泛娱乐、默认兜底 |
94
+
95
+ 1. 用户指定 `voice` → 用该 key
96
+ 2. 否则文案命中财经关键词 → `finance`
97
+ 3. 否则 → `default_voice`
98
+ 4. 两可 → 先问用户
99
+
100
+ ### 4. 执行合成
101
+
102
+ ```bash
103
+ python3 "$SKILL_DIR/scripts/synthesize.py" \
104
+ --text-file /tmp/doubao_tts_input.txt \
105
+ --voice <finance|young_female|...> \
106
+ [--output /absolute/path/out.mp3]
107
+ ```
108
+
109
+ - 长文优先 `--text-file`;单次建议 ≤ 1000 汉字
110
+ - 成功 stdout JSON:`{"ok":true,"path":"...","voice":"...","bytes":N}`
111
+
112
+ ### 5. 回报用户
113
+
114
+ 绝对路径、音色 key/label、文件大小;可 `open "<path>"` 试听。**禁止**回显 API Key。
115
+
116
+ ## 首次配置 / 迁移
117
+
118
+ ```bash
119
+ # 从旧路径 ~/.config/doubao-tts/config.json 迁到 ~/.dwy/config.yaml
120
+ python3 "$SKILL_DIR/scripts/setup_config.py" --migrate-legacy
121
+
122
+ # 或写入 / 更新 Key(可同时补音色)
123
+ python3 "$SKILL_DIR/scripts/setup_config.py" \
124
+ --api-key '<USER_KEY>' \
125
+ --voice 'finance=<ID>,金融财经' \
126
+ --voice 'young_female=<ID>,年轻女性'
127
+ ```
128
+
129
+ - 写入:`~/.dwy/config.yaml` → `doubao_tts`
130
+ - 成功后删除旧 JSON,避免双份密钥
131
+ - **永不**写入当前 git 仓库或 skill 目录
132
+
133
+ 仅换 Key:
134
+
135
+ ```bash
136
+ python3 "$SKILL_DIR/scripts/setup_config.py" --api-key '<NEW>' --update-key-only
137
+ ```
138
+
139
+ ## 协议要点
140
+
141
+ 见 `references/api.md`。Header:`X-Api-Key` / `X-Api-Resource-Id` / `X-Api-Request-Id`;流式 `code==0` 拼 base64,结束码 `20000000`。
142
+
143
+ ## 安全
144
+
145
+ - 私人信息只在 `~/.dwy/config.yaml`
146
+ - 不在项目建 `.env` / 本地 config
147
+ - 用户要求拷进项目 → 拒绝
@@ -0,0 +1,43 @@
1
+ # 豆包单向流式 TTS(HTTP Chunked)摘要
2
+
3
+ 权威字段以火山引擎控制台文档为准。实现以 `scripts/synthesize.py` 为准。
4
+
5
+ ## 配置
6
+
7
+ 私人信息只读:`~/.dwy/config.yaml` → 顶层 key `doubao_tts`
8
+ 示例骨架:`config.example.yaml`(无真实密钥)
9
+
10
+ ## Endpoint
11
+
12
+ `POST https://openspeech.bytedance.com/api/v3/tts/unidirectional`
13
+
14
+ ## Headers
15
+
16
+ | Header | 必选 | 说明 |
17
+ |--------|------|------|
18
+ | `X-Api-Key` | 是 | `doubao_tts.api_key` |
19
+ | `X-Api-Resource-Id` | 是 | 默认 `seed-icl-2.0` |
20
+ | `X-Api-Request-Id` | 是 | 客户端 uuid |
21
+
22
+ ## Body
23
+
24
+ ```json
25
+ {
26
+ "req_params": {
27
+ "text": "口播正文",
28
+ "speaker": "<音色ID>",
29
+ "audio_params": {
30
+ "format": "mp3",
31
+ "sample_rate": 24000,
32
+ "speech_rate": 0,
33
+ "loudness_rate": 0
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## 响应(Chunked)
40
+
41
+ - `code == 0`:`data` 为 base64 音频片
42
+ - `code == 20000000`:结束
43
+ - Header `X-Tt-Logid` 排障用
@@ -0,0 +1,33 @@
1
+ # 示例:复制结构到 ~/.dwy/config.yaml(勿提交真实密钥)
2
+ # 路径:/Users/<you>/.dwy/config.yaml
3
+ # 权限:chmod 600 ~/.dwy/config.yaml
4
+
5
+ version: 1
6
+
7
+ # 豆包配音(声音复刻 2.0)
8
+ doubao_tts:
9
+ api_key: "<从火山控制台粘贴>"
10
+ resource_id: seed-icl-2.0
11
+ endpoint: https://openspeech.bytedance.com/api/v3/tts/unidirectional
12
+ default_voice: young_female
13
+ voices:
14
+ finance:
15
+ id: "<金融财经音色ID>"
16
+ label: 金融财经
17
+ desc: 财经、投研、商业分析口播
18
+ young_female:
19
+ id: "<年轻女性音色ID>"
20
+ label: 年轻女性
21
+ desc: 生活、种草、日常、泛娱乐口播
22
+ audio:
23
+ format: mp3
24
+ sample_rate: 24000
25
+ speech_rate: 0
26
+ loudness_rate: 0
27
+ disable_markdown_filter: true
28
+ disable_emoji_filter: true
29
+ output_dir: ~/Movies/doubao-tts
30
+
31
+ # 其它业务配置可并列追加,例如:
32
+ # lark:
33
+ # app_id: "..."
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ """用户全局配置读写:~/.dwy/config.yaml
3
+
4
+ 私人信息(API Key、音色 ID 等)只放在此文件,不进 skill、不进项目仓库。
5
+ 多业务共用一个 YAML,各业务占独立顶层 key(如 doubao_tts)。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import stat
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ # 用户主目录全局配置根(非项目 .dwy)
18
+ DWY_HOME = Path.home() / ".dwy"
19
+ CONFIG_PATH = DWY_HOME / "config.yaml"
20
+
21
+ # 豆包配音在全局 YAML 中的 section 名
22
+ SECTION_DOUBAO_TTS = "doubao_tts"
23
+
24
+ # 旧路径:迁移用
25
+ LEGACY_JSON = Path.home() / ".config" / "doubao-tts" / "config.json"
26
+
27
+
28
+ def ensure_dwy_home() -> None:
29
+ """确保 ~/.dwy 存在且仅本人可访问。"""
30
+ DWY_HOME.mkdir(parents=True, exist_ok=True)
31
+ os.chmod(DWY_HOME, stat.S_IRWXU)
32
+
33
+
34
+ def load_root() -> dict[str, Any]:
35
+ """加载整份全局 YAML;不存在返回带 version 的空结构。"""
36
+ if not CONFIG_PATH.is_file():
37
+ return {"version": 1}
38
+ data = yaml.safe_load(CONFIG_PATH.read_text(encoding="utf-8"))
39
+ if data is None:
40
+ return {"version": 1}
41
+ if not isinstance(data, dict):
42
+ raise ValueError(f"全局配置格式错误(需 mapping): {CONFIG_PATH}")
43
+ data.setdefault("version", 1)
44
+ return data
45
+
46
+
47
+ def save_root(root: dict[str, Any]) -> Path:
48
+ """原子写入 ~/.dwy/config.yaml,chmod 600。"""
49
+ ensure_dwy_home()
50
+ root = dict(root)
51
+ root.setdefault("version", 1)
52
+ text = yaml.safe_dump(
53
+ root,
54
+ allow_unicode=True,
55
+ default_flow_style=False,
56
+ sort_keys=False,
57
+ )
58
+ tmp = CONFIG_PATH.with_suffix(".yaml.tmp")
59
+ tmp.write_text(text, encoding="utf-8")
60
+ tmp.replace(CONFIG_PATH)
61
+ os.chmod(CONFIG_PATH, stat.S_IRUSR | stat.S_IWUSR)
62
+ return CONFIG_PATH
63
+
64
+
65
+ def get_section(name: str) -> dict[str, Any]:
66
+ """读取某一业务 section;缺失返回 {}。"""
67
+ root = load_root()
68
+ sec = root.get(name)
69
+ if sec is None:
70
+ return {}
71
+ if not isinstance(sec, dict):
72
+ raise ValueError(f"section {name!r} 必须是 mapping: {CONFIG_PATH}")
73
+ return sec
74
+
75
+
76
+ def set_section(name: str, section: dict[str, Any]) -> Path:
77
+ """覆盖写入某一业务 section,保留其它业务配置。"""
78
+ root = load_root()
79
+ root[name] = section
80
+ return save_root(root)
81
+
82
+
83
+ def try_load_legacy_doubao_json() -> dict[str, Any]:
84
+ """读取旧版 ~/.config/doubao-tts/config.json(若存在)。"""
85
+ if not LEGACY_JSON.is_file():
86
+ return {}
87
+ import json
88
+
89
+ return json.loads(LEGACY_JSON.read_text(encoding="utf-8"))
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ """初始化 / 更新豆包配音配置到用户全局 ~/.dwy/config.yaml(doubao_tts section)。
3
+
4
+ 禁止写入 skill 目录与任何 git 项目。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import copy
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ # 同目录模块
16
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
17
+
18
+ from global_config import ( # noqa: E402
19
+ LEGACY_JSON,
20
+ SECTION_DOUBAO_TTS,
21
+ get_section,
22
+ set_section,
23
+ try_load_legacy_doubao_json,
24
+ )
25
+
26
+ # 结构默认值:不含真实 api_key / 音色 ID(私人信息只由用户写入全局 YAML)
27
+ DEFAULT_SECTION: dict = {
28
+ "api_key": "",
29
+ "resource_id": "seed-icl-2.0",
30
+ "endpoint": "https://openspeech.bytedance.com/api/v3/tts/unidirectional",
31
+ "voices": {},
32
+ "default_voice": "young_female",
33
+ "audio": {
34
+ "format": "mp3",
35
+ "sample_rate": 24000,
36
+ "speech_rate": 0,
37
+ "loudness_rate": 0,
38
+ "disable_markdown_filter": True,
39
+ "disable_emoji_filter": True,
40
+ },
41
+ "output_dir": str(Path.home() / "Movies" / "doubao-tts"),
42
+ }
43
+
44
+
45
+ def deep_merge(base: dict, overlay: dict) -> dict:
46
+ """浅层 + 一层 dict 合并;overlay 优先。"""
47
+ out = copy.deepcopy(base)
48
+ for k, v in overlay.items():
49
+ if isinstance(v, dict) and isinstance(out.get(k), dict):
50
+ merged = dict(out[k])
51
+ merged.update(v)
52
+ out[k] = merged
53
+ else:
54
+ out[k] = copy.deepcopy(v)
55
+ return out
56
+
57
+
58
+ def load_doubao_section() -> dict:
59
+ """优先 ~/.dwy/config.yaml#doubao_tts,其次旧 JSON。"""
60
+ sec = get_section(SECTION_DOUBAO_TTS)
61
+ if sec:
62
+ return sec
63
+ return try_load_legacy_doubao_json()
64
+
65
+
66
+ def main() -> int:
67
+ parser = argparse.ArgumentParser(description="写入豆包配音到 ~/.dwy/config.yaml")
68
+ parser.add_argument("--api-key", help="火山引擎语音 API Key")
69
+ parser.add_argument(
70
+ "--update-key-only",
71
+ action="store_true",
72
+ help="仅更新 api_key",
73
+ )
74
+ parser.add_argument("--default-voice", help="覆盖 default_voice(如 finance / young_female)")
75
+ parser.add_argument("--output-dir", help="覆盖口播输出目录")
76
+ parser.add_argument(
77
+ "--voice",
78
+ action="append",
79
+ metavar="KEY=ID[,label]",
80
+ help="写入/更新音色,可重复。例: --voice finance=S_xxx,金融财经",
81
+ )
82
+ parser.add_argument(
83
+ "--migrate-legacy",
84
+ action="store_true",
85
+ help="强制从 ~/.config/doubao-tts/config.json 迁移到 ~/.dwy/config.yaml",
86
+ )
87
+ args = parser.parse_args()
88
+
89
+ existing = load_doubao_section()
90
+ legacy = try_load_legacy_doubao_json()
91
+
92
+ if args.migrate_legacy and legacy:
93
+ existing = deep_merge(existing or {}, legacy)
94
+
95
+ if args.update_key_only:
96
+ if not args.api_key:
97
+ print("error: --update-key-only 需要 --api-key", file=sys.stderr)
98
+ return 2
99
+ if not existing:
100
+ print("error: 尚无 doubao_tts 配置可更新", file=sys.stderr)
101
+ return 2
102
+ sec = copy.deepcopy(existing)
103
+ sec["api_key"] = args.api_key.strip()
104
+ else:
105
+ sec = deep_merge(DEFAULT_SECTION, existing or {})
106
+ # 首次且无 voices:若有 legacy 整段带入
107
+ if not sec.get("voices") and legacy.get("voices"):
108
+ sec = deep_merge(sec, legacy)
109
+ if args.api_key:
110
+ sec["api_key"] = args.api_key.strip()
111
+ elif not (sec.get("api_key") or "").strip():
112
+ if legacy.get("api_key"):
113
+ sec["api_key"] = legacy["api_key"]
114
+ else:
115
+ print("error: 需要 --api-key(或先有可迁移的旧配置)", file=sys.stderr)
116
+ return 2
117
+
118
+ if args.default_voice:
119
+ sec["default_voice"] = args.default_voice
120
+ if args.output_dir:
121
+ sec["output_dir"] = str(Path(args.output_dir).expanduser())
122
+
123
+ if args.voice:
124
+ voices = dict(sec.get("voices") or {})
125
+ for item in args.voice:
126
+ if "=" not in item:
127
+ print(f"error: 无效 --voice {item!r},需 KEY=ID[,label]", file=sys.stderr)
128
+ return 2
129
+ key, rest = item.split("=", 1)
130
+ key = key.strip()
131
+ if "," in rest:
132
+ vid, label = rest.split(",", 1)
133
+ voices[key] = {"id": vid.strip(), "label": label.strip()}
134
+ else:
135
+ prev = voices.get(key) if isinstance(voices.get(key), dict) else {}
136
+ voices[key] = {**prev, "id": rest.strip()}
137
+ sec["voices"] = voices
138
+
139
+ if not (sec.get("api_key") or "").strip():
140
+ print("error: api_key 为空", file=sys.stderr)
141
+ return 2
142
+
143
+ path = set_section(SECTION_DOUBAO_TTS, sec)
144
+
145
+ removed_legacy = False
146
+ if LEGACY_JSON.is_file() and (args.migrate_legacy or legacy):
147
+ try:
148
+ LEGACY_JSON.unlink()
149
+ removed_legacy = True
150
+ parent = LEGACY_JSON.parent
151
+ if parent.is_dir() and not any(parent.iterdir()):
152
+ parent.rmdir()
153
+ except OSError:
154
+ pass
155
+
156
+ print(
157
+ json.dumps(
158
+ {
159
+ "ok": True,
160
+ "path": str(path),
161
+ "section": SECTION_DOUBAO_TTS,
162
+ "voices": list((sec.get("voices") or {}).keys()),
163
+ "default_voice": sec.get("default_voice"),
164
+ "resource_id": sec.get("resource_id"),
165
+ "has_api_key": bool(sec.get("api_key")),
166
+ "removed_legacy": removed_legacy,
167
+ },
168
+ ensure_ascii=False,
169
+ )
170
+ )
171
+ return 0
172
+
173
+
174
+ if __name__ == "__main__":
175
+ raise SystemExit(main())