@optima-chat/optima-agent 0.9.7 → 0.9.8

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 (89) hide show
  1. package/.claude/skills/.kb-skills-managed.json +9 -9
  2. package/.claude/skills/ads/SKILL.md +244 -244
  3. package/.claude/skills/ads/template/campaign/CREATIVES.md +18 -18
  4. package/.claude/skills/ads/template/campaign/NOTES.md +10 -10
  5. package/.claude/skills/ads/template/campaign/STRATEGY.md +29 -29
  6. package/.claude/skills/ads/template/user/ADS.md +29 -29
  7. package/.claude/skills/ads/template/user/LEARNINGS.md +15 -15
  8. package/.claude/skills/ads/template/user/PROGRESS.md +20 -20
  9. package/.claude/skills/ads/template/user/README.md +25 -25
  10. package/.claude/skills/ads/template/user/assets/.gitignore +2 -2
  11. package/.claude/skills/bi/SKILL.md +131 -131
  12. package/.claude/skills/browser/SKILL.md +201 -201
  13. package/.claude/skills/channels/SKILL.md +188 -188
  14. package/.claude/skills/collection/SKILL.md +88 -88
  15. package/.claude/skills/douyin/SKILL.md +408 -408
  16. package/.claude/skills/ffmpeg/SKILL.md +164 -164
  17. package/.claude/skills/gen/SKILL.md +279 -275
  18. package/.claude/skills/growth/SKILL.md +90 -90
  19. package/.claude/skills/growth/template/ACCOUNTS.md +14 -14
  20. package/.claude/skills/growth/template/CALENDAR.md +7 -7
  21. package/.claude/skills/growth/template/COMMENTS.md +7 -7
  22. package/.claude/skills/growth/template/GROWTH.md +37 -37
  23. package/.claude/skills/growth/template/PROGRESS.md +4 -4
  24. package/.claude/skills/growth/template/README.md +20 -20
  25. package/.claude/skills/growth/template/TOPICS.md +7 -7
  26. package/.claude/skills/homepage/SKILL.md +177 -177
  27. package/.claude/skills/i18n/SKILL.md +517 -517
  28. package/.claude/skills/ingesting-sources/SKILL.md +94 -94
  29. package/.claude/skills/initializing-kb/SKILL.md +117 -117
  30. package/.claude/skills/instagram/SKILL.md +321 -321
  31. package/.claude/skills/inventory/SKILL.md +328 -328
  32. package/.claude/skills/kol-outreach/SKILL.md +232 -232
  33. package/.claude/skills/kol-outreach/template/campaign/CONFIG.md +60 -60
  34. package/.claude/skills/kol-outreach/template/campaign/KOLS.md +6 -6
  35. package/.claude/skills/kol-outreach/template/campaign/PROGRESS.md +3 -3
  36. package/.claude/skills/kol-outreach/template/campaign/TEMPLATES.md +88 -88
  37. package/.claude/skills/kol-outreach/template/merchant/BRAND.md +36 -36
  38. package/.claude/skills/kol-outreach/template/merchant/CAMPAIGNS.md +6 -6
  39. package/.claude/skills/kol-outreach/template/merchant/MERCHANT_LIMITS.md +16 -16
  40. package/.claude/skills/kol-outreach/template/merchant/PROGRESS.md +4 -4
  41. package/.claude/skills/kol-outreach/template/merchant/README.md +20 -20
  42. package/.claude/skills/linting-the-wiki/SKILL.md +68 -68
  43. package/.claude/skills/logistics/SKILL.md +180 -180
  44. package/.claude/skills/markdown-pdf/SKILL.md +72 -72
  45. package/.claude/skills/merchant/SKILL.md +110 -110
  46. package/.claude/skills/multigrid-poster/SKILL.md +192 -0
  47. package/.claude/skills/multigrid-poster/layouts/2x2.json +34 -0
  48. package/.claude/skills/multigrid-poster/layouts/3x3.json +43 -0
  49. package/.claude/skills/multigrid-poster/scripts/compose.py +116 -0
  50. package/.claude/skills/multigrid-poster/scripts/placeholder.png +0 -0
  51. package/.claude/skills/multigrid-poster/shared/fonts/MaShanZheng-Regular.ttf +0 -0
  52. package/.claude/skills/order/SKILL.md +452 -452
  53. package/.claude/skills/product/SKILL.md +379 -379
  54. package/.claude/skills/product-page/SKILL.md +106 -106
  55. package/.claude/skills/querying-the-wiki/SKILL.md +59 -59
  56. package/.claude/skills/reddit/SKILL.md +277 -277
  57. package/.claude/skills/review/SKILL.md +321 -321
  58. package/.claude/skills/scout/SKILL.md +462 -462
  59. package/.claude/skills/sentinel/SKILL.md +281 -281
  60. package/.claude/skills/shein/SKILL.md +246 -246
  61. package/.claude/skills/shipping/SKILL.md +200 -200
  62. package/.claude/skills/shop-content/SKILL.md +101 -101
  63. package/.claude/skills/shopify/SKILL.md +282 -282
  64. package/.claude/skills/skillify/SKILL.md +114 -114
  65. package/.claude/skills/taobao/SKILL.md +238 -238
  66. package/.claude/skills/tiktok/SKILL.md +381 -381
  67. package/.claude/skills/twitter/SKILL.md +302 -302
  68. package/.claude/skills/updating-related-pages/SKILL.md +65 -65
  69. package/.claude/skills/video-edit/SKILL.md +143 -143
  70. package/.claude/skills/video-gen/SKILL.md +548 -571
  71. package/.claude/skills/video-gen/templates/INDEX.md +78 -78
  72. package/.claude/skills/video-gen/templates/before-after-beauty.md +183 -183
  73. package/.claude/skills/video-gen/templates/drama-fmcg.md +183 -183
  74. package/.claude/skills/video-gen/templates/kol-reaction-food.md +193 -193
  75. package/.claude/skills/video-gen/templates/multi-point-apparel.md +185 -185
  76. package/.claude/skills/video-gen/templates/pain-solution-home.md +184 -184
  77. package/.claude/skills/video-gen/templates/pdp-360-showcase.md +189 -189
  78. package/.claude/skills/video-gen/templates/pdp-feature-highlight.md +182 -182
  79. package/.claude/skills/video-gen/templates/scene-digital.md +183 -183
  80. package/.claude/skills/wechat/SKILL.md +174 -174
  81. package/.claude/skills/xhs/SKILL.md +170 -170
  82. package/README.md +276 -276
  83. package/dist/bin/optima.js +26 -26
  84. package/dist/bin/serve.js +23 -23
  85. package/dist/src/agent.js +4 -4
  86. package/dist/src/system-prompt.js +169 -169
  87. package/dist/src/tools/memory.js +10 -10
  88. package/dist/src/ui/headless.js +7 -7
  89. package/package.json +79 -79
