mirror of
https://github.com/luoluoluo22/jianying-editor-skill.git
synced 2026-09-19 07:30:30 +08:00
Improve agent workflow docs, add project governance files, and harden examples/bootstrap
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml,json,toml}]
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,27 @@
|
||||
## Summary
|
||||
|
||||
- What changed?
|
||||
- Why is this needed?
|
||||
|
||||
## Scope
|
||||
|
||||
- Affected modules/files:
|
||||
- Breaking changes: yes/no
|
||||
|
||||
## Validation
|
||||
|
||||
- [ ] `ruff check scripts tests tools`
|
||||
- [ ] `black --check scripts tests tools`
|
||||
- [ ] `python -m pytest tests/test_wrapper.py -q`
|
||||
- [ ] `python tools/check_repo_hygiene.py`
|
||||
- [ ] `python tools/validate_data_schema.py`
|
||||
|
||||
## Risk and Rollback
|
||||
|
||||
- Risk level:
|
||||
- Rollback plan:
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Docs updated (`SKILL.md` / `rules/` / `docs/api.md`) if API/behavior changed
|
||||
- [ ] No runtime artifacts committed
|
||||
@@ -0,0 +1,20 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.2
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pytest-wrapper
|
||||
name: pytest (wrapper quick suite)
|
||||
entry: python -m pytest tests/test_wrapper.py -q
|
||||
language: system
|
||||
pass_filenames: false
|
||||
@@ -0,0 +1,37 @@
|
||||
# Contributing
|
||||
|
||||
## Branching
|
||||
|
||||
- Use feature branches for all changes.
|
||||
- Keep `main` releasable.
|
||||
- Prefer small, focused pull requests.
|
||||
|
||||
## Local Setup
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install pytest ruff black pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## Required Checks
|
||||
|
||||
```bash
|
||||
ruff check scripts tests tools
|
||||
black --check scripts tests tools
|
||||
python -m pytest tests/test_wrapper.py -q
|
||||
python tools/check_repo_hygiene.py
|
||||
python tools/validate_data_schema.py
|
||||
```
|
||||
|
||||
## Commit Rules
|
||||
|
||||
- Use clear commit messages with intent and scope.
|
||||
- Include docs updates when behavior or APIs change.
|
||||
- Do not commit runtime artifacts (`cloud_cache`, `__pycache__`, logs).
|
||||
|
||||
## Pull Request Rules
|
||||
|
||||
- Describe user impact and migration risk.
|
||||
- Add concrete verification steps.
|
||||
- Include before/after behavior for API-affecting changes.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 luoluoluo22
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are provided for the latest `main` branch.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report vulnerabilities privately by opening a GitHub Security Advisory in this repository.
|
||||
|
||||
Include:
|
||||
|
||||
- Affected component or file path
|
||||
- Reproduction steps / proof of concept
|
||||
- Impact assessment
|
||||
- Suggested remediation (if available)
|
||||
|
||||
Do not disclose details publicly until a fix is available.
|
||||
|
||||
## Response Targets
|
||||
|
||||
- Initial triage: within 72 hours
|
||||
- Mitigation plan: within 7 days for confirmed issues
|
||||
@@ -8,6 +8,8 @@ description: 剪映 (JianYing) AI自动化剪辑的高级封装 API (JyWrapper)
|
||||
Use this skill when the user wants to automate video editing, generate drafts, or manipulate media assets in JianYing Pro.
|
||||
|
||||
Agent execution playbook: [docs/agent-playbook.md](docs/agent-playbook.md)
|
||||
Minimal command SOP: [docs/minimal-command-sop.md](docs/minimal-command-sop.md)
|
||||
For generic editing requests, always follow the "Quick Edit Runtime Template" and "Acceptance Checklist" in that playbook.
|
||||
|
||||
## 🚨 重要开发原则 (CRITICAL DEVELOPER RULES)
|
||||
1. **脚本位置**:**禁止在 Skill 内部目录创建剪辑脚本**。所有的剪辑逻辑实现代码(`.py` 脚本)必须存放在用户当前项目的**根目录**(或子目录,如 `scripts/`),以保持 Skill 库的纯净和可移植性。
|
||||
@@ -98,13 +100,19 @@ import sys
|
||||
|
||||
# 1. 环境初始化 (必须同步到脚本开头)
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
env_root = os.getenv("JY_SKILL_ROOT", "").strip()
|
||||
# 探测 Skill 路径 (支持 Antigravity, Trae, Claude 等)
|
||||
skill_root = next((p for p in [
|
||||
env_root,
|
||||
os.path.join(current_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".claude", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, "skills", "jianying-editor"),
|
||||
os.path.abspath(".agent/skills/jianying-editor"),
|
||||
os.path.abspath(".trae/skills/jianying-editor"),
|
||||
os.path.abspath(".claude/skills/jianying-editor"),
|
||||
os.path.dirname(current_dir) # 如果在 examples/ 目录下
|
||||
] if os.path.exists(os.path.join(p, "scripts", "jy_wrapper.py"))), None)
|
||||
] if p and os.path.exists(os.path.join(p, "scripts", "jy_wrapper.py"))), None)
|
||||
|
||||
if not skill_root: raise ImportError("Could not find jianying-editor skill root.")
|
||||
sys.path.insert(0, os.path.join(skill_root, "scripts"))
|
||||
|
||||
@@ -2,6 +2,31 @@
|
||||
|
||||
Task routing matrix for reliable execution.
|
||||
|
||||
## 0) Quick Edit Runtime Template (Default for "来个剪辑")
|
||||
|
||||
Use this fixed sequence:
|
||||
|
||||
1. Environment check (minimal):
|
||||
- verify Python works
|
||||
- verify draft root exists
|
||||
2. Asset resolution:
|
||||
- use explicit local/cloud assets
|
||||
- only call `asset_search.py` when user asks for style/effect lookup
|
||||
3. Script assembly:
|
||||
- generate one runnable script with deterministic APIs
|
||||
4. Execute once:
|
||||
- run script and capture success/failure output
|
||||
5. Acceptance check:
|
||||
- validate draft and track structure (see checklist below)
|
||||
|
||||
### Acceptance Checklist (Mandatory)
|
||||
|
||||
- Draft folder exists under JianYing drafts root
|
||||
- `project.save()` completed successfully
|
||||
- At least one `video` track segment exists
|
||||
- If BGM exists, it must be on `audio` track (not `video`)
|
||||
- If narration exists, subtitle segments should exist and be time-aligned
|
||||
|
||||
## 1) Cloud Video + BGM Draft
|
||||
|
||||
- Read: `rules/setup.md`, `rules/media.md`, `rules/audio-voice.md`
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@ Bootstrap:
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, r"<SKILL_ROOT>\\scripts")
|
||||
skill_root = os.getenv("JY_SKILL_ROOT", r"<SKILL_ROOT>")
|
||||
sys.path.insert(0, os.path.join(skill_root, "scripts"))
|
||||
from jy_wrapper import JyProject
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Minimal Command SOP
|
||||
|
||||
Use this SOP for simple editing requests to minimize noisy exploration.
|
||||
|
||||
## Goal
|
||||
|
||||
- Produce one runnable script
|
||||
- Run once
|
||||
- Validate result with fixed checks
|
||||
|
||||
## Step 1: Minimal Environment Check (2 commands max)
|
||||
|
||||
```powershell
|
||||
python --version
|
||||
Test-Path "C:\Users\Administrator\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft"
|
||||
```
|
||||
|
||||
If draft path differs, set `JY_PROJECTS_ROOT` and continue.
|
||||
|
||||
## Step 2: Asset Check (1 command)
|
||||
|
||||
```powershell
|
||||
Get-ChildItem .agent\skills\jianying-editor\assets -File
|
||||
```
|
||||
|
||||
Do not recursively scan the whole workspace unless asset lookup fails.
|
||||
|
||||
## Step 3: Script Generation
|
||||
|
||||
- Create exactly one script in workspace root, e.g. `simple_edit.py`.
|
||||
- Use deterministic APIs only:
|
||||
- `JyProject(...)`
|
||||
- `add_media_safe(...)`
|
||||
- `add_cloud_media(...)` or `add_cloud_music(...)`
|
||||
- `add_text_simple(...)` / `add_narrated_subtitles(...)`
|
||||
- `save()`
|
||||
|
||||
## Step 4: Single Execution
|
||||
|
||||
```powershell
|
||||
python simple_edit.py
|
||||
```
|
||||
|
||||
If failed, patch script once based on the concrete error, then rerun once.
|
||||
|
||||
## Step 5: Acceptance Validation (Mandatory)
|
||||
|
||||
Verify all:
|
||||
|
||||
- Draft directory exists
|
||||
- Save success log appears
|
||||
- At least one video segment exists
|
||||
- BGM is on audio track (if used)
|
||||
- Subtitles exist when narration exists
|
||||
|
||||
## Error Handling Rules
|
||||
|
||||
- `SegmentOverlap`: move clip start to track end or separate track.
|
||||
- Missing asset: switch to known local asset or valid cloud ID.
|
||||
- Import/path failure: set `JY_SKILL_ROOT`, rerun bootstrap.
|
||||
|
||||
## Anti-Patterns (Avoid)
|
||||
|
||||
- Repeated `Get-ChildItem -Recurse` over full workspace
|
||||
- Temporary introspection files (`*_dir.txt`, `output.txt`) unless debugging is explicitly requested
|
||||
- Multiple style/effect searches before first successful draft generation
|
||||
@@ -1,91 +1,64 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Standard Boilerplate to load the skill wrapper
|
||||
# -------------------------------------------------------------
|
||||
# 必选:这段代码负责自动定位 Skill 路径,请原样复制到你的脚本开头
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 扩展探测逻辑:覆盖 Antigravity(.agent), Trae(.trae), Claude(.claude), 以及通用(skills)
|
||||
skill_candidates = [
|
||||
os.path.join(current_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".claude", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, "jianying-editor-skill", ".agent", "skills", "jianying-editor"),
|
||||
os.path.abspath(".agent/skills/jianying-editor"),
|
||||
# Special case for examples folder (up one level)
|
||||
os.path.join(os.path.dirname(current_dir))
|
||||
]
|
||||
wrapper_path = None
|
||||
for p in skill_candidates:
|
||||
if os.path.exists(os.path.join(p, "scripts", "jy_wrapper.py")):
|
||||
wrapper_path = os.path.join(p, "scripts")
|
||||
break
|
||||
|
||||
if wrapper_path and wrapper_path not in sys.path:
|
||||
sys.path.insert(0, wrapper_path)
|
||||
def resolve_wrapper_path() -> str:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
env_root = os.getenv("JY_SKILL_ROOT", "").strip()
|
||||
candidates = [
|
||||
env_root,
|
||||
os.path.join(current_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".claude", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, "skills", "jianying-editor"),
|
||||
os.path.abspath(".agent/skills/jianying-editor"),
|
||||
os.path.abspath(".trae/skills/jianying-editor"),
|
||||
os.path.abspath(".claude/skills/jianying-editor"),
|
||||
os.path.dirname(current_dir), # examples/ under skill root
|
||||
]
|
||||
for p in candidates:
|
||||
if not p:
|
||||
continue
|
||||
scripts_dir = os.path.join(os.path.abspath(p), "scripts")
|
||||
if os.path.exists(os.path.join(scripts_dir, "jy_wrapper.py")):
|
||||
return scripts_dir
|
||||
raise ImportError("Could not find jianying-editor/scripts/jy_wrapper.py")
|
||||
|
||||
try:
|
||||
from jy_wrapper import JyProject
|
||||
print(f"✅ Successfully loaded JyProject from: {wrapper_path}") # Added print for success
|
||||
except ImportError:
|
||||
# 这一步是为了在找不到路径时给出明确提示,方便调试
|
||||
print("❌ Critical Error: Could not load 'jy_wrapper'. Check skill paths.")
|
||||
sys.exit(1)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# ==============================================================================
|
||||
# 🎬 简单剪辑示例 (Simple Clip Demo)
|
||||
# ==============================================================================
|
||||
WRAPPER_PATH = resolve_wrapper_path()
|
||||
if WRAPPER_PATH not in sys.path:
|
||||
sys.path.insert(0, WRAPPER_PATH)
|
||||
|
||||
def main():
|
||||
# 1. 初始化项目
|
||||
# project_name: 剪映草稿的名字
|
||||
# overwrite=True: 如果项目已存在,允许覆盖(谨慎使用)
|
||||
from jy_wrapper import JyProject
|
||||
|
||||
|
||||
def main() -> None:
|
||||
project = JyProject(project_name="Hello_JianYing_V3", overwrite=True)
|
||||
|
||||
# 2. 准备素材路径 (这里使用 Skill 自带的测试素材)
|
||||
# wrapper_path是指向 scripts 目录的,它的上一级通常是 skill root
|
||||
if 'wrapper_path' in globals() and wrapper_path:
|
||||
skill_root = os.path.dirname(wrapper_path)
|
||||
else:
|
||||
# Fallback if wrapper_path somehow isn't set (shouldn't happen with new boilerplate)
|
||||
skill_root = os.path.abspath(os.path.join(current_dir, ".."))
|
||||
|
||||
skill_root = os.path.dirname(WRAPPER_PATH)
|
||||
assets_dir = os.path.join(skill_root, "assets")
|
||||
video_path = os.path.join(assets_dir, "video.mp4")
|
||||
bgm_path = os.path.join(assets_dir, "audio.mp3")
|
||||
|
||||
if not os.path.exists(video_path):
|
||||
print(f"⚠️ Demo assets not found at {assets_dir}, using placeholders.")
|
||||
# 如果你运行此脚本时没有这些文件,请替换为你本地的真实路径
|
||||
if not os.path.exists(video_path) or not os.path.exists(bgm_path):
|
||||
print(f"Demo assets not found: {assets_dir}")
|
||||
return
|
||||
|
||||
# 3. 添加主视频轨道
|
||||
# add_media_safe 会自动识别文件类型
|
||||
print("📥 Importing Video...")
|
||||
project.add_media_safe(video_path, start_time=0, duration="5s")
|
||||
print("Importing video...")
|
||||
project.add_media_safe(video_path, start_time="0s", duration="5s", track_name="VideoTrack")
|
||||
|
||||
# 4. 添加背景音乐
|
||||
# track_name="Audio": 指定放入音频轨道
|
||||
print("🎵 Adding Music...")
|
||||
project.add_media_safe(bgm_path, start_time=0, duration="5s", track_name="Audio")
|
||||
print("Adding bgm...")
|
||||
project.add_media_safe(bgm_path, start_time="0s", duration="5s", track_name="AudioTrack")
|
||||
|
||||
# 5. 添加字幕 (带入场动画)
|
||||
# transform_y: 垂直位置,-1.0 是底部,1.0 是顶部,0 是中间
|
||||
# anim_in: 入场动画。支持直接使用中文名称 (如 "复古打字机", "弹入", "向右滑动")
|
||||
# 提示: 你可以使用 `python scripts/asset_search.py "打字"` 来查找可用的动画名
|
||||
print("📝 Adding Text with Animation...")
|
||||
project.add_text_simple("Hello JianYing API!", start_time="1s", duration="3s",
|
||||
transform_y=-0.7, color_rgb=(1, 1, 0), # 黄色字幕
|
||||
anim_in="复古打字机")
|
||||
print("Adding text...")
|
||||
# Keep text clips non-overlapping on the same text track to avoid SegmentOverlap.
|
||||
project.add_text_simple("Hello JianYing API!", start_time="1s", duration="1.6s", anim_in="复古打字机")
|
||||
project.add_text_simple("Simple Clip Demo", start_time="2.7s", duration="1.6s", anim_in="向右滑动")
|
||||
|
||||
|
||||
# 6. 保存项目
|
||||
# 这会生成草稿文件并自动刷新剪映首页列表
|
||||
print("💾 Saving Project...")
|
||||
print("Saving project...")
|
||||
project.save()
|
||||
|
||||
print("\n✨ Done! Open JianYing (剪映) and look for 'Hello_JianYing_V3'.")
|
||||
print("Done. Open JianYing and find draft: Hello_JianYing_V3")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -67,3 +67,23 @@ python <SKILL_ROOT>/scripts/auto_exporter.py "ProjectName" "custom_output.mp4" -
|
||||
- **Draft Recognition**: The wrapper automatically handles `DraftFolder` structure. Do not manually manipulate `draft_content.json` unless you know exactly what you are doing.
|
||||
- **Exporting Requirements**: Auto-exporting only works on **Windows** with **Jianying v5.9 or lower**. It relies on `uiautomation` to interact with the UI.
|
||||
- **UI Refresh**: After the script runs, if Jianying is open, the user may need to exit and re-enter the draft to see changes.
|
||||
|
||||
## Quick Edit Execution Template (Standard)
|
||||
|
||||
For generic requests like "来个剪辑", execute in this order:
|
||||
|
||||
1. Minimal environment checks (python + drafts root)
|
||||
2. Resolve required assets (local first, cloud second)
|
||||
3. Generate one deterministic edit script
|
||||
4. Run script once and collect output
|
||||
5. Perform acceptance checks and report concrete results
|
||||
|
||||
## Acceptance Checks (Standard)
|
||||
|
||||
After execution, verify:
|
||||
|
||||
- Draft directory exists
|
||||
- Save completed (`project.save()` success)
|
||||
- At least one segment exists on a video track
|
||||
- BGM (if used) is on audio track
|
||||
- Narration/subtitle pairing exists when TTS was requested
|
||||
|
||||
+13
-1
@@ -8,6 +8,7 @@ metadata:
|
||||
# Environment Initialization
|
||||
|
||||
Use a single reliable bootstrap path. Do not assume helper files outside this repo.
|
||||
Always support multiple editor layouts and allow explicit override.
|
||||
|
||||
## Recommended Bootstrap (Mandatory)
|
||||
|
||||
@@ -16,7 +17,9 @@ import os
|
||||
import sys
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
env_root = os.getenv("JY_SKILL_ROOT", "").strip()
|
||||
skill_candidates = [
|
||||
env_root,
|
||||
os.path.join(current_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(current_dir, ".claude", "skills", "jianying-editor"),
|
||||
@@ -26,13 +29,21 @@ skill_candidates = [
|
||||
]
|
||||
|
||||
scripts_path = None
|
||||
attempted = []
|
||||
for p in skill_candidates:
|
||||
if not p:
|
||||
continue
|
||||
p = os.path.abspath(p)
|
||||
attempted.append(p)
|
||||
if os.path.exists(os.path.join(p, "scripts", "jy_wrapper.py")):
|
||||
scripts_path = os.path.join(p, "scripts")
|
||||
break
|
||||
|
||||
if not scripts_path:
|
||||
raise ImportError("Could not find jianying-editor/scripts/jy_wrapper.py")
|
||||
raise ImportError(
|
||||
"Could not find jianying-editor/scripts/jy_wrapper.py\\nTried:\\n- "
|
||||
+ "\\n- ".join(attempted)
|
||||
)
|
||||
|
||||
if scripts_path not in sys.path:
|
||||
sys.path.insert(0, scripts_path)
|
||||
@@ -43,5 +54,6 @@ from jy_wrapper import JyProject
|
||||
## Notes
|
||||
|
||||
- Do not use `from jy import JyProject` unless your project explicitly provides `jy.py`.
|
||||
- Prefer setting `JY_SKILL_ROOT` in mixed editor environments.
|
||||
- Put business scripts in the user workspace, not inside the skill repo.
|
||||
- Always call `project.save()` at the end.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
from utils.skill_path import resolve_skill_root
|
||||
|
||||
def setup_env():
|
||||
"""
|
||||
@@ -16,27 +17,7 @@ def setup_env():
|
||||
except Exception:
|
||||
start_dir = os.getcwd()
|
||||
|
||||
skill_root = None
|
||||
|
||||
possible_roots = [
|
||||
start_dir,
|
||||
os.path.join(start_dir, ".."),
|
||||
os.path.join(start_dir, "..", ".."),
|
||||
os.path.join(start_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(os.getcwd(), ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(os.getcwd(), "skills", "jianying-editor"),
|
||||
]
|
||||
|
||||
for p in possible_roots:
|
||||
p = os.path.abspath(p)
|
||||
if os.path.exists(os.path.join(p, "scripts", "jy_wrapper.py")):
|
||||
skill_root = p
|
||||
break
|
||||
|
||||
if not skill_root:
|
||||
fallback_root = os.path.join(os.getcwd(), ".agent", "skills", "jianying-editor")
|
||||
if os.path.exists(fallback_root):
|
||||
skill_root = fallback_root
|
||||
skill_root, _ = resolve_skill_root(start_dir)
|
||||
|
||||
if skill_root:
|
||||
scripts_dir = os.path.join(skill_root, "scripts")
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
def _build_candidates(start_dir: str) -> List[str]:
|
||||
"""
|
||||
Build candidate skill roots for different editors/layouts.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
parents = [start_dir, os.path.join(start_dir, ".."), os.path.join(start_dir, "..", "..")]
|
||||
editor_paths = [
|
||||
os.path.join(start_dir, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(start_dir, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(start_dir, ".claude", "skills", "jianying-editor"),
|
||||
os.path.join(start_dir, "skills", "jianying-editor"),
|
||||
os.path.join(cwd, ".agent", "skills", "jianying-editor"),
|
||||
os.path.join(cwd, ".trae", "skills", "jianying-editor"),
|
||||
os.path.join(cwd, ".claude", "skills", "jianying-editor"),
|
||||
os.path.join(cwd, "skills", "jianying-editor"),
|
||||
]
|
||||
return parents + editor_paths
|
||||
|
||||
|
||||
def resolve_skill_root(start_dir: str) -> Tuple[Optional[str], List[str]]:
|
||||
"""
|
||||
Resolve skill root by environment variable first, then candidate probing.
|
||||
Returns (resolved_root, attempted_paths).
|
||||
"""
|
||||
attempted: List[str] = []
|
||||
|
||||
env_root = os.getenv("JY_SKILL_ROOT", "").strip()
|
||||
if env_root:
|
||||
env_root = os.path.abspath(env_root)
|
||||
attempted.append(env_root)
|
||||
if os.path.exists(os.path.join(env_root, "scripts", "jy_wrapper.py")):
|
||||
return env_root, attempted
|
||||
|
||||
for p in _build_candidates(start_dir):
|
||||
ap = os.path.abspath(p)
|
||||
attempted.append(ap)
|
||||
if os.path.exists(os.path.join(ap, "scripts", "jy_wrapper.py")):
|
||||
return ap, attempted
|
||||
|
||||
return None, attempted
|
||||
|
||||
|
||||
def ensure_skill_scripts_on_path(start_dir: str) -> str:
|
||||
"""
|
||||
Resolve skill root and prepend its scripts path to sys.path.
|
||||
Raises ImportError with tried paths when not found.
|
||||
"""
|
||||
import sys
|
||||
|
||||
root, attempted = resolve_skill_root(start_dir)
|
||||
if not root:
|
||||
msg = "Could not find jianying-editor skill root.\nTried:\n- " + "\n- ".join(attempted)
|
||||
raise ImportError(msg)
|
||||
|
||||
scripts_dir = os.path.join(root, "scripts")
|
||||
if scripts_dir not in sys.path:
|
||||
sys.path.insert(0, scripts_dir)
|
||||
return root
|
||||
Reference in New Issue
Block a user