@yuhan1124/draw-prompt 0.4.1 → 0.4.3
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 +22 -3
- package/SKILL.md +2 -1
- package/package.json +1 -1
- package/scripts/prompt_cli.py +160 -6
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ Prompt 的默认结构是三段:`User visual brief` 原始视觉需求、`Styl
|
|
|
36
36
|
```bash
|
|
37
37
|
npx @yuhan1124/draw-prompt status
|
|
38
38
|
npx @yuhan1124/draw-prompt convert "茶饮新品海报,写冷泡系列"
|
|
39
|
+
npx @yuhan1124/draw-prompt install-skill --target codex
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
`npx` 包内置同一份 Python CLI 和 references。执行时优先使用 `uv run` 自动处理
|
|
@@ -44,11 +45,29 @@ PEP723 依赖;没有 `uv` 时退回 `python3`。`overlay`、`visual-check`、`
|
|
|
44
45
|
|
|
45
46
|
`visual-regress` / `benchmark` 用到的回归集只保留在开发仓库本地,不随 npm 包发布。
|
|
46
47
|
|
|
47
|
-
##
|
|
48
|
+
## 安装 skill
|
|
49
|
+
|
|
50
|
+
普通用户优先从公网 npm 安装到 agent 的 skills 目录:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 安装到 Codex:~/.codex/skills/draw-prompt
|
|
54
|
+
npx @yuhan1124/draw-prompt install-skill --target codex
|
|
55
|
+
|
|
56
|
+
# 安装到 Claude Code:~/.claude/skills/draw-prompt
|
|
57
|
+
npx @yuhan1124/draw-prompt install-skill --target claude
|
|
58
|
+
|
|
59
|
+
# 同时安装两边;更新已有安装时加 --force
|
|
60
|
+
npx @yuhan1124/draw-prompt install-skill --target both --force
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`install-skill` 默认复制 npm 包内的运行文件,避免软链到 npx 缓存导致后续路径失效。它只复制
|
|
64
|
+
`SKILL.md`、CLI、references 和必要元数据;开发仓库里的 `tests/`、`tmp/`、`golden-cases.jsonl`、
|
|
65
|
+
`visual-cases.jsonl` 不会进入 npm 包或安装目录。
|
|
66
|
+
|
|
67
|
+
开发者本地调试也可以软链 repo:
|
|
48
68
|
|
|
49
69
|
```bash
|
|
50
|
-
#
|
|
51
|
-
# 2) 软链到 Claude Code 的 skills 目录,让它被即时发现
|
|
70
|
+
# 克隆/进入本 repo(这里假设在 ~/Desktop/draw-prompt)
|
|
52
71
|
ln -s ~/Desktop/draw-prompt ~/.claude/skills/draw-prompt
|
|
53
72
|
```
|
|
54
73
|
|
package/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
画图的指令"、"优化我的出图 prompt"、"按我的风格生成 prompt",或在用 GPT Image 2 /
|
|
9
9
|
gpt-image-2 出图前需要一段精准提示词时,使用本 skill。
|
|
10
10
|
metadata:
|
|
11
|
-
version: 0.4.
|
|
11
|
+
version: 0.4.3
|
|
12
12
|
openclaw:
|
|
13
13
|
anyBins: ["uv", "python3"]
|
|
14
14
|
---
|
|
@@ -60,6 +60,7 @@ prompt」「给 Codex 出图的指令」「优化这条出图 prompt」等意图
|
|
|
60
60
|
还是"想先聊方向"。解析时先锁定用户显式目标和约束,后续所有增强都不能越过这些约束。
|
|
61
61
|
|
|
62
62
|
2. **优先自动转化,并选对入口**。能直接交付时,按场景运行对应命令:
|
|
63
|
+
- 安装到 agent skill 目录:`prompt_cli.py install-skill --target codex|claude [--force]`
|
|
63
64
|
- 单图:`prompt_cli.py convert "<自然语言画图需求>" [--style-preset premium] [--strict-text] [--out <path>] [--record-pending]`
|
|
64
65
|
- 长输入整理成多张图:`prompt_cli.py compose "<长输入>" --max-images 6 [--style-preset corporate] [--strict-text]`
|
|
65
66
|
- 同一输入多风格探索:`prompt_cli.py variants "<自然语言画图需求>" --style-presets all`
|
package/package.json
CHANGED
package/scripts/prompt_cli.py
CHANGED
|
@@ -14,6 +14,7 @@ codex exec)。本 CLI 的 `handoff` 子命令负责把 prompt 包装成一段
|
|
|
14
14
|
粘贴/转交给 Codex 的现成指令,但不会自己去执行它。
|
|
15
15
|
|
|
16
16
|
CLI 只干确定性的脏活:
|
|
17
|
+
install-skill 从 npx/npm 包安装到 Codex/Claude skills 目录
|
|
17
18
|
convert 自然语言画图需求 -> Prompt / handoff
|
|
18
19
|
compose 长输入/文档 -> 多张配套图的视觉计划 + Prompt
|
|
19
20
|
variants 同一需求 -> 多风格 Prompt 组
|
|
@@ -49,6 +50,7 @@ import hashlib
|
|
|
49
50
|
import json
|
|
50
51
|
import os
|
|
51
52
|
import re
|
|
53
|
+
import shutil
|
|
52
54
|
import sys
|
|
53
55
|
from datetime import datetime, timezone
|
|
54
56
|
from pathlib import Path
|
|
@@ -133,7 +135,27 @@ def ensure_home() -> None:
|
|
|
133
135
|
|
|
134
136
|
|
|
135
137
|
SCHEMA_VERSION = 1
|
|
136
|
-
COMPILER_VERSION = "0.4.
|
|
138
|
+
COMPILER_VERSION = "0.4.3"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
PACKAGED_SKILL_FILES = [
|
|
142
|
+
"SKILL.md",
|
|
143
|
+
"README.md",
|
|
144
|
+
"LICENSE",
|
|
145
|
+
"package.json",
|
|
146
|
+
"agents/openai.yaml",
|
|
147
|
+
"bin/draw-prompt.js",
|
|
148
|
+
"scripts/prompt_cli.py",
|
|
149
|
+
"references/codex-handoff.md",
|
|
150
|
+
"references/compile.md",
|
|
151
|
+
"references/conversion-skill-plan.md",
|
|
152
|
+
"references/gallery.md",
|
|
153
|
+
"references/harness.md",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def package_root() -> Path:
|
|
158
|
+
return Path(__file__).resolve().parent.parent
|
|
137
159
|
|
|
138
160
|
|
|
139
161
|
# --------------------------------------------------------------------------- #
|
|
@@ -2076,6 +2098,7 @@ DEFAULT_VARIANT_PRESETS = ["corporate", "premium", "minimal", "flat-vector", "ph
|
|
|
2076
2098
|
|
|
2077
2099
|
ASPECT_SIZE = {
|
|
2078
2100
|
"3:4": "portrait",
|
|
2101
|
+
"4:5": "portrait",
|
|
2079
2102
|
"4:3": "landscape",
|
|
2080
2103
|
"16:9": "landscape",
|
|
2081
2104
|
"9:16": "portrait",
|
|
@@ -2095,7 +2118,7 @@ TEMPLATE_DEFS = {
|
|
|
2095
2118
|
"asset_type": "poster",
|
|
2096
2119
|
"label": "中文促销海报",
|
|
2097
2120
|
"layout": "large headline zone, hero product zone, price/offer block, quiet footer",
|
|
2098
|
-
"keywords": ["促销", "新品", "价格", "优惠", "茶饮", "冷泡"
|
|
2121
|
+
"keywords": ["促销", "新品", "价格", "优惠", "茶饮", "冷泡"],
|
|
2099
2122
|
},
|
|
2100
2123
|
"poster_brand_kv": {
|
|
2101
2124
|
"asset_type": "poster",
|
|
@@ -2107,7 +2130,7 @@ TEMPLATE_DEFS = {
|
|
|
2107
2130
|
"asset_type": "poster",
|
|
2108
2131
|
"label": "活动海报",
|
|
2109
2132
|
"layout": "title, date/venue, speaker or theme block, organizer footer",
|
|
2110
|
-
"keywords": ["活动", "会议", "展览", "event", "workshop", "讲座"],
|
|
2133
|
+
"keywords": ["活动", "会议", "发布会", "展览", "event", "workshop", "讲座"],
|
|
2111
2134
|
},
|
|
2112
2135
|
"poster_info_dense": {
|
|
2113
2136
|
"asset_type": "poster",
|
|
@@ -2421,11 +2444,17 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
2421
2444
|
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2422
2445
|
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2423
2446
|
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2447
|
+
text = re.sub(r"^(?:问候语|核心卡片|主要按钮|主按钮|按钮)\s+", "", text)
|
|
2448
|
+
text = re.sub(r"^(?:是|为|叫)\s+", "", text)
|
|
2424
2449
|
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2425
2450
|
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2426
2451
|
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2427
2452
|
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2428
2453
|
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2454
|
+
if re.search(r"箭头.*关系|关系.*箭头|展示输入输出关系", text):
|
|
2455
|
+
return ""
|
|
2456
|
+
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2457
|
+
return ""
|
|
2429
2458
|
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2430
2459
|
return ""
|
|
2431
2460
|
return text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
@@ -2458,6 +2487,31 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
2458
2487
|
for pat in patterns:
|
|
2459
2488
|
for match in re.finditer(pat, request):
|
|
2460
2489
|
add_candidate(match.start(1), match.group(1))
|
|
2490
|
+
labeled_single_patterns = [
|
|
2491
|
+
r"(?:主标题|标题|副标题|主题|时间|地点)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,、。;;\n]{2,40})",
|
|
2492
|
+
r"(?:主标题|标题|副标题|主题|时间|地点)\s+([^,,、。;;\n]{2,40})",
|
|
2493
|
+
r"(?:时间|地点)\s+([^,,、。;;\n]{2,40})",
|
|
2494
|
+
r"(?:核心卡片|主要按钮|主按钮|按钮)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,。;;\n]{2,30})",
|
|
2495
|
+
r"(?:需要)?包含\s*([A-Za-z][A-Za-z0-9_-]{2,30})\s*字样",
|
|
2496
|
+
r"(?:名为|叫做|名称是|名字叫)\s*([A-Za-z][A-Za-z0-9_-]{2,30})\b",
|
|
2497
|
+
]
|
|
2498
|
+
for pat in labeled_single_patterns:
|
|
2499
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2500
|
+
add_candidate(match.start(1), match.group(1))
|
|
2501
|
+
for match in re.finditer(
|
|
2502
|
+
r"问候语\s*(?:写上|写|显示|为|是|叫|[::])?\s*(.+?)(?=,(?:核心|下方|上方|主要|页面|风格)|[。;;\n]|$)",
|
|
2503
|
+
request,
|
|
2504
|
+
flags=re.IGNORECASE,
|
|
2505
|
+
):
|
|
2506
|
+
add_candidate(match.start(1), match.group(1))
|
|
2507
|
+
for match in re.finditer(
|
|
2508
|
+
r"(?:需要)?(?:出现|展示|包含|包括)?(?:文案|文字)\s*(?:写上|写|显示|为|是|[::])\s*([^。;;\n]{2,100})",
|
|
2509
|
+
request,
|
|
2510
|
+
flags=re.IGNORECASE,
|
|
2511
|
+
):
|
|
2512
|
+
value = match.group(1)
|
|
2513
|
+
for part_match in re.finditer(r"[^、;;/|]+", value):
|
|
2514
|
+
add_candidate(match.start(1) + part_match.start(), part_match.group(0))
|
|
2461
2515
|
title_patterns = [
|
|
2462
2516
|
r"\btitle\s*[::]\s*([^,,。.;;\n]{1,40})",
|
|
2463
2517
|
r"(?:Chinese\s+title|title\s+in\s+Chinese)\s*[::]\s*(.+?)(?=\s+(?:Bullet\s+points?|Icon|Column\s+\d+|Bottom\s+area|High\s+contrast)\b|[,,。.;;\n]|$)",
|
|
@@ -2475,8 +2529,8 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
2475
2529
|
value = match.group(1)
|
|
2476
2530
|
for part_match in re.finditer(r"[^,,、;;]+", value):
|
|
2477
2531
|
add_candidate(match.start(1) + part_match.start(), part_match.group(0))
|
|
2478
|
-
text_hint = r"(
|
|
2479
|
-
for match in re.finditer(rf"{text_hint}[::\s]*(
|
|
2532
|
+
text_hint = r"(?:写上|写|显示|品牌名|wordmark|(?<!副)标题)"
|
|
2533
|
+
for match in re.finditer(rf"{text_hint}[::\s]*(?:写上|写|显示|为|是|叫)?[::\s]*([^,,、。;;,.]{{2,30}})", request, flags=re.IGNORECASE):
|
|
2480
2534
|
add_candidate(match.start(1), match.group(1))
|
|
2481
2535
|
for match in re.finditer(r"(?:文字|文案)\s*(?:写上|写|显示|为|是|[::])\s*([^,,、。;;,.]{2,24})", request, flags=re.IGNORECASE):
|
|
2482
2536
|
add_candidate(match.start(1), match.group(1))
|
|
@@ -2495,12 +2549,18 @@ def merge_texts(primary: list[str], extra: list[str]) -> list[str]:
|
|
|
2495
2549
|
text = item.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2496
2550
|
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2497
2551
|
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2552
|
+
text = re.sub(r"^(?:问候语|核心卡片|主要按钮|主按钮|按钮)\s+", "", text)
|
|
2553
|
+
text = re.sub(r"^(?:是|为|叫)\s+", "", text)
|
|
2498
2554
|
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2499
2555
|
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2500
2556
|
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2501
2557
|
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2502
2558
|
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2503
2559
|
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2560
|
+
if re.search(r"箭头.*关系|关系.*箭头|展示输入输出关系", text):
|
|
2561
|
+
continue
|
|
2562
|
+
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2563
|
+
continue
|
|
2504
2564
|
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2505
2565
|
continue
|
|
2506
2566
|
key = re.sub(r"\s+", "", text)
|
|
@@ -2622,6 +2682,8 @@ def infer_template_id(request: str, asset_type: str, override: str | None = None
|
|
|
2622
2682
|
if asset_type == "poster":
|
|
2623
2683
|
if any(keyword_in_text(kw, lower) for kw in ["小红书", "社媒", "方图", "封面", "cover", "social"]):
|
|
2624
2684
|
return "poster_social_cover"
|
|
2685
|
+
if any(keyword_in_text(kw, lower) for kw in ["活动", "会议", "发布会", "展览", "event", "workshop", "讲座"]):
|
|
2686
|
+
return "poster_event"
|
|
2625
2687
|
if any(keyword_in_text(kw, lower) for kw in ["品牌", "主视觉", "kv", "brand key visual"]):
|
|
2626
2688
|
return "poster_brand_kv"
|
|
2627
2689
|
if any(keyword_in_text(kw, lower) for kw in ["信息", "清单", "流程", "说明"]):
|
|
@@ -3220,7 +3282,7 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
3220
3282
|
lower = compact.lower()
|
|
3221
3283
|
if len(compact) < 80:
|
|
3222
3284
|
add("error", "prompt.too_short", "Prompt 太短,缺少可执行的视觉约束。")
|
|
3223
|
-
if not re.search(r"
|
|
3285
|
+
if not re.search(r"(?<!\d)(?:3:4|4:5|4:3|16:9|9:16|1:1)(?!\d)|\b(?:portrait|landscape|square)\b", lower):
|
|
3224
3286
|
add("error", "prompt.missing_aspect", "Prompt 缺少画幅/宽高比/制品类型开场。")
|
|
3225
3287
|
if not re.search(r"\b(avoid|no |without|不要|避免)\b", lower):
|
|
3226
3288
|
add("warning", "prompt.missing_negative", "Prompt 缺少针对常见失败模式的否定项。")
|
|
@@ -5425,6 +5487,86 @@ def cmd_adapt(args: argparse.Namespace) -> int:
|
|
|
5425
5487
|
return 1 if any(has_lint_error(item["lint"]) for item in variants) else 0
|
|
5426
5488
|
|
|
5427
5489
|
|
|
5490
|
+
def default_skill_parent(target: str) -> Path:
|
|
5491
|
+
if target == "claude":
|
|
5492
|
+
return Path.home() / ".claude" / "skills"
|
|
5493
|
+
return Path.home() / ".codex" / "skills"
|
|
5494
|
+
|
|
5495
|
+
|
|
5496
|
+
def is_draw_prompt_install(path: Path) -> bool:
|
|
5497
|
+
skill = path / "SKILL.md"
|
|
5498
|
+
if not skill.exists():
|
|
5499
|
+
return False
|
|
5500
|
+
try:
|
|
5501
|
+
head = skill.read_text(encoding="utf-8")[:500]
|
|
5502
|
+
except OSError:
|
|
5503
|
+
return False
|
|
5504
|
+
return "name: draw-prompt" in head
|
|
5505
|
+
|
|
5506
|
+
|
|
5507
|
+
def copy_packaged_skill(src_root: Path, dst: Path) -> list[str]:
|
|
5508
|
+
copied = []
|
|
5509
|
+
for rel in PACKAGED_SKILL_FILES:
|
|
5510
|
+
src = src_root / rel
|
|
5511
|
+
if not src.exists():
|
|
5512
|
+
raise FileNotFoundError(f"缺少发布文件:{src}")
|
|
5513
|
+
target = dst / rel
|
|
5514
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
5515
|
+
shutil.copy2(src, target)
|
|
5516
|
+
copied.append(rel)
|
|
5517
|
+
return copied
|
|
5518
|
+
|
|
5519
|
+
|
|
5520
|
+
def install_skill_to_path(dst: Path, mode: str, force: bool) -> dict:
|
|
5521
|
+
src_root = package_root()
|
|
5522
|
+
dst = dst.expanduser()
|
|
5523
|
+
if dst.exists() or dst.is_symlink():
|
|
5524
|
+
if not force:
|
|
5525
|
+
if is_draw_prompt_install(dst):
|
|
5526
|
+
return {
|
|
5527
|
+
"path": str(dst),
|
|
5528
|
+
"status": "exists",
|
|
5529
|
+
"message": "draw-prompt skill 已存在;如需更新请加 --force。",
|
|
5530
|
+
"mode": mode,
|
|
5531
|
+
}
|
|
5532
|
+
raise FileExistsError(f"目标路径已存在且不像 draw-prompt skill:{dst}。如确认覆盖,请加 --force。")
|
|
5533
|
+
if dst.is_symlink() or dst.is_file():
|
|
5534
|
+
dst.unlink()
|
|
5535
|
+
else:
|
|
5536
|
+
shutil.rmtree(dst)
|
|
5537
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
5538
|
+
if mode == "symlink":
|
|
5539
|
+
dst.symlink_to(src_root, target_is_directory=True)
|
|
5540
|
+
copied = []
|
|
5541
|
+
else:
|
|
5542
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
5543
|
+
copied = copy_packaged_skill(src_root, dst)
|
|
5544
|
+
return {"path": str(dst), "status": "installed", "mode": mode, "files": copied, "source": str(src_root)}
|
|
5545
|
+
|
|
5546
|
+
|
|
5547
|
+
def cmd_install_skill(args: argparse.Namespace) -> int:
|
|
5548
|
+
targets = ["codex", "claude"] if args.target == "both" else [args.target]
|
|
5549
|
+
if args.path and len(targets) > 1:
|
|
5550
|
+
print("--path 只能和单个 --target 一起使用,不能用于 --target both。", file=sys.stderr)
|
|
5551
|
+
return 2
|
|
5552
|
+
results = []
|
|
5553
|
+
try:
|
|
5554
|
+
for target in targets:
|
|
5555
|
+
dst = Path(args.path).expanduser() if args.path else default_skill_parent(target) / "draw-prompt"
|
|
5556
|
+
results.append({"target": target, **install_skill_to_path(dst, args.mode, args.force)})
|
|
5557
|
+
except Exception as exc:
|
|
5558
|
+
print(f"install-skill 失败:{exc}", file=sys.stderr)
|
|
5559
|
+
return 2
|
|
5560
|
+
if args.json:
|
|
5561
|
+
print(json.dumps({"results": results}, ensure_ascii=False, indent=2))
|
|
5562
|
+
else:
|
|
5563
|
+
for item in results:
|
|
5564
|
+
print(f"{item['target']}: {item['status']} {item['path']} ({item['mode']})")
|
|
5565
|
+
if item.get("message"):
|
|
5566
|
+
print(f" {item['message']}")
|
|
5567
|
+
return 0
|
|
5568
|
+
|
|
5569
|
+
|
|
5428
5570
|
# --------------------------------------------------------------------------- #
|
|
5429
5571
|
# status
|
|
5430
5572
|
# --------------------------------------------------------------------------- #
|
|
@@ -5440,6 +5582,10 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|
|
5440
5582
|
print(f" 自带范例库 : {own} ({'可用' if own.exists() else '缺失!'})")
|
|
5441
5583
|
gi = Path.home() / ".claude" / "skills" / "gpt-image" / "references" / "gallery.md"
|
|
5442
5584
|
print(f" 可选扩展库 : gpt-image ({'可用' if gi.exists() else '未装(不影响)'})")
|
|
5585
|
+
codex_skill = Path.home() / ".codex" / "skills" / "draw-prompt"
|
|
5586
|
+
claude_skill = Path.home() / ".claude" / "skills" / "draw-prompt"
|
|
5587
|
+
print(f" Codex skill: {codex_skill} ({'已安装' if is_draw_prompt_install(codex_skill) else '未安装,可运行 install-skill --target codex'})")
|
|
5588
|
+
print(f" Claude skill: {claude_skill} ({'已安装' if is_draw_prompt_install(claude_skill) else '未安装,可运行 install-skill --target claude'})")
|
|
5443
5589
|
print(" ── 下游出图通道(本 skill 不主动调用,仅提示可用性)──")
|
|
5444
5590
|
print(f" codex CLI : {which('codex') or '未找到'}")
|
|
5445
5591
|
plugin = Path.home() / ".claude" / "plugins" / "cache" / "codex-image-in-cc"
|
|
@@ -5482,6 +5628,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5482
5628
|
p = argparse.ArgumentParser(prog="prompt_cli.py", description="draw-prompt:自然语言画图需求 -> 生图 Prompt / handoff(不主动出图)")
|
|
5483
5629
|
sub = p.add_subparsers(dest="cmd", required=True)
|
|
5484
5630
|
|
|
5631
|
+
pis = sub.add_parser("install-skill", help="把当前包安装到 Codex/Claude skills 目录")
|
|
5632
|
+
pis.add_argument("--target", choices=["codex", "claude", "both"], default="codex", help="默认安装到 ~/.codex/skills/draw-prompt")
|
|
5633
|
+
pis.add_argument("--path", help="覆盖安装目标路径;只能和单个 target 一起用")
|
|
5634
|
+
pis.add_argument("--mode", choices=["copy", "symlink"], default="copy", help="默认复制发布文件,避免 npx 缓存路径失效")
|
|
5635
|
+
pis.add_argument("--force", action="store_true", help="覆盖已有 draw-prompt skill")
|
|
5636
|
+
pis.add_argument("--json", action="store_true")
|
|
5637
|
+
pis.set_defaults(func=cmd_install_skill)
|
|
5638
|
+
|
|
5485
5639
|
pc = sub.add_parser("convert", help="自然语言画图需求 -> 高质量生图 Prompt / handoff")
|
|
5486
5640
|
pc.add_argument("request_text", nargs="+", help="自然语言画图需求")
|
|
5487
5641
|
pc.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖自动识别的资产类型")
|