@@ -1,110 +1,110 @@
1
- ---
2
- name: merchant
3
- description: "店铺基础信息管理。使用场景:查看店铺信息、更新店铺名称/描述/联系方式/发货地址。注意:运费设置用 shipping skill。"
4
- ---
5
-
6
- # 店铺管理
7
-
8
- ## 典型场景
9
-
10
- | 用户说 | 命令 |
11
- |--------|------|
12
- | "查看店铺信息" / "我的店铺" | `commerce merchant info` |
13
- | "修改店铺名称" / "更新店铺描述" | `commerce merchant update --name/--description` |
14
- | "设置联系方式" | `commerce merchant update --contact-*` |
15
- | "更新发货地址" | `commerce merchant update --origin-*` |
16
- | "设置品牌色" / "修改主题色" / "换个颜色" | `commerce merchant update --primary-color "#1a3a5c"` |
17
-
18
- ## 不在此模块(重要)
19
-
20
- | 用户说 | 正确位置 |
21
- |--------|----------|
22
- | "运费设置" / "运费模式" / "运费区域" | **shipping skill**:`commerce shipping get-mode` |
23
- | "Easyship" / "快递公司" | **shipping skill**:`commerce easyship couriers` |
24
- | "Stripe/支付设置" | `commerce stripe status` |
25
- | "首页设置" | **homepage skill** |
26
-
27
- ---
28
-
29
- ## 查看店铺信息
30
-
31
- ```bash
32
- commerce merchant info
33
- ```
34
-
35
- 返回:店铺名称、描述、Logo、货币、联系信息、发货地址
36
-
37
- **注意**:此命令不返回运费设置,运费相关请用 `commerce shipping get-mode`
38
-
39
- ---
40
-
41
- ## 更新店铺信息
42
-
43
- ```bash
44
- commerce merchant update [选项]
45
- ```
46
-
47
- ### 基础信息
48
- | 选项 | 说明 |
49
- |------|------|
50
- | `--name` | 店铺名称 |
51
- | `--slug` | 店铺标识(影响 URL) |
52
- | `--description` | 店铺描述 |
53
- | `--default-currency` | 默认货币(USD, CNY 等) |
54
- | `--google-ads-customer-id` | Google Ads 客户 ID(10位数字) |
55
- | `--primary-color` | 品牌主色(hex 格式,如 #FF5A5F) |
56
-
57
- ### 联系方式
58
- | 选项 | 说明 |
59
- |------|------|
60
- | `--contact-name` | 联系人 |
61
- | `--contact-phone` | 联系电话 |
62
- | `--contact-email` | 联系邮箱 |
63
- | `--company-name` | 公司名称 |
64
-
65
- ### 发货地址
66
- | 选项 | 说明 |
67
- |------|------|
68
- | `--origin-country-alpha2` | 国家代码(CN, US 等) |
69
- | `--origin-city` | 城市 |
70
- | `--origin-postal-code` | 邮编 |
71
- | `--origin-line-1` | 地址行1 |
72
- | `--origin-line-2` | 地址行2 |
73
- | `--origin-state` | 省/州 |
74
-
75
- ---
76
-
77
- ## 示例
78
-
79
- ```bash
80
- # 更新名称和描述
81
- commerce merchant update --name "我的小店" --description "精选好物"
82
-
83
- # 更新联系方式
84
- commerce merchant update \
85
- --contact-name "张三" \
86
- --contact-phone "13800138000" \
87
- --contact-email "shop@example.com"
88
-
89
- # 更新发货地址
90
- commerce merchant update \
91
- --origin-country-alpha2 CN \
92
- --origin-city 深圳 \
93
- --origin-postal-code 518000 \
94
- --origin-line-1 "科技园路1号" \
95
- --origin-state 广东
96
-
97
- # 设置品牌色
98
- commerce merchant update --primary-color "#1a3a5c"
99
- ```
100
-
101
- ---
102
-
103
- ## 命令参考
104
-
105
- - `commerce merchant info` - 查看店铺信息
106
- - `commerce merchant update` - 更新店铺信息
107
-
108
- ## 不确定用什么命令?
109
-
110
- 运行 `commerce --help` 查看所有模块,或 `commerce merchant --help` 查看子命令。
1
+ ---
2
+ name: merchant
3
+ description: "店铺基础信息管理。使用场景:查看店铺信息、更新店铺名称/描述/联系方式/发货地址。注意:运费设置用 shipping skill。"
4
+ ---
5
+
6
+ # 店铺管理
7
+
8
+ ## 典型场景
9
+
10
+ | 用户说 | 命令 |
11
+ |--------|------|
12
+ | "查看店铺信息" / "我的店铺" | `commerce merchant info` |
13
+ | "修改店铺名称" / "更新店铺描述" | `commerce merchant update --name/--description` |
14
+ | "设置联系方式" | `commerce merchant update --contact-*` |
15
+ | "更新发货地址" | `commerce merchant update --origin-*` |
16
+ | "设置品牌色" / "修改主题色" / "换个颜色" | `commerce merchant update --primary-color "#1a3a5c"` |
17
+
18
+ ## 不在此模块(重要)
19
+
20
+ | 用户说 | 正确位置 |
21
+ |--------|----------|
22
+ | "运费设置" / "运费模式" / "运费区域" | **shipping skill**:`commerce shipping get-mode` |
23
+ | "Easyship" / "快递公司" | **shipping skill**:`commerce easyship couriers` |
24
+ | "Stripe/支付设置" | `commerce stripe status` |
25
+ | "首页设置" | **homepage skill** |
26
+
27
+ ---
28
+
29
+ ## 查看店铺信息
30
+
31
+ ```bash
32
+ commerce merchant info
33
+ ```
34
+
35
+ 返回:店铺名称、描述、Logo、货币、联系信息、发货地址
36
+
37
+ **注意**:此命令不返回运费设置,运费相关请用 `commerce shipping get-mode`
38
+
39
+ ---
40
+
41
+ ## 更新店铺信息
42
+
43
+ ```bash
44
+ commerce merchant update [选项]
45
+ ```
46
+
47
+ ### 基础信息
48
+ | 选项 | 说明 |
49
+ |------|------|
50
+ | `--name` | 店铺名称 |
51
+ | `--slug` | 店铺标识(影响 URL) |
52
+ | `--description` | 店铺描述 |
53
+ | `--default-currency` | 默认货币(USD, CNY 等) |
54
+ | `--google-ads-customer-id` | Google Ads 客户 ID(10位数字) |
55
+ | `--primary-color` | 品牌主色(hex 格式,如 #FF5A5F) |
56
+
57
+ ### 联系方式
58
+ | 选项 | 说明 |
59
+ |------|------|
60
+ | `--contact-name` | 联系人 |
61
+ | `--contact-phone` | 联系电话 |
62
+ | `--contact-email` | 联系邮箱 |
63
+ | `--company-name` | 公司名称 |
64
+
65
+ ### 发货地址
66
+ | 选项 | 说明 |
67
+ |------|------|
68
+ | `--origin-country-alpha2` | 国家代码(CN, US 等) |
69
+ | `--origin-city` | 城市 |
70
+ | `--origin-postal-code` | 邮编 |
71
+ | `--origin-line-1` | 地址行1 |
72
+ | `--origin-line-2` | 地址行2 |
73
+ | `--origin-state` | 省/州 |
74
+
75
+ ---
76
+
77
+ ## 示例
78
+
79
+ ```bash
80
+ # 更新名称和描述
81
+ commerce merchant update --name "我的小店" --description "精选好物"
82
+
83
+ # 更新联系方式
84
+ commerce merchant update \
85
+ --contact-name "张三" \
86
+ --contact-phone "13800138000" \
87
+ --contact-email "shop@example.com"
88
+
89
+ # 更新发货地址
90
+ commerce merchant update \
91
+ --origin-country-alpha2 CN \
92
+ --origin-city 深圳 \
93
+ --origin-postal-code 518000 \
94
+ --origin-line-1 "科技园路1号" \
95
+ --origin-state 广东
96
+
97
+ # 设置品牌色
98
+ commerce merchant update --primary-color "#1a3a5c"
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 命令参考
104
+
105
+ - `commerce merchant info` - 查看店铺信息
106
+ - `commerce merchant update` - 更新店铺信息
107
+
108
+ ## 不确定用什么命令?
109
+
110
+ 运行 `commerce --help` 查看所有模块,或 `commerce merchant --help` 查看子命令。
@@ -0,0 +1,192 @@
1
+ ---
2
+ name: multigrid-poster
3
+ description: "为商家生成小红书 2×2 四宫格 / 3×3 九宫格封面图。触发场景:做小红书封面 / 小红书首图 / 种草帖封面 / 爆款封面 / 四宫格 / 九宫格。一句话指令产出 1242×1660 成片,支持自然语言迭代(换版式 / 重抽某格 / 改文案)。本 skill 只生成封面图,搜索小红书笔记 / 分析博主请用 'xhs' skill。"
4
+ ---
5
+
6
+ # 小红书多宫格封面生成
7
+
8
+ 帮电商商家用 AI 图 + 通用网格布局合成小红书封面。**一句话从意图到 1242×1660 成片**,支持 4 宫格 / 9 宫格两种版式。
9
+
10
+ ## Global Rules
11
+
12
+ 优先级高于任何 pipeline 步骤。
13
+
14
+ 1. **User-facing 不出现模型名 / 服务名**
15
+ status / 成本 / 进度统一用"封面生成中 / 素材生成中 / 合成封面中"。`gen image` 作为 CLI 字面值可以,但不要把整条命令原文回显给用户。
16
+
17
+ 2. **花钱前必走 COST-GATE**
18
+ 任何 `gen image` 批量调用之前必走一次成本确认。**Fast-path、迭代、重试均无例外**。2×2 = 4 张,3×3 = 9 张,SKU 拉图 = 0 张。rate 按 `gen image` 每次 1 积分估。
19
+
20
+ 3. **Per-post init 是任何 pipeline 第一步**
21
+ 出图 / 合成执行前先建目录 + cd。否则 write 写错位置。迭代场景用 `{旧id}-vN`。
22
+
23
+ 4. **Anti-fabrication**
24
+ 未在本 skill 显式列出的命令 / flag / 参数,不允许凭印象拼。`gen image` / `commerce` / `compose.py` 子命令同样适用。
25
+
26
+ 5. **不自动发帖**
27
+ 只产 PNG,**绝对不调用**任何自动登录 / 发帖 / 上传命令。完成后给路径,用户自己下载手动发。
28
+
29
+ ## 工作目录
30
+
31
+ ```
32
+ ~/multigrid-poster/
33
+ ├── preferences.md
34
+ ├── history.md
35
+ └── posters/{post-id}/
36
+ ├── intent.md # 用户意图 + layout + 文案
37
+ ├── cells/cell_0..N.png # 4 或 9 张素材
38
+ ├── cover.png # 成片
39
+ └── cost.md
40
+ ```
41
+
42
+ 整个 `~/multigrid-poster/` 是一个 git repo。每步完成后 `git add -A && git commit`。
43
+
44
+ ## 启动流程
45
+
46
+ 1. **首次** (`ls ~/multigrid-poster/preferences.md` 不存在) → `mkdir -p ~/multigrid-poster/posters && cd ~/multigrid-poster && git init -b main`,创建空的 `preferences.md` (字段:merchant_id / brand_name / category / xhs_account_id / default_layout) 和 `history.md` (表头:date | post-id | layout | title | parent | status)。`.gitignore` 加 `posters/**/cells/*.png` 和 `posters/**/cover.png`。
47
+ 2. **扫未完成**:`posters/` 下有 `intent.md` 但无 `cover.png` 且 < 7 天 → 提一次"你有 N 个封面没完成"。
48
+ 3. **读 preferences.md / history.md**,继续主流程。
49
+
50
+ ## 主流程
51
+
52
+ ### Step 1: 选 layout
53
+
54
+ | 用户原话 | layout | cells |
55
+ |---|---|---|
56
+ | 含"九宫格 / 9 格 / 9 张 / 清单 / N 款 / 礼物推荐 / 榜单" | **3×3** | 9 |
57
+ | 其他(包含"四宫格 / 4 格 / 4 张" 或没指定) | **2×2** | 4 |
58
+
59
+ ### Step 2: Per-post init
60
+
61
+ ```bash
62
+ # slug = 用户意图前 20 字内的 kebab-case
63
+ POST_ID="$(date +%Y%m%d-%H%M)-<slug>"
64
+ mkdir -p ~/multigrid-poster/posters/$POST_ID/cells
65
+ cd ~/multigrid-poster/posters/$POST_ID
66
+ ```
67
+
68
+ 迭代("换版式 / 重抽 / 改文案")时:轻迭代沿用旧目录,重迭代新建 `{旧id}-vN`。
69
+
70
+ ### Step 3: 写文案
71
+
72
+ agent 自己写,不调外部生成器。约束:
73
+ - **title**:2 行 × 8-12 字 / 行(2×2 适合)或 2 行 × 6-10 字 / 行(3×3 标题挤)
74
+ - **caption**:2 行 × 15-20 字 / 行
75
+ - 硬禁:医疗 / 保健 / 绝对化用语(最 / 第一 / 唯一 / 100%)
76
+
77
+ 写到 `intent.md`:
78
+
79
+ ```markdown
80
+ # Intent
81
+ | 项目 | 值 |
82
+ |---|---|
83
+ | 用户原话 | <原话> |
84
+ | layout | 2x2 / 3x3 |
85
+ | title 行 1 | <8-12 字> |
86
+ | title 行 2 | <8-12 字> |
87
+ | caption 行 1 | <15-20 字> |
88
+ | caption 行 2 | <15-20 字> |
89
+ ```
90
+
91
+ 展示给用户:"标题'XXX / YYY',副标题'AAA / BBB'。OK 吗?"
92
+ - Fast-path(意图明确):告知,用户喊停才停
93
+ - 意图模糊:必须等确认
94
+
95
+ ### Step 4: COST-GATE
96
+
97
+ **生成前必做**(包括 Fast-path / 迭代 / 重试):
98
+
99
+ > 即将生成封面(布局: 2x2 / 3x3),预计:
100
+ > - 素材调用: N 次(2×2=4 / 3×3=9 / SKU 拉图=0)
101
+ > - 预估耗时: ~X 分钟
102
+ > - 预估成本: ~Y 积分
103
+ >
104
+ > 继续?
105
+
106
+ 用户说"继续 / 好" → 执行。"太贵 / 换便宜的" → 提议降级 (3×3 → 2×2,或 SKU 拉图)。不回应 → 等。
107
+
108
+ ### Step 5: 出图
109
+
110
+ **默认走 AI 生图**。每个 cell 并行调一次:
111
+
112
+ ```bash
113
+ # 2×2: cell 尺寸 621×830;3×3: cell 尺寸 414×420
114
+ gen image "<subprompt>" -W <W> -H <H> -o ./cells/cell_<i>.png -s <seed> -f png
115
+ ```
116
+
117
+ `<subprompt>` 由 agent 根据用户意图为每格独立设计(不同视角 / 不同 step / 不同场景 / 不同 SKU 等)。`<seed>` 用确定性 hash(POST_ID + cell_index),迭代复用同格 seed。
118
+
119
+ **SKU 拉图模式**(用户明确说"用我店里商品图"做 listicle):
120
+
121
+ ```bash
122
+ commerce product list --limit 9
123
+ ```
124
+
125
+ 下载到 `./cells/cell_0..8.png`。商品 < 9 → 降级 2×2 取前 4。商品 < 4 → 报错。
126
+
127
+ **失败容忍**:单格生图失败 → 重试 1 次(换 seed)。两次失败 → 用 `${CLAUDE_SKILL_DIR}/scripts/placeholder.png` 占位,告知用户"第 N 格失败,先占位,要重抽直接说"。
128
+
129
+ ### Step 6: 合成
130
+
131
+ ```bash
132
+ python3 $CLAUDE_SKILL_DIR/scripts/compose.py \
133
+ --layout $CLAUDE_SKILL_DIR/layouts/2x2.json \
134
+ --cells ./cells/cell_0.png ./cells/cell_1.png ... \
135
+ --title-line "<title 行 1>" \
136
+ --title-line "<title 行 2>" \
137
+ --caption-line "<caption 行 1>" \
138
+ --caption-line "<caption 行 2>" \
139
+ --output ./cover.png
140
+ ```
141
+
142
+ 依赖:Pillow(容器自带)。失败常见原因:
143
+
144
+ | 错误 | 处理 |
145
+ |---|---|
146
+ | `cell 数量不对` | layout 要求 4 / 9,检查 `--cells` 参数 |
147
+ | `font not found` | 检查 `$CLAUDE_SKILL_DIR/shared/fonts/` 完整 |
148
+ | 中文显示方块 | 同上,字体没加载 |
149
+
150
+ ### Step 7: 交付
151
+
152
+ 写 `cost.md`,追加 `~/multigrid-poster/history.md` 一行,告知用户:
153
+
154
+ > 封面在 `~/multigrid-poster/posters/<POST_ID>/cover.png`,可以下载发帖了。
155
+ > 换版式 / 改文案 / 重抽某格直接告诉我。
156
+
157
+ 用户说"好 / 完美" → preferences.md `Learned` 追加一条 → commit。
158
+
159
+ ## 迭代
160
+
161
+ | 类型 | 重跑步骤 | 新目录 | 成本 |
162
+ |---|---|---|---|
163
+ | 换 layout(2×2 ↔ 3×3) | 文案 → 出图 → 合成 | 是(`-vN`) | 全成本 |
164
+ | 重抽全部 | 出图 → 合成 | 是 | 全素材 |
165
+ | 重抽单格 N | 出图(单格) → 合成 | 否 | 1 素材 |
166
+ | 改文案 | 合成 | 否 | 0 |
167
+
168
+ **每次迭代也走 COST-GATE**,即使 0 积分。
169
+
170
+ ## 错误处理
171
+
172
+ | 故障 | 处理 |
173
+ |---|---|
174
+ | `gen image` 返回 failed | 重试 1 次换 seed → 仍失败用占位图 |
175
+ | 超配额 / 余额不足 | 告知用户,不自动降级 |
176
+ | `commerce product list` < 9 | 降级 2×2 取前 4 |
177
+ | 会话关闭 | 状态在文件系统 + git,下次接续 |
178
+
179
+ ## 相关工具
180
+
181
+ - `gen image` — 文生图(详见 `gen` skill)
182
+ - `commerce merchant get` / `commerce product list` — 商家档案 / 商品(详见 `merchant` skill 和 `product` skill)
183
+ - `compose.py` — 本 skill 自带的 Pillow 渲染器
184
+
185
+ ## 流程偏好
186
+
187
+ - **信息够就直接做(Fast-path)**
188
+ - **`intent.md` 是可追溯产物**
189
+ - **每步完成立刻 git commit**
190
+ - **生成过程零打扰**
191
+ - **迭代用 `-vN` 不覆盖**
192
+ - **新会话有未完成先告知一次**
@@ -0,0 +1,34 @@
1
+ {
2
+ "_comment": "通用 2×2 网格布局 — 适合 4 张 cell 的所有 intent (创业故事/对比测评/教程/场景)",
3
+ "canvas_size": [1242, 1660],
4
+ "cells": {
5
+ "positions": [[0, 0], [621, 0], [0, 830], [621, 830]],
6
+ "sizes": [[621, 830], [621, 830], [621, 830], [621, 830]]
7
+ },
8
+ "text_zones": {
9
+ "title": {
10
+ "_comment": "中央偏上 2 行标题 - 8-12 字 / 行最佳",
11
+ "font": "shared/fonts/MaShanZheng-Regular.ttf",
12
+ "size": 110,
13
+ "color": "#FFB940",
14
+ "stroke_w": 8,
15
+ "stroke_color": "#D63D3D",
16
+ "lines": [
17
+ {"position": [621, 480], "anchor": "mm"},
18
+ {"position": [621, 620], "anchor": "mm"}
19
+ ]
20
+ },
21
+ "caption": {
22
+ "_comment": "底部 2 行 caption - 15-20 字 / 行最佳",
23
+ "font": "shared/fonts/MaShanZheng-Regular.ttf",
24
+ "size": 78,
25
+ "color": "#FFB940",
26
+ "stroke_w": 6,
27
+ "stroke_color": "#D63D3D",
28
+ "lines": [
29
+ {"position": [621, 1340], "anchor": "mm"},
30
+ {"position": [621, 1450], "anchor": "mm"}
31
+ ]
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "_comment": "通用 3×3 网格布局 - 适合 9 张 cell (商品清单 / 多角度展示)",
3
+ "canvas_size": [1242, 1660],
4
+ "cells": {
5
+ "_comment": "9 格 414×420。顶部 200px 标题区,cells y=200..1460,底部 200px caption 区。3×420 + 200×2 = 1660 = canvas_h ✓",
6
+ "positions": [
7
+ [0, 200], [414, 200], [828, 200],
8
+ [0, 620], [414, 620], [828, 620],
9
+ [0, 1040], [414, 1040], [828, 1040]
10
+ ],
11
+ "sizes": [
12
+ [414, 420], [414, 420], [414, 420],
13
+ [414, 420], [414, 420], [414, 420],
14
+ [414, 420], [414, 420], [414, 420]
15
+ ]
16
+ },
17
+ "text_zones": {
18
+ "title": {
19
+ "_comment": "顶部白边 2 行标题(y < 200 区间)",
20
+ "font": "shared/fonts/MaShanZheng-Regular.ttf",
21
+ "size": 78,
22
+ "color": "#FFB940",
23
+ "stroke_w": 6,
24
+ "stroke_color": "#D63D3D",
25
+ "lines": [
26
+ {"position": [621, 60], "anchor": "mm"},
27
+ {"position": [621, 150], "anchor": "mm"}
28
+ ]
29
+ },
30
+ "caption": {
31
+ "_comment": "底部白边 2 行 caption(cells 结束于 y=1460,留 200px)",
32
+ "font": "shared/fonts/MaShanZheng-Regular.ttf",
33
+ "size": 60,
34
+ "color": "#FFB940",
35
+ "stroke_w": 5,
36
+ "stroke_color": "#D63D3D",
37
+ "lines": [
38
+ {"position": [621, 1530], "anchor": "mm"},
39
+ {"position": [621, 1610], "anchor": "mm"}
40
+ ]
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ """multigrid-poster compose — Pillow 版渲染器
3
+
4
+ 输入:layout.json + N 张 cell 图片 + 标题文字 + caption 文字
5
+ 输出:1242×1660 PNG(小红书封面标准)
6
+
7
+ 用法:
8
+ python compose.py \
9
+ --layout <SKILL_DIR>/layouts/2x2.json \
10
+ --cells cell_0.png cell_1.png cell_2.png cell_3.png \
11
+ --title-line "26岁一个人创业" \
12
+ --title-line "跨境电商月入10w+" \
13
+ --caption-line "只需要一部手机就可以完成!" \
14
+ --caption-line "跨境人的必备app推荐" \
15
+ --output cover.png
16
+
17
+ 依赖:Pillow (容器自带,无需额外安装)
18
+ 字体:从 SKILL_DIR/shared/fonts/ 加载(layout.json 里指定)
19
+ """
20
+ import argparse
21
+ import json
22
+ import sys
23
+ from pathlib import Path
24
+ from PIL import Image, ImageDraw, ImageFont
25
+
26
+
27
+ def compose(layout_path: Path, cell_paths: list[Path],
28
+ title_lines: list[str], caption_lines: list[str],
29
+ output_path: Path) -> Path:
30
+ """根据 layout.json 渲染海报。
31
+
32
+ layout.json 字段:
33
+ canvas_size [w, h]
34
+ cells.positions [[x,y], ...] cell 左上角
35
+ cells.sizes [[w,h], ...] cell 尺寸
36
+ text_zones.title {lines: [...], size, color, stroke_w, stroke_color, font}
37
+ text_zones.caption {同上}
38
+ """
39
+ layout = json.loads(layout_path.read_text(encoding="utf-8"))
40
+ # 约定:layout 必须放在 <skill>/layouts/ 下,字体路径相对 <skill>/ 解析
41
+ skill_dir = layout_path.parent.parent
42
+
43
+ # 文本行数 vs layout 配置行数:行多了 zip 会静默截断,显式 fail-fast
44
+ # 错误信息用英文 — sys.exit 走 stderr,Windows GBK locale 下中文会乱码
45
+ for zone_key, lines in [("title", title_lines), ("caption", caption_lines)]:
46
+ zone = layout.get("text_zones", {}).get(zone_key)
47
+ if not zone:
48
+ continue
49
+ max_lines = len(zone["lines"])
50
+ if len(lines) > max_lines:
51
+ sys.exit(f"too many {zone_key} lines: got {len(lines)}, layout supports {max_lines}")
52
+
53
+ # Canvas
54
+ canvas = Image.new("RGB", tuple(layout["canvas_size"]), "white")
55
+
56
+ # Cells
57
+ cell_count = len(layout["cells"]["positions"])
58
+ if len(cell_paths) != cell_count:
59
+ sys.exit(f"cell count mismatch: layout expects {cell_count}, got {len(cell_paths)}")
60
+ for cp, pos, size in zip(cell_paths,
61
+ layout["cells"]["positions"],
62
+ layout["cells"]["sizes"]):
63
+ img = Image.open(cp).convert("RGB").resize(tuple(size), Image.LANCZOS)
64
+ canvas.paste(img, tuple(pos))
65
+
66
+ draw = ImageDraw.Draw(canvas)
67
+
68
+ # Text zones (title + caption 通用绘制)
69
+ for zone_key, lines in [("title", title_lines), ("caption", caption_lines)]:
70
+ if zone_key not in layout["text_zones"]:
71
+ continue
72
+ zone = layout["text_zones"][zone_key]
73
+ font_path = skill_dir / zone["font"]
74
+ font = ImageFont.truetype(str(font_path), zone["size"])
75
+ for text, line_cfg in zip(lines, zone["lines"]):
76
+ if not text:
77
+ continue
78
+ draw.text(
79
+ tuple(line_cfg["position"]), text,
80
+ fill=zone["color"], font=font,
81
+ stroke_width=zone.get("stroke_w", 0),
82
+ stroke_fill=zone.get("stroke_color", zone["color"]),
83
+ anchor=line_cfg.get("anchor", "la"),
84
+ )
85
+
86
+ canvas.save(output_path, optimize=True)
87
+ return output_path
88
+
89
+
90
+ def main():
91
+ ap = argparse.ArgumentParser(description=__doc__,
92
+ formatter_class=argparse.RawDescriptionHelpFormatter)
93
+ ap.add_argument("--layout", required=True, type=Path,
94
+ help="layout.json 路径(如 SKILL_DIR/layouts/2x2.json)")
95
+ ap.add_argument("--cells", required=True, nargs="+", type=Path,
96
+ help="N 张 cell 图片路径,顺序对应 layout.cells.positions")
97
+ ap.add_argument("--title-line", action="append", default=[],
98
+ help="标题行(可重复)")
99
+ ap.add_argument("--caption-line", action="append", default=[],
100
+ help="底部 caption 行(可重复)")
101
+ ap.add_argument("--output", required=True, type=Path,
102
+ help="输出 PNG 路径")
103
+ args = ap.parse_args()
104
+
105
+ out = compose(
106
+ layout_path=args.layout,
107
+ cell_paths=args.cells,
108
+ title_lines=args.title_line,
109
+ caption_lines=args.caption_line,
110
+ output_path=args.output,
111
+ )
112
+ print(f"saved {out} ({out.stat().st_size // 1024} KB)")
113
+
114
+
115
+ if __name__ == "__main__":
116
+ main()