Compare commits
10 Commits
081278af74
...
d8747e9831
| Author | SHA1 | Date | |
|---|---|---|---|
| d8747e9831 | |||
| ae9d794881 | |||
| efd7f21a28 | |||
| 4621a4abcf | |||
| 120edb82e6 | |||
| 1726616c48 | |||
| 260b8966ed | |||
| 1873b30e30 | |||
| 9e42c2ccd9 | |||
| a3169930fd |
1
.opencode/skills/js-runner
Symbolic link
1
.opencode/skills/js-runner
Symbolic link
@@ -0,0 +1 @@
|
||||
../../skills/js-runner
|
||||
1
.opencode/skills/python-runner
Symbolic link
1
.opencode/skills/python-runner
Symbolic link
@@ -0,0 +1 @@
|
||||
../../skills/python-runner
|
||||
238
document/specification.md
Normal file
238
document/specification.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 规范说明
|
||||
|
||||
> Agent Skills 的完整格式规范。
|
||||
|
||||
本文档定义了 Agent Skills 格式规范。
|
||||
|
||||
## 目录结构
|
||||
|
||||
一个技能是一个目录,至少包含一个 `SKILL.md` 文件:
|
||||
|
||||
```
|
||||
skill-name/
|
||||
└── SKILL.md # 必需
|
||||
```
|
||||
|
||||
<Tip>
|
||||
你可以选择性地包含[额外的目录](#可选目录),如 `scripts/`、`references/` 和 `assets/` 来支持你的技能。
|
||||
</Tip>
|
||||
|
||||
## SKILL.md 格式
|
||||
|
||||
`SKILL.md` 文件必须包含 YAML 前置数据(frontmatter),后跟 Markdown 内容。
|
||||
|
||||
### 前置数据(必需)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: A description of what this skill does and when to use it.
|
||||
---
|
||||
```
|
||||
|
||||
包含可选字段:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: pdf-processing
|
||||
description: Extract text and tables from PDF files, fill forms, merge documents.
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: example-org
|
||||
version: '1.0'
|
||||
---
|
||||
```
|
||||
|
||||
| 字段 | 是否必需 | 约束条件 |
|
||||
| --------------- | -------- | -------------------------------------------------------------------- |
|
||||
| `name` | 是 | 最多 64 个字符。仅限小写字母、数字和连字符。不能以连字符开头或结尾。 |
|
||||
| `description` | 是 | 最多 1024 个字符。非空。描述该技能的作用以及何时使用它。 |
|
||||
| `license` | 否 | 许可证名称或对打包许可证文件的引用。 |
|
||||
| `compatibility` | 否 | 最多 500 个字符。指示环境要求(目标产品、系统包、网络访问等)。 |
|
||||
| `metadata` | 否 | 用于附加元数据的任意键值映射。 |
|
||||
| `allowed-tools` | 否 | 技能可能使用的预先批准工具的空格分隔列表。(实验性) |
|
||||
|
||||
#### `name` 字段
|
||||
|
||||
必需的 `name` 字段:
|
||||
|
||||
- 必须为 1-64 个字符
|
||||
- 仅可包含 unicode 小写字母数字字符和连字符(`a-z` 和 `-`)
|
||||
- 不能以 `-` 开头或结尾
|
||||
- 不能包含连续的连字符(`--`)
|
||||
- 必须与父目录名称匹配
|
||||
|
||||
有效示例:
|
||||
|
||||
```yaml
|
||||
name: pdf-processing
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: data-analysis
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: code-review
|
||||
```
|
||||
|
||||
无效示例:
|
||||
|
||||
```yaml
|
||||
name: PDF-Processing # 不允许大写字母
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: -pdf # 不能以连字符开头
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: pdf--processing # 不允许连续的连字符
|
||||
```
|
||||
|
||||
#### `description` 字段
|
||||
|
||||
必需的 `description` 字段:
|
||||
|
||||
- 必须为 1-1024 个字符
|
||||
- 应该描述该技能的作用以及何时使用它
|
||||
- 应该包含有助于代理识别相关任务的特定关键字
|
||||
|
||||
好的示例:
|
||||
|
||||
```yaml
|
||||
description: Extracts text and tables from PDF files, fills PDF forms, and merges multiple PDFs. Use when working with PDF documents or when the user mentions PDFs, forms, or document extraction.
|
||||
```
|
||||
|
||||
差的示例:
|
||||
|
||||
```yaml
|
||||
description: Helps with PDFs.
|
||||
```
|
||||
|
||||
#### `license` 字段
|
||||
|
||||
可选的 `license` 字段:
|
||||
|
||||
- 指定应用于该技能的许可证
|
||||
- 我们建议保持简短(要么是许可证名称,要么是打包许可证文件的名称)
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
```
|
||||
|
||||
#### `compatibility` 字段
|
||||
|
||||
可选的 `compatibility` 字段:
|
||||
|
||||
- 如果提供,必须为 1-500 个字符
|
||||
- 仅在你的技能具有特定环境要求时才应包含
|
||||
- 可以指示目标产品、所需的系统包、网络访问需求等
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
compatibility: Designed for Claude Code (or similar products)
|
||||
```
|
||||
|
||||
```yaml
|
||||
compatibility: Requires git, docker, jq, and access to the internet
|
||||
```
|
||||
|
||||
<Note>
|
||||
大多数技能不需要 `compatibility` 字段。
|
||||
</Note>
|
||||
|
||||
#### `metadata` 字段
|
||||
|
||||
可选的 `metadata` 字段:
|
||||
|
||||
- 从字符串键到字符串值的映射
|
||||
- 客户端可以使用它来存储未由 Agent Skills 规范定义的附加属性
|
||||
- 我们建议使你的键名足够唯一以避免意外冲突
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
author: example-org
|
||||
version: '1.0'
|
||||
```
|
||||
|
||||
#### `allowed-tools` 字段
|
||||
|
||||
可选的 `allowed-tools` 字段:
|
||||
|
||||
- 预先批准运行的工具的空格分隔列表
|
||||
- 实验性功能。不同代理实现对该字段的支持可能有所不同
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
allowed-tools: Bash(git:*) Bash(jq:*) Read
|
||||
```
|
||||
|
||||
### 正文内容
|
||||
|
||||
前置数据后的 Markdown 正文包含技能指令。没有格式限制。编写任何有助于代理有效执行任务的内容。
|
||||
|
||||
推荐的部分:
|
||||
|
||||
- 分步指令
|
||||
- 输入和输出示例
|
||||
- 常见边界情况
|
||||
|
||||
请注意,代理在决定激活技能后将加载整个文件。考虑将较长的 `SKILL.md` 内容拆分为引用文件。
|
||||
|
||||
## 可选目录
|
||||
|
||||
### scripts/
|
||||
|
||||
包含代理可以运行的可执行代码。脚本应该:
|
||||
|
||||
- 自包含或清楚地记录依赖关系
|
||||
- 包含有用的错误消息
|
||||
- 优雅地处理边界情况
|
||||
|
||||
支持的语言取决于代理实现。常见选项包括 Python、Bash 和 JavaScript。
|
||||
|
||||
### references/
|
||||
|
||||
包含代理可以在需要时阅读的附加文档:
|
||||
|
||||
- `REFERENCE.md` - 详细的技术参考
|
||||
- `FORMS.md` - 表单模板或结构化数据格式
|
||||
- 特定于领域的文件(`finance.md`、`legal.md` 等)
|
||||
|
||||
保持单个[参考文件](#文件引用)专注。代理按需加载这些文件,因此较小的文件意味着更少的上下文使用。
|
||||
|
||||
### assets/
|
||||
|
||||
包含静态资源:
|
||||
|
||||
- 模板(文档模板、配置模板)
|
||||
- 图像(图表、示例)
|
||||
- 数据文件(查找表、模式)
|
||||
|
||||
## 渐进式披露
|
||||
|
||||
技能的结构应高效利用上下文:
|
||||
|
||||
1. **元数据**(~100 tokens):所有技能在启动时加载 `name` 和 `description` 字段
|
||||
2. **指令**(建议 < 5000 tokens):技能激活时加载完整的 `SKILL.md` 正文
|
||||
3. **资源**(按需):文件(例如 `scripts/`、`references/` 或 `assets/` 中的文件)仅在需要时加载
|
||||
|
||||
将主 `SKILL.md` 保持在 500 行以下。将详细参考资料移动到单独的文件中。
|
||||
|
||||
## 文件引用
|
||||
|
||||
在技能中引用其他文件时,使用从技能根目录开始的相对路径:
|
||||
|
||||
```markdown
|
||||
Run the extraction script:
|
||||
scripts/extract.py
|
||||
```
|
||||
|
||||
保持文件引用从 `SKILL.md` 开始只有一层深度。避免深度嵌套的引用链。
|
||||
65
document/what-are-skills.md
Normal file
65
document/what-are-skills.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 什么是技能?
|
||||
|
||||
> Agent Skills 是一种轻量级、开放的格式,用于通过专业知识和工作流扩展 AI 代理的能力。
|
||||
|
||||
核心来说,一个技能是一个包含 `SKILL.md` 文件的文件夹。该文件包含元数据(至少包括 `name` 和 `description`)以及告诉代理如何执行特定任务的指令。技能还可以打包脚本、模板和参考资料。
|
||||
|
||||
```directory theme={null}
|
||||
my-skill/
|
||||
├── SKILL.md # 必需:指令 + 元数据
|
||||
├── scripts/ # 可选:可执行代码
|
||||
├── references/ # 可选:文档
|
||||
└── assets/ # 可选:模板、资源
|
||||
```
|
||||
|
||||
## 技能如何工作
|
||||
|
||||
技能使用**渐进式披露**来高效管理上下文:
|
||||
|
||||
1. **发现**:在启动时,代理仅加载每个可用技能的名称和描述,足以了解何时可能相关。
|
||||
|
||||
2. **激活**:当任务匹配技能的描述时,代理将完整的 `SKILL.md` 指令读入上下文。
|
||||
|
||||
3. **执行**:代理遵循指令,根据需要可选择性地加载引用文件或执行打包的代码。
|
||||
|
||||
这种方法使代理保持快速,同时能够按需访问更多上下文。
|
||||
|
||||
## SKILL.md 文件
|
||||
|
||||
每个技能都从包含 YAML 前置数据和 Markdown 指令的 `SKILL.md` 文件开始:
|
||||
|
||||
```mdx
|
||||
---
|
||||
name: pdf-processing
|
||||
description: Extract text and tables from PDF files, fill forms, merge documents.
|
||||
---
|
||||
|
||||
# PDF Processing
|
||||
|
||||
## When to use this skill
|
||||
|
||||
Use this skill when the user needs to work with PDF files...
|
||||
|
||||
## How to extract text
|
||||
|
||||
1. Use pdfplumber for text extraction...
|
||||
|
||||
## How to fill forms
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
以下前置数据在 `SKILL.md` 的顶部是必需的:
|
||||
|
||||
- `name`:简短标识符
|
||||
- `description`:何时使用此技能
|
||||
|
||||
Markdown 正文包含实际指令,对结构或内容没有特定限制。
|
||||
|
||||
这种简单格式具有一些关键优势:
|
||||
|
||||
- **自文档化**:技能作者或用户可以阅读 `SKILL.md` 并了解其功能,使技能易于审核和改进。
|
||||
|
||||
- **可扩展**:技能的复杂度可以范围从仅文本指令到可执行代码、资产和模板。
|
||||
|
||||
- **可移植**:技能只是文件,因此易于编辑、版本控制和共享。
|
||||
@@ -57,7 +57,7 @@ uv是Astral开发的快速Python包管理器,支持PEP 723内联元数据格
|
||||
|
||||
### Decision 2: 使用辅助脚本实现跨平台临时目录
|
||||
|
||||
**决策:** 创建`skills/uv-python-runner/script/get_temp_path.py`辅助脚本,使用Python标准库的`tempfile`模块在系统临时目录中创建空的Python脚本文件,并返回脚本文件路径。**辅助脚本本身也使用uv run执行,保持一致性。**
|
||||
**决策:** 创建`skills/python-runner/script/get_temp_path.py`辅助脚本,使用Python标准库的`tempfile`模块在系统临时目录中创建空的Python脚本文件,并返回脚本文件路径。**辅助脚本本身也使用uv run执行,保持一致性。**
|
||||
|
||||
**理由:**
|
||||
- `tempfile.gettempdir()`自动适配所有平台(Windows/macOS/Linux)
|
||||
@@ -72,7 +72,7 @@ uv是Astral开发的快速Python包管理器,支持PEP 723内联元数据格
|
||||
- **功能**:在系统临时目录创建空的Python脚本文件,返回文件路径
|
||||
- **无注释**:脚本只包含必要代码,没有文档字符串或注释
|
||||
- **PEP 723格式**:包含空的依赖声明`# dependencies = []`
|
||||
- **执行方式**:`uv run skills/uv-python-runner/script/get_temp_path.py`
|
||||
- **执行方式**:`uv run skills/python-runner/script/get_temp_path.py`
|
||||
- **输出**:直接在stdout输出临时Python脚本文件的完整路径
|
||||
- Linux/macOS: `/tmp/uv_script_xxx.py`
|
||||
- Windows: `C:\Users\<username>\AppData\Local\Temp\uv_script_xxx.py`
|
||||
@@ -220,13 +220,13 @@ uv是Astral开发的快速Python包管理器,支持PEP 723内联元数据格
|
||||
这是新skill的首次实现,不存在迁移问题。
|
||||
|
||||
**部署步骤:**
|
||||
1. 创建skill目录:`skills/uv-python-runner/`
|
||||
2. 创建skill文件:`skills/uv-python-runner/SKILL.md`
|
||||
1. 创建skill目录:`skills/python-runner/`
|
||||
2. 创建skill文件:`skills/python-runner/SKILL.md`
|
||||
3. 编写skill内容(YAML元数据 + Markdown文档)
|
||||
4. 测试skill是否可以被正确加载和触发
|
||||
|
||||
**回滚策略:**
|
||||
- 如有问题,直接删除`skills/uv-python-runner/`目录即可
|
||||
- 如有问题,直接删除`skills/python-runner/`目录即可
|
||||
- skill文件不影响系统或现有代码
|
||||
|
||||
## Open Questions
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
## What Changes
|
||||
|
||||
- 创建新的skill:`uv-python-runner`
|
||||
**注:** 实现时将技能名称从 `python-runner` 简化为 `python-runner`,以保持命名更通用和简洁,避免与未来可能的其他 Python runner 实现名称冲突。
|
||||
|
||||
- 创建新的skill:`python-runner` (proposal中为 `python-runner`)
|
||||
- 指导大模型按照PEP 723规范编写Python脚本
|
||||
- 提供临时文件创建和uv run执行的标准流程
|
||||
- 严格错误处理模式
|
||||
@@ -26,7 +28,7 @@
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `uv-python-runner`: 通用Python脚本执行工具,指导大模型使用uv的隔离环境特性来执行临时Python脚本,无需在系统环境预安装依赖。适用于数据处理、API交互、文件操作、科学计算等各种任务。
|
||||
- `python-runner`: 通用Python脚本执行工具,指导大模型使用uv的隔离环境特性来执行临时Python脚本,无需在系统环境预安装依赖。适用于数据处理、API交互、文件操作、科学计算等各种任务。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
@@ -34,7 +36,7 @@
|
||||
|
||||
## Impact
|
||||
|
||||
- **代码影响**:新增skill文件 `skills/uv-python-runner/SKILL.md`
|
||||
- **代码影响**:新增skill文件 `skills/python-runner/SKILL.md`
|
||||
- **依赖影响**:要求系统安装uv(https://docs.astral.sh/uv/)
|
||||
- **系统影响**:无(skill只提供指导,不直接修改系统)
|
||||
- **API影响**:无
|
||||
@@ -36,7 +36,7 @@ Skill SHALL指导LLM在系统临时目录中创建临时Python文件,并使用
|
||||
|
||||
### 需求:跨平台临时目录支持
|
||||
|
||||
Skill SHALL使用`skills/uv-python-runner/script/get_temp_path.py`辅助脚本来获取平台特定的临时目录。Skill SHALL在创建临时Python文件之前,先调用此辅助脚本获取临时目录路径。Skill SHALL支持Windows、macOS、Linux三个平台。
|
||||
Skill SHALL使用`skills/python-runner/script/get_temp_path.py`辅助脚本来获取平台特定的临时目录。Skill SHALL在创建临时Python文件之前,先调用此辅助脚本获取临时目录路径。Skill SHALL支持Windows、macOS、Linux三个平台。
|
||||
|
||||
#### 场景:获取临时目录路径
|
||||
- **WHEN** LLM需要为Python脚本创建临时文件
|
||||
@@ -1,17 +1,17 @@
|
||||
## 1. Skill结构设置
|
||||
|
||||
- [x] 1.1 创建skill目录:`skills/uv-python-runner/`
|
||||
- [x] 1.2 创建script子目录:`skills/uv-python-runner/script/`
|
||||
- [x] 1.3 创建辅助脚本:`skills/uv-python-runner/script/get_temp_path.py`
|
||||
- [x] 1.1 创建skill目录:`skills/python-runner/`
|
||||
- [x] 1.2 创建script子目录:`skills/python-runner/script/`
|
||||
- [x] 1.3 创建辅助脚本:`skills/python-runner/script/get_temp_path.py`
|
||||
- [x] 1.3.1 添加PEP 723元数据块(`# dependencies = []`)
|
||||
- [x] 1.3.2 在系统临时目录创建空的Python脚本文件并返回路径
|
||||
- [x] 1.3.3 直接在stdout输出脚本文件完整路径
|
||||
- [x] 1.3.4 添加命令行支持,直接运行脚本
|
||||
- [x] 1.4 创建skill文件:`skills/uv-python-runner/SKILL.md`
|
||||
- [x] 1.4 创建skill文件:`skills/python-runner/SKILL.md`
|
||||
|
||||
## 2. 编写YAML前置元数据
|
||||
|
||||
- [x] 2.1 添加skill名称:`uv-python-runner`
|
||||
- [x] 2.1 添加skill名称:`python-runner`
|
||||
- [x] 2.2 添加英文skill描述
|
||||
- [x] 2.3 添加参数提示(不适用,此skill无参数)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
- [x] 5.1.2 提供包含外部依赖的示例
|
||||
- [x] 5.1.3 提供仅使用标准库的示例
|
||||
- [x] 5.2 记录步骤2:获取临时目录
|
||||
- [x] 5.2.1 说明调用辅助脚本:`uv run skills/uv-python-runner/script/get_temp_path.py`
|
||||
- [x] 5.2.1 说明调用辅助脚本:`uv run skills/python-runner/script/get_temp_path.py`
|
||||
- [x] 5.2.2 说明辅助脚本直接在stdout输出临时目录路径
|
||||
- [x] 5.2.3 说明大模型捕获stdout输出得到临时目录
|
||||
- [x] 5.2.4 说明根据临时目录构造脚本文件路径:`<temp_dir>/uv_script_<timestamp>_<random>.py`
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-05
|
||||
143
openspec/changes/archive/2026-02-05-js-runner/design.md
Normal file
143
openspec/changes/archive/2026-02-05-js-runner/design.md
Normal file
@@ -0,0 +1,143 @@
|
||||
## Context(背景)
|
||||
|
||||
本项目在 `skills/` 目录下开发用于大模型工具的技能(skills),每个子目录代表一个技能。目前,项目已经有一个 `python-runner` 技能,它使用 `uv` 提供了隔离的 Python 执行环境,支持依赖管理和临时文件隔离。
|
||||
|
||||
`js-runner/` 目录已存在但未完成,只包含一个测试文件(`test/axios.js`)。目前项目中还没有功能完整的 JavaScript/TypeScript 执行能力。
|
||||
|
||||
**当前状态:**
|
||||
- `python-runner` 技能已完整实现(基于 uv 的隔离)
|
||||
- `js-runner` 目录骨架存在但缺少实现
|
||||
- 没有 JavaScript/TypeScript 的依赖管理机制
|
||||
- 没有 JS/TS 脚本的临时文件生命周期管理
|
||||
|
||||
**约束条件:**
|
||||
- 必须保持环境隔离(不污染系统包)
|
||||
- 应遵循 `python-runner` 建立的模式以保持一致性
|
||||
- 技能结构必须符合项目规范(`skills/<skill-name>/`)
|
||||
- 必须同时支持 JavaScript 和 TypeScript 执行
|
||||
|
||||
## Goals / Non-Goals(目标 / 非目标)
|
||||
|
||||
**目标:**
|
||||
- 提供辅助脚本供大模型调用(使用 JavaScript 编写,与 skill 主题一致)
|
||||
- 在 SKILL.md 中清晰描述完整的调用流程
|
||||
- 利用 Bun 的自动依赖管理能力
|
||||
- 匹配 `python-runner` 的工作流程和用户体验,保持一致性
|
||||
|
||||
**非目标:**
|
||||
- 提供完整的 IDE 或开发环境
|
||||
- 脚本的长期存储或持久化
|
||||
- 超越 JavaScript/TypeScript 的多语言支持
|
||||
- 替换或修改现有的 `python-runner` 技能
|
||||
- 构建自定义的 Bun 运行时包装器(直接使用 Bun)
|
||||
|
||||
## Decisions(技术决策)
|
||||
|
||||
### 1. 使用 Bun 作为 JavaScript 运行时
|
||||
|
||||
**理由:** Bun 相比替代方案具有显著优势:
|
||||
- **Bun:** 比 Node.js 快约 100 倍的冷启动,内置包管理器(bun install),原生 TypeScript 支持,兼容 Node.js API
|
||||
- **Node.js + npm/yarn:** 启动慢,需要外部包管理器,没有原生 TypeScript 支持
|
||||
- **Deno:** 不同的 API 表面(不兼容 Node.js),生态系统较小,Node.js 用户学习曲线陡峭
|
||||
|
||||
**考虑的替代方案:**
|
||||
- **Node.js with npm:** 因启动速度慢且无原生 TypeScript 支持而被拒绝
|
||||
- **Deno:** 因 API 与 Node.js 生态不兼容且包管理不熟悉而被拒绝
|
||||
|
||||
### 2. 基于临时文件的执行模型
|
||||
|
||||
**理由:** 遵循 `python-runner` 的模式,在临时文件中执行脚本可以:
|
||||
- 在执行之间保持干净的隔离
|
||||
- 自动清理产物
|
||||
- 不需要跨运行的状态管理
|
||||
- 比内存执行实现更简单
|
||||
|
||||
**考虑的替代方案:**
|
||||
- **通过 Bun 的 `Bun.transpileString()` 进行内存执行:** 因对模块导入和外部依赖支持不好而被拒绝
|
||||
- **持久化目录结构:** 因避免执行之间的状态泄漏而被拒绝
|
||||
|
||||
### 3. 使用 Bun 的自动依赖管理
|
||||
|
||||
**理由:** Bun 可以直接运行脚本并自动处理依赖:
|
||||
- **自动依赖解析:** 当脚本使用 `import` 或 `require` 引入外部包时,Bun 自动下载并缓存该包
|
||||
- **无需手动安装:** 不需要手动运行 `bun install`,Bun 在运行时自动处理
|
||||
- **智能缓存:** Bun 全局缓存已下载的包,后续运行无需重复下载
|
||||
- **简化实现:** 不需要生成临时的 `package.json`,也不需要管理 `node_modules`
|
||||
|
||||
**工作流程:**
|
||||
1. 用户直接运行脚本(内联或从文件)
|
||||
2. 脚本中使用 `import` 或 `require` 引入依赖(如 `import axios from 'axios'`)
|
||||
3. Bun 自动检测到未安装的依赖
|
||||
4. Bun 下载并缓存依赖到 Bun 的全局缓存(`~/.bun/install/cache`)
|
||||
5. 执行脚本
|
||||
6. 清理临时脚本文件(Bun 的缓存保持不变)
|
||||
|
||||
**考虑的替代方案:**
|
||||
- **手动 package.json + bun install:** 因 Bun 已经提供自动依赖处理而被拒绝(过度设计)
|
||||
- **要求用户提供 package.json:** 因增加用户负担而被拒绝
|
||||
|
||||
### 4. 用于路径管理的辅助脚本
|
||||
|
||||
**理由:** `get_temp_path.js` 辅助脚本将:
|
||||
- 为脚本生成唯一的临时文件路径
|
||||
- 遵循特定于操作系统的临时目录约定
|
||||
- 使用 JavaScript 编写,与 js-runner 主题一致
|
||||
- 使用 Bun 运行(已确保环境就绪)
|
||||
|
||||
**实现方式:**
|
||||
```javascript
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
export function getTempPath(extension) {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
return join(tmpdir(), `js-runner-${timestamp}-${random}.${extension}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Risks / Trade-offs(风险 / 权衡)
|
||||
|
||||
### 风险:需要安装 Bun
|
||||
|
||||
**风险:** 用户必须单独安装 Bun,这增加了设置障碍。
|
||||
|
||||
**缓解措施:**
|
||||
- 在 SKILL.md 中清晰记录安装过程(`curl -fsSL https://bun.sh/install | bash`)
|
||||
- 如果未找到 Bun,提供有用的错误消息
|
||||
- 考虑添加 "check" 命令来验证 Bun 安装
|
||||
|
||||
## Migration Plan(迁移计划)
|
||||
|
||||
由于这是一个没有现有生产使用的新技能:
|
||||
|
||||
1. **第一阶段:实现**
|
||||
- 创建 `scripts/get_temp_path.js` 辅助脚本(使用 JavaScript 编写)
|
||||
- 编写完整的 `SKILL.md` 文档,包含必要的 frontmatter
|
||||
- 描述完整调用流程
|
||||
|
||||
2. **第二阶段:验证**
|
||||
- 验证 `get_temp_path.js` 辅助脚本工作正常
|
||||
- 验证 SKILL.md 格式符合 Agent Skills 规范
|
||||
- 验证 SKILL.md 中的调用流程准确无误
|
||||
|
||||
3. **第三阶段:文档**
|
||||
- 确保 SKILL.md 涵盖所有用例
|
||||
- 为常见场景提供示例
|
||||
- 记录错误处理和故障排除
|
||||
|
||||
**回滚策略:**
|
||||
- 如果实现有问题,删除 `skills/js-runner/` 目录
|
||||
- 对项目其他部分无影响,因为这是一个新的隔离技能
|
||||
|
||||
## Open Questions(待解决的问题)
|
||||
|
||||
1. **我们是否应该支持持久化项目目录?**
|
||||
- 当前方法:只有临时文件
|
||||
- 考虑:允许用户指定工作目录
|
||||
- 决策:推迟到未来增强;首先专注于简单的脚本执行
|
||||
|
||||
2. **需要什么级别的错误报告?**
|
||||
- 当前计划:按原样转发 Bun 的 stderr 输出
|
||||
- 考虑:解析并美化常见错误
|
||||
- 决策:从原始输出开始,根据用户需求增强
|
||||
32
openspec/changes/archive/2026-02-05-js-runner/proposal.md
Normal file
32
openspec/changes/archive/2026-02-05-js-runner/proposal.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Why
|
||||
|
||||
当前项目已有基于uv的Python执行环境(python-runner),但缺少相应的JavaScript/TypeScript执行能力。Bun作为现代JavaScript运行时,具有极快的启动速度、内置包管理器和对标准库的完整支持,非常适合创建一个类似python-runner的隔离式JS/TS执行环境,保持系统环境整洁。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 完善现有的`skills/js-runner/`目录(目前仅有test/axios.js文件)
|
||||
- 基于Bun实现JavaScript/TypeScript脚本执行能力,使用临时文件和环境隔离
|
||||
- 参考python-runner的工作流,支持依赖管理(通过package.json或内联声明)
|
||||
- 提供辅助脚本生成临时文件路径
|
||||
- 创建完整的SKILL.md文档
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `js-runner`: 提供基于Bun的JavaScript/TypeScript脚本执行能力,支持依赖管理、环境隔离和临时文件清理
|
||||
|
||||
### Modified Capabilities
|
||||
- None
|
||||
|
||||
## Impact
|
||||
|
||||
**新增文件**:
|
||||
- `skills/js-runner/SKILL.md` - Skill文档
|
||||
- `skills/js-runner/scripts/get_temp_path.js` - 临时路径生成辅助脚本
|
||||
|
||||
**外部依赖**:
|
||||
- 需要安装Bun运行时(https://bun.sh)
|
||||
|
||||
**不影响的范围**:
|
||||
- 不影响现有的python-runner skill
|
||||
- 不修改其他skill的实现
|
||||
@@ -0,0 +1,101 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 生成临时文件路径
|
||||
辅助脚本 MUST 提供为脚本生成唯一临时文件路径的功能。
|
||||
|
||||
#### Scenario: 生成 JavaScript 临时路径
|
||||
- **WHEN** 调用 `scripts/get_temp_path.js` 并传入参数 `'js'`
|
||||
- **THEN** 返回操作系统的临时目录路径
|
||||
- **THEN** 路径包含时间戳和随机字符串以确保唯一性
|
||||
- **THEN** 文件扩展名为 `.js`
|
||||
|
||||
#### Scenario: 生成 TypeScript 临时路径
|
||||
- **WHEN** 调用 `scripts/get_temp_path.js` 并传入参数 `'ts'`
|
||||
- **THEN** 返回操作系统的临时目录路径
|
||||
- **THEN** 路径包含时间戳和随机字符串以确保唯一性
|
||||
- **THEN** 文件扩展名为 `.ts`
|
||||
|
||||
### Requirement: 文档描述完整调用流程
|
||||
SKILL.md MUST 清晰描述大模型如何使用 js-runner 技能执行 JavaScript/TypeScript 脚本的完整流程。
|
||||
|
||||
#### Scenario: 执行脚本的标准流程
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档描述以下标准流程:
|
||||
1. 执行 `bun --version` 检查环境
|
||||
2. 调用 `scripts/get_temp_path.js` 生成临时文件路径
|
||||
3. 将脚本内容写入临时文件
|
||||
4. 使用 `bun <temp-file>` 执行脚本
|
||||
5. Bun 自动处理依赖和 TypeScript 转译
|
||||
6. 临时文件由系统自动清理
|
||||
|
||||
#### Scenario: 文档中的示例代码
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档提供完整的示例代码,展示:
|
||||
- 如何检查 Bun 安装(`bun --version`)
|
||||
- 如何调用辅助脚本生成临时文件路径
|
||||
- 如何执行脚本
|
||||
- 如何处理输出和错误
|
||||
|
||||
### Requirement: 错误处理和诊断
|
||||
系统 MUST 提供清晰的错误信息和诊断功能,帮助用户识别和解决问题。
|
||||
|
||||
#### Scenario: 未安装 Bun 时的错误
|
||||
- **WHEN** 系统未检测到 Bun 运行时
|
||||
- **THEN** 系统输出友好的错误消息,说明需要安装 Bun
|
||||
- **THEN** 系统提供安装指令(`curl -fsSL https://bun.sh/install | bash`)
|
||||
- **THEN** 退出码指示依赖缺失
|
||||
|
||||
#### Scenario: 脚本语法错误
|
||||
- **WHEN** 脚本包含语法错误
|
||||
- **THEN** Bun 输出详细的语法错误信息(包括文件名、行号、错误描述)
|
||||
- **THEN** 系统将这些信息传递给用户
|
||||
- **THEN** 退出码指示语法错误
|
||||
|
||||
#### Scenario: 运行时错误
|
||||
- **WHEN** 脚本执行过程中抛出运行时错误
|
||||
- **THEN** 系统输出完整的错误堆栈跟踪
|
||||
- **THEN** 临时文件路径被正确映射以便用户调试
|
||||
- **THEN** 退出码指示运行时错误
|
||||
|
||||
### Requirement: 输出处理
|
||||
系统 MUST 正确处理脚本的 stdout 和 stderr 输出,将其传递给用户终端。
|
||||
|
||||
#### Scenario: 标准输出
|
||||
- **WHEN** 脚本使用 `console.log()` 或 `console.error()` 输出
|
||||
- **THEN** 系统将输出实时传递到用户终端的 stdout 或 stderr
|
||||
- **THEN** 不修改或过滤输出内容
|
||||
|
||||
#### Scenario: 退出码传递
|
||||
- **WHEN** 脚本使用 `process.exit(n)` 显式退出
|
||||
- **THEN** 系统使用该退出码
|
||||
- **WHEN** 脚本正常完成且不调用 `process.exit()`
|
||||
- **THEN** 系统使用退出码 0
|
||||
|
||||
### Requirement: 文档完整性
|
||||
系统 MUST 包含完整的 SKILL.md 文档,说明如何使用 js-runner。
|
||||
|
||||
#### Scenario: SKILL.md 包含必要的 frontmatter
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档顶部包含 YAML frontmatter
|
||||
- **THEN** 包含 `name` 字段,值为 `js-runner`
|
||||
- **THEN** 包含 `description` 字段,描述技能的功能和使用场景
|
||||
- **THEN** 可选包含 `compatibility` 字段,说明 Bun 依赖
|
||||
|
||||
#### Scenario: 安装说明
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档包含 Bun 的安装说明和命令
|
||||
- **THEN** 文档说明 js-runner 的依赖要求
|
||||
|
||||
#### Scenario: 使用示例
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档提供基本使用示例(执行简单脚本)
|
||||
- **THEN** 文档提供使用外部依赖的示例(通过 import 直接引入,Bun 自动处理)
|
||||
- **THEN** 文档提供 JavaScript 和 TypeScript 执行示例(同样流程)
|
||||
- **THEN** 文档提供错误处理示例
|
||||
|
||||
#### Scenario: API 参考
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档列出所有可用命令行标志
|
||||
- **THEN** 文档说明每个标志的作用和用法
|
||||
- **THEN** 文档说明 `get_temp_path()` 辅助函数的用法
|
||||
- **THEN** 文档说明 Bun 的自动依赖管理机制
|
||||
51
openspec/changes/archive/2026-02-05-js-runner/tasks.md
Normal file
51
openspec/changes/archive/2026-02-05-js-runner/tasks.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## 1. 项目设置和目录结构
|
||||
|
||||
- [x] 1.1 验证 `skills/js-runner/` 目录结构
|
||||
- [x] 1.2 创建 `skills/js-runner/scripts/` 目录(注意是复数 scripts/)
|
||||
- [x] 1.3 确认现有测试文件 `skills/js-runner/test/axios.js` 的状态
|
||||
- [x] 1.4 验证 Bun 运行时是否已安装(本地开发环境)
|
||||
|
||||
## 2. 实现临时路径生成辅助脚本
|
||||
|
||||
- [x] 2.1 创建 `skills/js-runner/scripts/get_temp_path.js` 文件(使用 JavaScript)
|
||||
- [x] 2.2 实现 `getTempPath(extension)` 函数
|
||||
- [x] 2.3 确保函数生成唯一路径(包含时间戳和随机字符串)
|
||||
- [x] 2.4 测试辅助脚本的输出格式正确性
|
||||
- [x] 2.5 使用 `bun scripts/get_temp_path.js` 验证脚本可执行
|
||||
|
||||
## 3. 编写 SKILL.md 文档
|
||||
|
||||
- [x] 3.1 创建 `skills/js-runner/SKILL.md` 文档
|
||||
- [x] 3.2 添加必要的 YAML frontmatter:
|
||||
- `name: js-runner`
|
||||
- `description` 描述功能和场景
|
||||
- 可选 `compatibility` 说明 Bun 依赖
|
||||
- [x] 3.3 编写 Bun 安装说明和先决条件
|
||||
- [x] 3.4 描述完整的调用流程(供大模型使用):
|
||||
- 执行 `bun --version` 检查环境
|
||||
- 调用 `scripts/get_temp_path.js` 生成临时文件路径
|
||||
- 写入脚本内容到临时文件
|
||||
- 使用 `bun <temp-file>` 执行脚本
|
||||
- 输出结果到 stdout/stderr
|
||||
- [x] 3.5 添加调用流程的完整示例代码
|
||||
- [x] 3.6 添加依赖管理示例(通过 import 直接引入,Bun 自动处理)
|
||||
- [x] 3.7 添加 JavaScript 和 TypeScript 执行示例(同样流程)
|
||||
- [x] 3.8 添加错误处理和故障排除指南
|
||||
- [x] 3.9 添加辅助函数 API 参考
|
||||
- [x] 3.10 说明不主动清理临时文件,由系统自动处理
|
||||
|
||||
## 4. 验证 SKILL.md 格式
|
||||
|
||||
- [x] 4.1 使用 `skills-ref validate ./skills/js-runner` 验证格式(如果可用)
|
||||
- [x] 4.2 确认 frontmatter 符合 Agent Skills 规范
|
||||
- [x] 4.3 确认 name 字段格式正确(小写、数字、连字符)
|
||||
- [x] 4.4 确认 description 字段长度在 1024 字符以内
|
||||
- [x] 4.5 确认文档内容清晰且易于理解
|
||||
|
||||
## 5. 集成和最终验证
|
||||
|
||||
- [x] 5.1 手动测试 `scripts/get_temp_path.js` 辅助脚本
|
||||
- [x] 5.2 验证 SKILL.md 格式符合规范
|
||||
- [x] 5.3 验证调用流程的可行性
|
||||
- [x] 5.4 验证与 `python-runner` 模式的一致性
|
||||
- [x] 5.5 提交所有更改到版本控制(如适用)
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-06
|
||||
@@ -0,0 +1,100 @@
|
||||
## Context
|
||||
|
||||
js-runner 技能目前的标准流程假设所有脚本都需要通过临时文件执行。这种设计确保了脚本的隔离性和自动清理,但牺牲了灵活性。用户可能希望:
|
||||
1. 执行已存在的脚本文件
|
||||
2. 将脚本持久化到项目中的特定位置
|
||||
3. 利用大模型已有的文件操作工具(Write)直接创建脚本
|
||||
|
||||
当前实现强制所有场景都通过 `get_temp_path.js` 生成临时路径,限制了这些使用场景。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 在文档中增加条件判断逻辑,根据用户输入选择不同的执行路径
|
||||
- 保持向后兼容:未指定路径时仍使用临时文件路径(场景3)
|
||||
- 支持直接执行已存在的脚本文件(场景1)
|
||||
- 支持使用大模型工具在指定路径创建脚本(场景2)
|
||||
|
||||
**Non-Goals:**
|
||||
- 不修改 `get_temp_path.js` 的实现
|
||||
- 不改变 Bun 运行时的行为
|
||||
- 不增加新的辅助脚本或工具
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策1:文档驱动的实现方案
|
||||
|
||||
**决策选择:** 通过更新 SKILL.md 文档来定义新的使用流程,而不是修改代码实现。
|
||||
|
||||
**理由:**
|
||||
- js-runner 的核心功能(脚本执行)已经完全由 Bun 运行时提供
|
||||
- 大模型根据 SKILL.md 的描述来选择操作方式
|
||||
- 当前代码(`get_temp_path.js`)在临时路径场景下已经完全满足需求
|
||||
- 直接使用大模型的 Write 工具创建文件比通过辅助脚本更灵活
|
||||
|
||||
**替代方案考虑:**
|
||||
- 方案A:修改 `get_temp_path.js` 增加路径参数 → 拒绝,因为增加了不必要的复杂性,大模型已有 Write 工具
|
||||
- 方案B:创建新的辅助脚本用于自定义路径 → 拒绝,重复造轮子,大模型工具已覆盖此需求
|
||||
|
||||
### 决策2:场景优先级的设计
|
||||
|
||||
**决策选择:** 在文档中将场景分为明确的优先级,确保大模型在处理用户请求时有清晰的决策树。
|
||||
|
||||
**决策树:**
|
||||
1. 用户是否提供了要执行的脚本文件路径?
|
||||
- 是 → 直接执行(场景1)
|
||||
- 否 → 进入下一步
|
||||
2. 用户是否指定了脚本的生成路径?
|
||||
- 是 → 用 Write 工具在指定路径创建脚本,然后执行(场景2)
|
||||
- 否 → 使用 `get_temp_path.js` 生成临时路径,创建脚本,执行(场景3)
|
||||
|
||||
**理由:**
|
||||
- 优先处理用户明确指定的意图(已有脚本或自定义路径)
|
||||
- 临时路径作为兜底方案,确保未指定路径时仍能工作
|
||||
- 决策树简单清晰,大模型易于理解和执行
|
||||
|
||||
### 决策3:保持 `get_temp_path.js` 不变
|
||||
|
||||
**决策选择:** 不修改任何现有代码,仅更新文档。
|
||||
|
||||
**理由:**
|
||||
- 临时路径生成逻辑在场景3中仍然必需
|
||||
- 增加参数会增加代码维护成本
|
||||
- 文档更新比代码修改更容易审查和回滚
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险1:大模型可能不理解优先级逻辑
|
||||
|
||||
**风险:** 如果文档描述不够清晰,大模型可能在场景选择上出错。
|
||||
|
||||
**缓解措施:**
|
||||
- 在文档中使用明确的"决策树"或"流程图"形式
|
||||
- 为每个场景提供清晰的示例代码
|
||||
- 在 SKILL.md 顶部使用简洁的"快速参考"部分总结三种场景
|
||||
|
||||
### 权衡:文档复杂度 vs 灵活性
|
||||
|
||||
**权衡:** 增加文档的复杂度来提供更高的灵活性。
|
||||
|
||||
**说明:**
|
||||
- 用户获得了更多控制权(直接指定路径)
|
||||
- 文档需要更详细地解释不同场景
|
||||
- 但这符合 js-runner 作为"技能工具"的定位——为大模型提供灵活的执行选项
|
||||
|
||||
## Migration Plan
|
||||
|
||||
本变更仅涉及文档更新,无需代码迁移或部署步骤。
|
||||
|
||||
**更新步骤:**
|
||||
1. 更新 `skills/js-runner/SKILL.md`,增加三种使用场景的说明
|
||||
2. 更新 `openspec/specs/js-runner/spec.md`,增加新的需求场景
|
||||
3. 验证文档描述是否清晰,易于理解和执行
|
||||
|
||||
**回滚策略:**
|
||||
- 使用 git 版本控制可以轻松回滚文档修改
|
||||
- 不涉及代码修改,回滚风险极低
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。本变更的范围明确,技术实现路径清晰。
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
当前 js-runner 技能的标准流程中,所有脚本执行都必须使用 `get_temp_path.js` 生成临时文件路径。这导致当用户想要运行已存在的脚本文件或希望将脚本持久化到特定位置时,流程不够灵活。大模型已经拥有可以直接创建文件的工具(如 Write 工具),不需要强制通过临时路径生成脚本。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 增加条件判断逻辑:只有当用户没有指定脚本文件或没有指定脚本生成路径时,才使用 `get_temp_path.js` 生成临时路径
|
||||
- 增加直接执行已存在脚本文件的使用场景
|
||||
- 增加使用大模型工具在指定路径创建脚本的使用场景
|
||||
- 保持向后兼容:不提供路径或脚本时,仍使用原有的临时文件路径生成逻辑
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
无 - 本变更是在现有 js-runner 技能基础上的增强
|
||||
|
||||
### Modified Capabilities
|
||||
- `js-runner`: 增加条件性使用临时路径的需求。这将修改现有的"文档描述完整调用流程"需求,增加新的场景:
|
||||
- 场景1:用户提供已存在的脚本文件路径 → 直接执行
|
||||
- 场景2:用户提供脚本生成路径 → 用大模型工具创建脚本到指定路径,然后执行
|
||||
- 场景3:用户未提供任何路径信息 → 使用 `get_temp_path.js` 生成临时路径(现有流程)
|
||||
|
||||
## Impact
|
||||
|
||||
- **文档更新**: `skills/js-runner/SKILL.md` 需要重新组织调用流程说明,增加条件判断逻辑和新的使用场景
|
||||
- **规范更新**: `openspec/specs/js-runner/spec.md` 需要增加新的需求场景,描述不同条件下的执行路径选择
|
||||
- **代码**: `get_temp_path.js` 保持不变,仅在未提供路径时调用
|
||||
@@ -0,0 +1,77 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 环境检查前置条件
|
||||
在任何场景执行脚本之前,大模型 MUST 先检查 Bun 运行时是否可用。
|
||||
|
||||
#### Scenario: 所有场景的环境检查
|
||||
- **WHEN** 大模型准备执行脚本(无论使用哪种场景)
|
||||
- **THEN** 必须在执行任何脚本之前先运行 `bun --version`
|
||||
- **THEN** 如果 `bun --version` 失败或未找到命令,必须输出友好的错误信息
|
||||
- **THEN** 错误信息必须明确说明 "Bun 运行时未安装" 或类似提示
|
||||
- **THEN** 错误信息必须包含 Bun 安装说明(如 `curl -fsSL https://bun.sh/install | bash`)
|
||||
- **THEN** 环境检查失败时必须停止后续执行
|
||||
- **THEN** 大模型禁止自动尝试使用 nodejs、npm、yarn、pnpm 等其他 JavaScript 运行时或包管理工具
|
||||
- **THEN** 大模型禁止建议用户将脚本转换为其他运行时格式
|
||||
- **WHEN** `bun --version` 成功
|
||||
- **THEN** 可以继续执行脚本的相应场景流程
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 文档描述完整调用流程
|
||||
SKILL.md MUST 清晰描述大模型如何使用 js-runner 技能执行 JavaScript/TypeScript 脚本的完整流程,并根据用户提供的路径信息选择不同的执行方式。
|
||||
|
||||
#### Scenario: 执行已存在的脚本文件
|
||||
- **WHEN** 用户提供已存在的脚本文件路径
|
||||
- **THEN** 首先执行 `bun --version` 检查环境
|
||||
- **THEN** 如果环境检查失败,输出错误信息并停止执行
|
||||
- **THEN** 环境检查通过后,大模型直接使用 `bun <script-file>` 执行该脚本
|
||||
- **THEN** 跳过 `get_temp_path.js` 调用
|
||||
- **THEN** 不创建任何新文件
|
||||
- **THEN** 使用脚本的 stdout 和 stderr 作为输出
|
||||
|
||||
#### Scenario: 在指定路径创建并执行脚本
|
||||
- **WHEN** 用户提供脚本生成路径(但脚本文件不存在)
|
||||
- **THEN** 首先执行 `bun --version` 检查环境
|
||||
- **THEN** 如果环境检查失败,输出错误信息并停止执行
|
||||
- **THEN** 环境检查通过后,大模型使用 Write 工具在指定路径创建脚本文件
|
||||
- **THEN** 使用 `bun <specified-path>` 执行该脚本
|
||||
- **THEN** 跳过 `get_temp_path.js` 调用
|
||||
- **THEN** 脚本文件持久化到指定位置
|
||||
|
||||
#### Scenario: 使用临时路径执行脚本(默认流程)
|
||||
- **WHEN** 大模型阅读 SKILL.md 且用户未提供脚本路径或生成路径
|
||||
- **THEN** 文档描述以下标准流程:
|
||||
1. 执行 `bun --version` 检查环境
|
||||
2. 调用 `scripts/get_temp_path.js` 生成临时文件路径
|
||||
3. 将脚本内容写入临时文件
|
||||
4. 使用 `bun <temp-file>` 执行脚本
|
||||
5. Bun 自动处理依赖和 TypeScript 转译
|
||||
6. 临时文件由系统自动清理
|
||||
|
||||
#### Scenario: 文档包含场景选择决策树
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档提供清晰的执行流程决策树:
|
||||
1. 首先执行 `bun --version` 检查环境
|
||||
- 失败 → 输出错误信息并停止
|
||||
- 成功 → 进入下一步
|
||||
2. 用户是否提供了已存在的脚本文件路径?
|
||||
- 是 → 场景1:直接执行
|
||||
- 否 → 进入下一步
|
||||
3. 用户是否指定了脚本的生成路径?
|
||||
- 是 → 场景2:在指定路径创建脚本,然后执行
|
||||
- 否 → 场景3:使用临时路径(默认)
|
||||
|
||||
#### Scenario: 文档中的示例代码(三种场景)
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档提供完整的示例代码,展示:
|
||||
- 场景1示例:直接执行已存在的脚本文件
|
||||
- 场景2示例:在指定路径创建脚本并执行
|
||||
- 场景3示例:使用临时路径执行脚本(包含如何调用辅助脚本生成临时文件路径)
|
||||
- 如何检查 Bun 安装(`bun --version`)
|
||||
- 如何处理输出和错误
|
||||
|
||||
#### Scenario: 快速参考部分
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档顶部包含快速参考部分
|
||||
- **THEN** 快速参考简洁地总结三种使用场景的关键命令
|
||||
- **THEN** 快速参考帮助大模型快速选择正确的执行方式
|
||||
@@ -0,0 +1,46 @@
|
||||
## 1. 文档准备工作
|
||||
|
||||
- [x] 1.1 读取当前的 `skills/js-runner/SKILL.md` 文件,了解现有结构
|
||||
- [x] 1.2 确认文档中现有的安装说明和基本使用流程
|
||||
|
||||
## 2. 文档结构调整
|
||||
|
||||
- [x] 2.1 在 SKILL.md 顶部添加"快速参考"部分,总结三种使用场景
|
||||
- [x] 2.2 在"使用流程"章节添加场景选择决策树
|
||||
- [x] 2.3 将原有的"完整示例"重新组织为场景3的示例(临时路径)
|
||||
|
||||
## 3. 场景1:执行已存在的脚本文件
|
||||
|
||||
- [x] 3.1 添加场景1的使用说明章节
|
||||
- [x] 3.2 编写场景1的完整示例代码(包含 `bun --version` 检查)
|
||||
- [x] 3.3 说明场景1不调用 `get_temp_path.js`
|
||||
- [x] 3.4 说明场景1不创建新文件
|
||||
|
||||
## 4. 场景2:在指定路径创建并执行脚本
|
||||
|
||||
- [x] 4.1 添加场景2的使用说明章节
|
||||
- [x] 4.2 编写场景2的完整示例代码(包含 `bun --version` 检查和 Write 工具使用)
|
||||
- [x] 4.3 说明场景2使用大模型的 Write 工具创建文件
|
||||
- [x] 4.4 说明场景2脚本持久化到指定位置
|
||||
|
||||
## 5. 环境检查和错误处理
|
||||
|
||||
- [x] 5.1 在所有场景的示例中确保第一步都是 `bun --version` 检查
|
||||
- [x] 5.2 添加环境检查失败的错误处理说明
|
||||
- [x] 5.3 明确说明当 Bun 未安装时的错误提示内容(包含安装命令)
|
||||
- [x] 5.4 添加禁止条款:禁止使用 nodejs/npm/yarn/pnpm 等其他工具
|
||||
- [x] 5.5 添加禁止条款:禁止建议用户转换为其他运行时格式
|
||||
|
||||
## 6. 文档验证
|
||||
|
||||
- [x] 6.1 验证决策树的逻辑正确性
|
||||
- [x] 6.2 验证所有三个场景的示例代码完整且可执行
|
||||
- [x] 6.3 验证快速参考部分简洁明了
|
||||
- [x] 6.4 验证文档保持向后兼容(原有临时路径流程仍然可用)
|
||||
|
||||
## 7. 最终检查
|
||||
|
||||
- [x] 7.1 检查文档格式和排版
|
||||
- [x] 7.2 确认所有章节的标题层级正确
|
||||
- [x] 7.3 确认文档使用中文面向中文开发者
|
||||
- [x] 7.4 验证 frontmatter 保持完整
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-06
|
||||
@@ -0,0 +1,254 @@
|
||||
## Context
|
||||
|
||||
当前python-runner skill要求用户在Python脚本顶部添加PEP 723元数据块来声明依赖,这需要修改用户现有的脚本文件,降低了使用便利性。此外,现有实现总是使用临时文件,即使在用户明确指定路径时也是如此。
|
||||
|
||||
该skill目前的工作流程:
|
||||
1. 要求LLM生成包含PEP 723元数据的Python脚本
|
||||
2. 使用辅助脚本创建临时文件
|
||||
3. 写入脚本内容(包含元数据块)
|
||||
4. 使用`uv run <temp_file_path>`执行
|
||||
|
||||
现有实现的限制:
|
||||
- 用户必须修改现有脚本以添加PEP 723元数据
|
||||
- 总是使用临时文件,即使用户指定了存储路径
|
||||
- 无法智能识别uv项目,每次都创建新的虚拟环境
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 无需修改用户现有Python脚本即可使用uv执行
|
||||
- 自动解析脚本依赖并使用`--with`语法传递
|
||||
- 智能检测uv项目,复用项目虚拟环境
|
||||
- 尊重用户指定的脚本路径,仅在未指定时使用临时文件
|
||||
- 保持跨平台兼容性(Windows/macOS/Linux)
|
||||
- 保持严格错误处理和调试支持
|
||||
|
||||
**Non-Goals:**
|
||||
- 不修改用户现有的脚本文件
|
||||
- 不实现复杂版本约束或依赖冲突解决(交给uv)
|
||||
- 不支持命令行参数或stdin输入(保持现有限制)
|
||||
- 不持久化虚拟环境(每次执行都是新的)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策1:项目检测方法
|
||||
|
||||
**选择:** 使用`uv sync --dry-run`命令检测当前目录是否为uv项目
|
||||
|
||||
**原因:**
|
||||
- `uv sync --dry-run`是uv提供的官方方法,能准确判断目录是否为有效的uv项目
|
||||
- 不仅检查pyproject.toml存在性,还验证项目配置的有效性
|
||||
- 如果命令成功退出(exit code 0),则目录是有效的uv项目
|
||||
- 如果命令失败(非零退出码),则目录不是uv项目或配置无效
|
||||
|
||||
**替代方案考虑:**
|
||||
1. 仅检查pyproject.toml文件存在
|
||||
- 优点:简单快速
|
||||
- 缺点:无法验证文件有效性,可能误判
|
||||
- **拒绝理由**:不够可靠,可能导致误判
|
||||
|
||||
2. 检查.venv目录存在
|
||||
- 优点:可以检测已初始化的虚拟环境
|
||||
- 缺点:uv项目可能尚未运行过sync,没有.venv目录
|
||||
- **拒绝理由**:对未初始化的uv项目不友好
|
||||
|
||||
**实现细节:**
|
||||
```bash
|
||||
# 检测当前目录是否为uv项目
|
||||
if uv sync --dry-run > /dev/null 2>&1; then
|
||||
# 是uv项目,使用项目环境
|
||||
uv run <script_path>
|
||||
else
|
||||
# 不是uv项目,使用--with语法
|
||||
uv run --with package1 --with package2 <script_path>
|
||||
fi
|
||||
```
|
||||
|
||||
### 决策2:依赖解析策略
|
||||
|
||||
**选择:** 使用大模型的分析能力直接从脚本内容提取import语句,而非使用外部工具
|
||||
|
||||
**原因:**
|
||||
- 大模型已经理解脚本内容,可以直接提取import语句
|
||||
- 避免引入新的依赖(如ast模块解析工具)
|
||||
- 大模型可以理解各种import语法变体(import x, from x import y, import x as z等)
|
||||
- 可以排除Python标准库模块,只需检查是否为常见标准库名称
|
||||
|
||||
**实现细节:**
|
||||
1. 大模型分析脚本中的所有import语句
|
||||
2. 提取包名(import pandas → pandas, from numpy import array → numpy)
|
||||
3. 排除已知的标准库名称(os, sys, json, pathlib, re等)
|
||||
4. 去重后生成依赖列表
|
||||
|
||||
**限制:**
|
||||
- 大模型需要维护标准库列表(可基于Python 3.10+标准库)
|
||||
- 对于动态导入(__import__)和条件导入,可能无法识别
|
||||
- 不支持解析requirements.txt或其他依赖文件(只解析脚本内容)
|
||||
|
||||
### 决策3:路径处理策略
|
||||
|
||||
**选择:** 三层路径处理逻辑(用户指定 → 现有脚本 → 临时文件)
|
||||
|
||||
**原因:**
|
||||
- 优先尊重用户明确指定的路径
|
||||
- 对现有脚本直接执行,无需修改
|
||||
- 仅在大模型自主生成且未指定路径时使用临时文件
|
||||
|
||||
**决策树:**
|
||||
|
||||
```
|
||||
用户是否指定脚本存储路径?
|
||||
├─ 是:写入用户指定路径 → 执行
|
||||
└─ 否:
|
||||
├─ 用户是否指定现有脚本路径?
|
||||
│ ├─ 是:直接执行该脚本
|
||||
│ └─ 否:创建临时文件 → 执行
|
||||
```
|
||||
|
||||
**实现细节:**
|
||||
- 用户指定路径示例:"在scripts/data.py中写入" → 写入`scripts/data.py`
|
||||
- 用户指定现有脚本:"运行analyze.py" → 执行`analyze.py`(读取内容解析依赖)
|
||||
- 大模型自主生成:使用`get_temp_path.py`辅助脚本创建临时文件
|
||||
|
||||
### 决策4:执行命令构造
|
||||
|
||||
**选择:** 根据项目检测和依赖解析结果动态构造`uv run`命令
|
||||
|
||||
**命令模式:**
|
||||
|
||||
| 场景 | 命令格式 |
|
||||
|------|----------|
|
||||
| uv项目内脚本 | `uv run <script_path>` |
|
||||
| 非uv项目,有依赖 | `uv run --with pkg1 --with pkg2 <script_path>` |
|
||||
| 非uv项目,无依赖 | `uv run <script_path>` |
|
||||
|
||||
**原因:**
|
||||
- uv项目:依赖已在pyproject.toml中管理,不需要`--with`
|
||||
- 非uv项目:使用`--with`逐个指定依赖,uv自动处理隔离环境
|
||||
- 无依赖:不需要`--with`,使用标准库环境
|
||||
|
||||
### 决策5:不使用辅助脚本进行项目检测
|
||||
|
||||
**选择:** 直接使用大模型的命令行工具执行`uv sync --dry-run`进行项目检测
|
||||
|
||||
**原因:**
|
||||
- 大模型可以直接执行bash命令,无需额外的辅助脚本
|
||||
- 简化实现,减少维护成本
|
||||
- 避免辅助脚本的版本管理问题
|
||||
|
||||
**实现细节:**
|
||||
```bash
|
||||
# 使用大模型的命令行工具执行项目检测
|
||||
uv sync --dry-run
|
||||
```
|
||||
|
||||
- 如果命令返回exit code 0,则当前目录是有效的uv项目
|
||||
- 如果命令返回非零退出码,则当前目录不是uv项目
|
||||
- 检测结果直接由大模型判断,无需解析辅助脚本输出
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险1:uv项目检测失败
|
||||
|
||||
**描述:** `uv sync --dry-run`在某些情况下可能误判(如网络问题、依赖解析错误)
|
||||
|
||||
**缓解措施:**
|
||||
- 检测失败时回退到非uv项目模式(使用`--with`语法)
|
||||
- 记录检测失败日志,但不阻塞脚本执行
|
||||
- 用户可以手动指定使用项目环境(通过注释或显式指令)
|
||||
|
||||
### 风险2:依赖解析不完整
|
||||
|
||||
**描述:** 大模型可能遗漏某些import语句或误判标准库模块
|
||||
|
||||
**缓解措施:**
|
||||
- 在SKILL.md中提供常见标准库列表供参考
|
||||
- 当脚本执行失败时,检查是否为缺失依赖,提示用户
|
||||
- 允许用户手动指定依赖(通过参数或注释)
|
||||
|
||||
### 风险3:路径冲突和权限问题
|
||||
|
||||
**描述:** 用户指定的路径可能不存在、无写入权限或与现有文件冲突
|
||||
|
||||
**缓解措施:**
|
||||
- 写入前检查目录是否存在,不存在则创建(如果允许)
|
||||
- 写入前检查文件权限,失败时提示用户
|
||||
- 支持覆盖提示(如果文件已存在)
|
||||
|
||||
### 风险4:跨平台路径处理复杂
|
||||
|
||||
**描述:** Windows和Unix系统的路径分隔符、路径长度限制不同
|
||||
|
||||
**缓解措施:**
|
||||
- 使用Python的`pathlib`或`os.path`处理路径,而非字符串拼接
|
||||
- 保持辅助脚本使用标准库路径处理函数
|
||||
- 测试覆盖Windows、macOS、Linux三个平台
|
||||
|
||||
### 权衡1:自动依赖解析 vs 用户显式声明
|
||||
|
||||
**当前选择:** 自动解析import语句
|
||||
|
||||
**权衡:**
|
||||
- 优点:用户无需手动声明依赖,开箱即用
|
||||
- 缺点:可能遗漏某些隐式依赖(如动态导入)
|
||||
|
||||
**缓解:** 支持用户通过参数或注释显式指定依赖
|
||||
|
||||
### 权衡2:项目检测成本 vs 准确性
|
||||
|
||||
**当前选择:** 使用`uv sync --dry-run`(准确但较慢)
|
||||
|
||||
**权衡:**
|
||||
- 优点:准确判断uv项目,避免误判
|
||||
- 缺点:每次执行都需要运行检测命令(约几百毫秒)
|
||||
|
||||
**缓解:** 可以考虑缓存检测结果(在同一工作目录内)
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### 步骤1:更新SKILL.md文档
|
||||
|
||||
1. 移除PEP 723相关内容
|
||||
2. 添加依赖解析指导
|
||||
3. 添加项目检测流程
|
||||
4. 更新路径处理说明
|
||||
5. 更新执行命令示例
|
||||
6. 保持错误处理和调试支持部分
|
||||
|
||||
### 步骤2:更新工作流示例
|
||||
|
||||
在SKILL.md中提供新的工作流示例:
|
||||
- uv项目内执行脚本
|
||||
- 非uv项目执行脚本(有依赖)
|
||||
- 非uv项目执行脚本(无依赖)
|
||||
- 用户指定路径的执行流程
|
||||
|
||||
### 步骤4:测试验证
|
||||
|
||||
在三个平台(Windows/macOS/Linux)上测试:
|
||||
1. uv项目检测
|
||||
2. 依赖解析准确性
|
||||
3. 路径处理(临时/指定/现有)
|
||||
4. 跨平台兼容性
|
||||
5. 错误处理
|
||||
|
||||
### 回滚策略
|
||||
|
||||
如果新实现存在问题,可以直接回退到旧版SKILL.md内容(从git历史恢复)。
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **依赖解析准确性:** 是否需要维护一个更完整的Python标准库模块列表,以减少误判?
|
||||
- 建议:基于Python 3.10+标准库文档维护列表
|
||||
|
||||
2. **项目检测缓存:** 是否需要在同一工作目录内缓存项目检测结果,避免重复运行`uv sync --dry-run`?
|
||||
- 建议:初期不实现,观察性能影响后再决定
|
||||
|
||||
3. **隐式依赖处理:** 如何处理通过`__import__()`或动态加载的隐式依赖?
|
||||
- 建议:暂不支持,在SKILL.md中说明限制
|
||||
|
||||
4. **用户干预机制:** 是否提供机制让用户在脚本中显式声明额外依赖(如通过特殊注释)?
|
||||
- 建议:初期不实现,通过错误提示引导用户
|
||||
|
||||
5. **Windows路径长度限制:** Windows路径最大260字符限制如何处理?
|
||||
- 建议:使用长路径前缀(\\?\)或尽量使用相对路径
|
||||
@@ -0,0 +1,41 @@
|
||||
## Why
|
||||
|
||||
当前python-runner skill要求使用PEP 723规范在脚本顶部添加内联元数据块来声明依赖,这需要修改用户现有的Python脚本,降低了使用便利性。通过改用`uv run --with`语法并自动解析脚本内容中的依赖,可以在不改动用户脚本的情况下利用uv的隔离环境直接运行脚本,提升用户体验和灵活性。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: 移除对PEP 723规范的依赖,不再要求脚本顶部包含`# /// script`元数据块
|
||||
- **BREAKING**: 不再保持对包含PEP 723元数据的旧脚本的兼容性
|
||||
- 新增依赖解析功能:通过分析脚本内容自动识别所需的Python包
|
||||
- 新增智能执行逻辑:
|
||||
- 使用`uv sync --dry-run`检测当前目录是否为uv项目
|
||||
- 如果是uv项目,直接使用`uv run script.py`执行
|
||||
- 如果不是uv项目,使用`uv run --with package1 --with package2 script.py`执行
|
||||
- 新增路径处理逻辑:
|
||||
- 当用户指定脚本存储路径时,将脚本写入指定目录
|
||||
- 仅在用户未指定路径或由大模型自主生成脚本时,才使用临时脚本
|
||||
- 更新执行命令模式,统一使用`--with`语法传递依赖
|
||||
- 简化实现:直接使用大模型命令行工具执行项目检测,无需新增辅助脚本
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `uv-run-with-syntax`: 使用`uv run --with`语法执行Python脚本的能力,包括自动依赖解析和智能项目检测
|
||||
|
||||
### Modified Capabilities
|
||||
- `uv-python-runner`: 现有spec需要更新以反映新的执行流程,包括:移除PEP 723要求、新增依赖解析逻辑、新增智能路径和项目检测
|
||||
|
||||
## Impact
|
||||
|
||||
- **代码影响**:
|
||||
- 修改`skills/python-runner/SKILL.md`,更新执行工作流和示例
|
||||
- 保留现有的`get_temp_path.py`辅助脚本
|
||||
- 不新增辅助脚本,项目检测直接使用命令行工具
|
||||
- **行为影响**:
|
||||
- 不再支持PEP 723元数据格式
|
||||
- 新版工作流可以不经修改直接运行用户现有的Python脚本
|
||||
- 改进了与现有uv项目的集成体验
|
||||
- **依赖影响**:无新增依赖,继续使用uv作为核心依赖
|
||||
- **兼容性**:
|
||||
- 保持跨平台兼容性(Windows/macOS/Linux)
|
||||
- 不保持对旧版PEP 723格式的兼容性(BREAKING CHANGE)
|
||||
@@ -0,0 +1,113 @@
|
||||
# UV Python Runner Spec (Delta)
|
||||
|
||||
## Purpose
|
||||
|
||||
This is a delta spec documenting changes to the existing uv-python-runner capability to support `uv run --with` syntax and smart project detection.
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### 需求:生成符合PEP 723规范的Python脚本
|
||||
|
||||
**Reason**: 已迁移至使用`uv run --with`语法和自动依赖解析,不再需要PEP 723内联元数据。用户可以直接运行标准Python脚本,无需修改脚本内容。
|
||||
|
||||
**Migration**: 现有包含PEP 723元数据的脚本仍可正常运行,但不再要求使用该格式。新的工作流使用自动依赖解析和`--with`语法。
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 需求:在隔离的uv环境中执行脚本
|
||||
|
||||
Skill SHALL指导LLM使用`uv run`命令执行Python脚本,并根据脚本位置和项目类型选择执行策略。Skill SHALL支持临时文件和用户指定路径两种模式。Skill SHALL使用`--with`语法传递依赖或利用项目现有依赖配置。
|
||||
|
||||
#### 场景:成功执行独立脚本(无uv项目)
|
||||
- **WHEN** LLM生成有效的Python脚本且当前目录不是uv项目
|
||||
- **THEN** skill SHALL解析脚本内容获取依赖列表
|
||||
- **THEN** skill SHALL在用户指定路径或临时目录创建/使用脚本文件
|
||||
- **THEN** skill SHALL使用`uv run --with package1 --with package2 <script_path>`执行脚本
|
||||
- **THEN** uv SHALL自动创建隔离的虚拟环境,安装依赖,并执行脚本
|
||||
- **THEN** skill SHALL捕获并返回stdout/stderr输出
|
||||
|
||||
#### 场景:成功执行uv项目内的脚本
|
||||
- **WHEN** LLM执行位于uv项目内的脚本(存在pyproject.toml)
|
||||
- **THEN** skill SHALL识别为uv项目
|
||||
- **THEN** skill SHALL使用`uv run <script_path>`执行脚本(相对路径)
|
||||
- **THEN** uv SHALL使用项目的虚拟环境和依赖配置
|
||||
- **THEN** skill SHALL捕获并返回stdout/stderr输出
|
||||
|
||||
#### 场景:脚本执行失败
|
||||
- **WHEN** 脚本执行失败(运行时错误、依赖解析失败等)
|
||||
- **THEN** skill SHALL保留脚本文件用于调试(临时或指定路径)
|
||||
- **THEN** skill SHALL显示包含traceback的完整错误消息
|
||||
- **THEN** skill SHALL显示脚本文件路径
|
||||
- **THEN** skill SHALL停止任务(严格错误处理模式)
|
||||
|
||||
### 需求:跨平台路径支持
|
||||
|
||||
Skill SHALL支持Windows、macOS、Linux三个平台的路径处理,包括临时文件和用户指定路径。Skill SHALL在需要时使用辅助脚本获取平台特定的临时目录。
|
||||
|
||||
#### 场景:创建临时脚本文件
|
||||
- **WHEN** LLM需要创建临时Python脚本且用户未指定路径
|
||||
- **THEN** skill SHALL指导LLM调用辅助脚本获取临时目录
|
||||
- **THEN** skill SHALL在临时目录创建唯一的Python文件
|
||||
- **THEN** 辅助脚本SHALL使用`tempfile.gettempdir()`自动返回平台特定的路径
|
||||
|
||||
#### 场景:使用用户指定路径
|
||||
- **WHEN** 用户明确指定脚本存储或执行路径
|
||||
- **THEN** skill SHALL使用用户指定的完整路径或相对路径
|
||||
- **THEN** skill SHALL不创建临时文件
|
||||
- **THEN** skill SHALL确保路径在不同平台(Windows/macOS/Linux)上的有效性
|
||||
|
||||
#### 场景:Windows平台路径处理
|
||||
- **WHEN** 在Windows平台创建或执行脚本
|
||||
- **THEN** skill SHALL正确处理Windows路径分隔符(`\`)
|
||||
- **THEN** 辅助脚本SHALL返回Windows临时目录(例如:`C:\Users\<username>\AppData\Local\Temp`)
|
||||
- **THEN** skill SHALL确保大模型不需要硬编码Windows路径
|
||||
|
||||
#### 场景:macOS/Linux平台路径处理
|
||||
- **WHEN** 在macOS或Linux平台创建或执行脚本
|
||||
- **THEN** skill SHALL正确处理Unix路径分隔符(`/`)
|
||||
- **THEN** 辅助脚本SHALL返回`/tmp`路径
|
||||
- **THEN** skill SHALL确保大模型不需要硬编码Unix路径
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### 需求:自动依赖解析
|
||||
|
||||
Skill SHALL自动分析Python脚本内容以识别所需的Python包,排除Python标准库模块。
|
||||
|
||||
#### 场景:解析import语句提取依赖
|
||||
- **WHEN** Skill分析Python脚本的import语句
|
||||
- **THEN** Skill SHALL提取外部包名
|
||||
- **THEN** Skill SHALL排除Python标准库模块
|
||||
- **THEN** Skill SHALL生成用于`--with`参数的依赖列表
|
||||
|
||||
### 需求:智能项目检测
|
||||
|
||||
Skill SHALL检测当前工作目录是否为uv项目(存在pyproject.toml),并据此选择执行策略。
|
||||
|
||||
#### 场景:检测到uv项目
|
||||
- **WHEN** 当前工作目录包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL使用项目依赖配置执行脚本
|
||||
- **THEN** Skill SHALL不添加`--with`参数
|
||||
|
||||
#### 场景:非uv项目环境
|
||||
- **WHEN** 当前工作目录不包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL使用`--with`语法传递解析的依赖
|
||||
|
||||
### 需求:灵活脚本路径处理
|
||||
|
||||
Skill SHALL支持用户指定脚本路径、现有脚本文件和临时脚本三种模式。
|
||||
|
||||
#### 场景:用户指定脚本存储路径
|
||||
- **WHEN** 用户明确指定脚本存储路径
|
||||
- **THEN** Skill SHALL将脚本写入用户指定的路径
|
||||
- **THEN** Skill SHALL不使用临时文件
|
||||
|
||||
#### 场景:用户指定现有脚本路径
|
||||
- **WHEN** 用户指定要运行的现有Python脚本
|
||||
- **THEN** Skill SHALL直接在指定路径执行
|
||||
- **THEN** Skill SHALL不修改用户脚本内容
|
||||
|
||||
#### 场景:自主生成脚本且未指定路径
|
||||
- **WHEN** 大模型自主生成脚本且用户未指定存储路径
|
||||
- **THEN** Skill SHALL使用临时文件路径
|
||||
- **THEN** Skill SHALL允许系统自动清理临时文件
|
||||
@@ -0,0 +1,111 @@
|
||||
# UV Run With Syntax Spec
|
||||
|
||||
## Purpose
|
||||
|
||||
Define requirements for using `uv run --with` syntax to execute Python scripts with automatic dependency parsing, smart project detection, and flexible path handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### 需求:自动解析脚本依赖
|
||||
|
||||
Skill SHALL自动分析Python脚本内容以识别所需的Python包。Skill SHALL解析import语句并提取外部包名,排除Python标准库模块。
|
||||
|
||||
#### 场景:解析包含外部依赖的脚本
|
||||
- **WHEN** Skill分析包含import语句的Python脚本(例如:`import pandas as pd`)
|
||||
- **THEN** Skill SHALL提取包名(pandas)
|
||||
- **THEN** Skill SHALL排除Python标准库模块(os、sys、json等)
|
||||
- **THEN** Skill SHALL生成依赖列表,用于`--with`参数
|
||||
|
||||
#### 场景:解析多个import来源
|
||||
- **WHEN** 脚本包含多种import语句(`import pandas`, `from numpy import array`, `import requests as req`)
|
||||
- **THEN** Skill SHALL正确提取所有外部包名(pandas, numpy, requests)
|
||||
- **THEN** Skill SHALL处理`import ... as`和`from ... import`语法
|
||||
- **THEN** Skill SHALL去重依赖列表
|
||||
|
||||
#### 场景:仅使用标准库的脚本
|
||||
- **WHEN** 脚本只使用Python标准库(os、sys、json、pathlib等)
|
||||
- **THEN** Skill SHALL生成空的依赖列表
|
||||
- **THEN** Skill SHALL不添加任何`--with`参数到执行命令中
|
||||
|
||||
### 需求:智能项目检测
|
||||
|
||||
Skill SHALL检测当前工作目录是否为uv项目,并据此选择执行策略。
|
||||
|
||||
#### 场景:检测到uv项目
|
||||
- **WHEN** 当前工作目录包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL识别为uv项目
|
||||
- **THEN** Skill SHALL对当前目录下的脚本使用`uv run script.py`直接执行
|
||||
- **THEN** Skill SHALL不添加`--with`参数(依赖由项目pyproject.toml管理)
|
||||
|
||||
#### 场景:非uv项目环境
|
||||
- **WHEN** 当前工作目录不包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL识别为非uv项目
|
||||
- **THEN** Skill SHALL对脚本使用`uv run --with package1 --with package2 script.py`执行
|
||||
- **THEN** Skill SHALL根据解析的依赖列表动态构建`--with`参数
|
||||
|
||||
#### 场景:脚本位于工作目录外
|
||||
- **WHEN** 用户指定的脚本路径不在当前工作目录下
|
||||
- **THEN** Skill SHALL按独立脚本处理
|
||||
- **THEN** Skill SHALL使用`uv run --with package1 --with package2 <script_path>`执行
|
||||
- **THEN** Skill SHALL解析脚本内容以获取依赖列表
|
||||
|
||||
### 需求:灵活路径处理
|
||||
|
||||
Skill SHALL根据用户指定和上下文灵活处理脚本路径,支持用户指定路径、现有脚本文件和临时脚本。
|
||||
|
||||
#### 场景:用户指定脚本存储路径
|
||||
- **WHEN** 用户明确指定脚本存储路径(例如:"在scripts/data_processing.py中写入脚本")
|
||||
- **THEN** Skill SHALL将脚本内容写入用户指定的路径
|
||||
- **THEN** Skill SHALL不使用临时文件
|
||||
- **THEN** Skill SHALL在指定路径执行脚本
|
||||
|
||||
#### 场景:用户指定现有脚本路径
|
||||
- **WHEN** 用户指定要运行的现有Python脚本(例如:"运行scripts/my_script.py")
|
||||
- **THEN** Skill SHALL读取该脚本内容(如需解析依赖)
|
||||
- **THEN** Skill SHALL直接在指定路径执行脚本
|
||||
- **THEN** Skill SHALL不修改用户脚本内容
|
||||
|
||||
#### 场景:大模型自主生成脚本且未指定路径
|
||||
- **WHEN** 大模型自主生成Python脚本且用户未指定存储路径
|
||||
- **THEN** Skill SHALL使用辅助脚本获取临时文件路径
|
||||
- **THEN** Skill SHALL将脚本内容写入临时文件
|
||||
- **THEN** Skill SHALL在临时路径执行脚本
|
||||
- **THEN** Skill SHALL允许系统自动清理临时文件
|
||||
|
||||
### 需求:使用--with语法执行
|
||||
|
||||
Skill SHALL使用`uv run --with`语法将依赖传递给uv,替代PEP 723内联元数据方式。
|
||||
|
||||
#### 场景:有外部依赖的脚本执行
|
||||
- **WHEN** 脚本解析结果包含依赖列表[package1, package2]
|
||||
- **THEN** Skill SHALL构造命令:`uv run --with package1 --with package2 <script_path>`
|
||||
- **THEN** uv SHALL为每个`--with`指定的包创建隔离环境
|
||||
- **THEN** Skill SHALL捕获并返回脚本输出
|
||||
|
||||
#### 场景:无外部依赖的脚本执行
|
||||
- **WHEN** 脚本解析结果为空依赖列表
|
||||
- **THEN** Skill SHALL构造命令:`uv run <script_path>`
|
||||
- **THEN** Skill SHALL不包含任何`--with`参数
|
||||
- **THEN** uv SHALL使用隔离的Python标准库环境执行
|
||||
|
||||
#### 场景:uv项目内的脚本执行
|
||||
- **WHEN** 脚本位于检测到的uv项目内
|
||||
- **THEN** Skill SHALL构造命令:`uv run <script_path>`(相对路径)
|
||||
- **THEN** Skill SHALL不包含`--with`参数
|
||||
- **THEN** uv SHALL使用项目的虚拟环境和依赖配置
|
||||
|
||||
### 需求:向后兼容性
|
||||
|
||||
Skill SHALL保持对包含PEP 723元数据的旧脚本的兼容性,但不再要求使用该格式。
|
||||
|
||||
#### 场景:执行包含PEP 723元数据的脚本
|
||||
- **WHEN** 用户提供的脚本顶部包含PEP 723元数据块
|
||||
- **THEN** Skill SHALL正常执行脚本(PEP 723元数据将被uv忽略)
|
||||
- **THEN** Skill SHALL仍使用`--with`语法传递解析出的依赖
|
||||
- **THEN** 脚本SHALL成功执行
|
||||
|
||||
#### 场景:新工作流执行标准Python脚本
|
||||
- **WHEN** 用户提供的标准Python脚本无任何元数据
|
||||
- **THEN** Skill SHALL自动解析import语句
|
||||
- **THEN** Skill SHALL使用`--with`语法传递依赖
|
||||
- **THEN** 脚本SHALL在不修改的情况下成功执行
|
||||
@@ -0,0 +1,93 @@
|
||||
## 1. 文档准备
|
||||
|
||||
- [x] 1.1 备份现有 SKILL.md 文件为 SKILL.md.backup
|
||||
- [x] 1.2 阅读 python-runner skill 文档结构,了解现有内容组织方式
|
||||
|
||||
## 2. 移除PEP 723相关内容
|
||||
|
||||
- [x] 2.1 移除"生成符合PEP 723规范的Python脚本"相关章节
|
||||
- [x] 2.2 移除所有PEP 723元数据块的示例代码
|
||||
- [x] 2.3 移除`# /// script`、`# dependencies = []`等PEP 723语法说明
|
||||
- [x] 2.4 更新Purpose部分,移除对PEP 723的提及
|
||||
|
||||
## 3. 添加依赖解析指导
|
||||
|
||||
- [x] 3.1 添加"自动解析脚本依赖"章节,说明如何从import语句提取依赖
|
||||
- [x] 3.2 添加常见Python标准库模块列表(os、sys、json、pathlib等)
|
||||
- [x] 3.3 添加依赖解析示例(pandas、numpy、requests等外部包)
|
||||
- [x] 3.4 说明去重逻辑和过滤标准库的规则
|
||||
|
||||
## 4. 添加项目检测流程
|
||||
|
||||
- [x] 4.1 添加"智能项目检测"章节,说明使用`uv sync --dry-run`检测uv项目
|
||||
- [x] 4.2 说明检测逻辑:exit code 0为uv项目,非零为非uv项目
|
||||
- [x] 4.3 添加项目检测失败的处理说明(回退到非uv项目模式)
|
||||
|
||||
## 5. 更新路径处理说明
|
||||
|
||||
- [x] 5.1 添加路径处理三层逻辑说明(用户指定 → 现有脚本 → 临时文件)
|
||||
- [x] 5.2 更新临时文件获取流程,保留`get_temp_path.py`辅助脚本的使用
|
||||
- [x] 5.3 添加用户指定路径的处理说明和示例
|
||||
- [x] 5.4 添加现有脚本直接执行的说明
|
||||
|
||||
## 6. 更新执行命令示例
|
||||
|
||||
- [x] 6.1 添加uv项目内执行命令示例:`uv run script.py`
|
||||
- [x] 6.2 添加非uv项目有依赖执行命令示例:`uv run --with pandas --with numpy script.py`
|
||||
- [x] 6.3 添加非uv项目无依赖执行命令示例:`uv run script.py`
|
||||
- [x] 6.4 移除所有使用PEP 723元数据的执行示例
|
||||
|
||||
## 7. 更新工作流章节
|
||||
|
||||
- [x] 7.1 重写工作流步骤,移除"步骤1:生成符合PEP 723的Python脚本"
|
||||
- [x] 7.2 新增"步骤1:解析脚本依赖"(读取或生成脚本后分析import语句)
|
||||
- [x] 7.3 新增"步骤2:检测是否为uv项目"(执行`uv sync --dry-run`)
|
||||
- [x] 7.4 更新"步骤3:根据用户需求确定脚本路径"
|
||||
- [x] 7.5 更新"步骤4:根据检测结果构造执行命令"
|
||||
- [x] 7.6 更新完整工作流示例,包含三种场景(uv项目、非uv项目有依赖、非uv项目无依赖)
|
||||
|
||||
## 8. 更新错误处理章节
|
||||
|
||||
- [x] 8.1 添加项目检测失败的场景和错误处理
|
||||
- [x] 8.2 添加依赖解析不准确导致执行失败的处理说明
|
||||
- [x] 8.3 更新现有错误处理场景,移除对PEP 723的引用
|
||||
- [x] 8.4 添加路径权限问题的错误处理
|
||||
|
||||
## 9. 更新示例章节
|
||||
|
||||
- [x] 9.1 重写"示例1:数据分析",使用标准Python脚本(无PEP 723)
|
||||
- [x] 9.2 重写"示例2:API交互",使用标准Python脚本(无PEP 723)
|
||||
- [x] 9.3 重写"示例3:文件操作",使用标准Python脚本(无PEP 723)
|
||||
- [x] 9.4 新增"示例4:uv项目内执行",展示在uv项目中的执行流程
|
||||
- [x] 9.5 新增"示例5:用户指定路径",展示指定存储路径的执行流程
|
||||
|
||||
## 10. 更新说明和最佳实践
|
||||
|
||||
- [x] 10.1 更新"为什么使用uv?"表格,移除PEP 723相关说明
|
||||
- [x] 10.2 更新"最佳实践"部分,移除PEP 723相关内容
|
||||
- [x] 10.3 添加新的最佳实践:依赖解析的准确性注意事项
|
||||
- [x] 10.4 更新"限制"部分,移除PEP 723相关限制
|
||||
- [x] 10.5 添加新的限制说明:项目检测失败、动态导入等
|
||||
|
||||
## 11. 更新Workflow Summary
|
||||
|
||||
- [x] 11.1 重写完整工作流示例,反映新的执行流程
|
||||
- [x] 11.2 更新关键特点说明,移除PEP 723相关内容
|
||||
- [x] 11.3 添加项目检测和依赖解析的特点说明
|
||||
|
||||
## 12. 验证和测试
|
||||
|
||||
- [x] 12.1 在Windows平台测试uv项目检测
|
||||
- [x] 12.2 在macOS平台测试uv项目检测
|
||||
- [x] 12.3 在Linux平台测试uv项目检测
|
||||
- [x] 12.4 测试依赖解析准确性(包含标准库和外部包)
|
||||
- [x] 12.5 测试三种路径模式(用户指定、现有脚本、临时文件)
|
||||
- [x] 12.6 测试错误处理场景(uv未安装、语法错误、依赖失败等)
|
||||
- [x] 12.7 验证SKILL.md文档的完整性和一致性
|
||||
|
||||
## 13. 清理和提交
|
||||
|
||||
- [x] 13.1 删除备份文件 SKILL.md.backup(如果一切正常)
|
||||
- [x] 13.2 验证修改后的SKILL.md符合所有spec要求
|
||||
- [x] 13.3 确认辅助脚本 `get_temp_path.py` 仍然可用
|
||||
- [x] 13.4 确认无需新增辅助脚本(项目检测直接使用命令行工具)
|
||||
@@ -4,3 +4,5 @@ context: |
|
||||
忽略项目目录下的「.opencode」和「opencode」两个目录,与开发的skill无关;
|
||||
这个项目是专门用于开发用于大模型工具的 skill;
|
||||
所有开发的 skill 都放在「skills」目录下,每个子目录都代表一个 skill,目录名为 skill 的名称;
|
||||
开发过程中的文档使用中文,面向中文开发者进行交流;
|
||||
「document」下是 skill 开发的相关规范文档;
|
||||
|
||||
160
openspec/specs/js-runner/spec.md
Normal file
160
openspec/specs/js-runner/spec.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# JS Runner Spec
|
||||
|
||||
## Purpose
|
||||
|
||||
Define requirements for the js-runner skill, which enables execution of JavaScript and TypeScript scripts using Bun runtime with automatic dependency management and temporary file handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 环境检查前置条件
|
||||
在任何场景执行脚本之前,大模型 MUST 先检查 Bun 运行时是否可用。
|
||||
|
||||
#### Scenario: 所有场景的环境检查
|
||||
- **WHEN** 大模型准备执行脚本(无论使用哪种场景)
|
||||
- **THEN** 必须在执行任何脚本之前先运行 `bun --version`
|
||||
- **THEN** 如果 `bun --version` 失败或未找到命令,必须输出友好的错误信息
|
||||
- **THEN** 错误信息必须明确说明 "Bun 运行时未安装" 或类似提示
|
||||
- **THEN** 错误信息必须包含 Bun 安装说明(如 `curl -fsSL https://bun.sh/install | bash`)
|
||||
- **THEN** 环境检查失败时必须停止后续执行
|
||||
- **THEN** 大模型禁止自动尝试使用 nodejs、npm、yarn、pnpm 等其他 JavaScript 运行时或包管理工具
|
||||
- **THEN** 大模型禁止建议用户将脚本转换为其他运行时格式
|
||||
- **WHEN** `bun --version` 成功
|
||||
- **THEN** 可以继续执行脚本的相应场景流程
|
||||
|
||||
### Requirement: 生成临时文件路径
|
||||
辅助脚本 MUST 提供为脚本生成唯一临时文件路径的功能。
|
||||
|
||||
#### Scenario: 生成 JavaScript 临时路径
|
||||
- **WHEN** 调用 `scripts/get_temp_path.js` 并传入参数 `'js'`
|
||||
- **THEN** 返回操作系统的临时目录路径
|
||||
- **THEN** 路径包含时间戳和随机字符串以确保唯一性
|
||||
- **THEN** 文件扩展名为 `.js`
|
||||
|
||||
#### Scenario: 生成 TypeScript 临时路径
|
||||
- **WHEN** 调用 `scripts/get_temp_path.js` 并传入参数 `'ts'`
|
||||
- **THEN** 返回操作系统的临时目录路径
|
||||
- **THEN** 路径包含时间戳和随机字符串以确保唯一性
|
||||
- **THEN** 文件扩展名为 `.ts`
|
||||
|
||||
### Requirement: 文档描述完整调用流程
|
||||
SKILL.md MUST 清晰描述大模型如何使用 js-runner 技能执行 JavaScript/TypeScript 脚本的完整流程,并根据用户提供的路径信息选择不同的执行方式。
|
||||
|
||||
#### Scenario: 执行已存在的脚本文件
|
||||
- **WHEN** 用户提供已存在的脚本文件路径
|
||||
- **THEN** 首先执行 `bun --version` 检查环境
|
||||
- **THEN** 如果环境检查失败,输出错误信息并停止执行
|
||||
- **THEN** 环境检查通过后,大模型直接使用 `bun <script-file>` 执行该脚本
|
||||
- **THEN** 跳过 `get_temp_path.js` 调用
|
||||
- **THEN** 不创建任何新文件
|
||||
- **THEN** 使用脚本的 stdout 和 stderr 作为输出
|
||||
|
||||
#### Scenario: 在指定路径创建并执行脚本
|
||||
- **WHEN** 用户提供脚本生成路径(但脚本文件不存在)
|
||||
- **THEN** 首先执行 `bun --version` 检查环境
|
||||
- **THEN** 如果环境检查失败,输出错误信息并停止执行
|
||||
- **THEN** 环境检查通过后,大模型使用 Write 工具在指定路径创建脚本文件
|
||||
- **THEN** 使用 `bun <specified-path>` 执行该脚本
|
||||
- **THEN** 跳过 `get_temp_path.js` 调用
|
||||
- **THEN** 脚本文件持久化到指定位置
|
||||
|
||||
#### Scenario: 使用临时路径执行脚本(默认流程)
|
||||
- **WHEN** 大模型阅读 SKILL.md 且用户未提供脚本路径或生成路径
|
||||
- **THEN** 文档描述以下标准流程:
|
||||
1. 执行 `bun --version` 检查环境
|
||||
2. 调用 `scripts/get_temp_path.js` 生成临时文件路径
|
||||
3. 将脚本内容写入临时文件
|
||||
4. 使用 `bun <temp-file>` 执行脚本
|
||||
5. Bun 自动处理依赖和 TypeScript 转译
|
||||
6. 临时文件由系统自动清理
|
||||
|
||||
#### Scenario: 文档包含场景选择决策树
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档提供清晰的执行流程决策树:
|
||||
1. 首先执行 `bun --version` 检查环境
|
||||
- 失败 → 输出错误信息并停止
|
||||
- 成功 → 进入下一步
|
||||
2. 用户是否提供了已存在的脚本文件路径?
|
||||
- 是 → 场景1:直接执行
|
||||
- 否 → 进入下一步
|
||||
3. 用户是否指定了脚本的生成路径?
|
||||
- 是 → 场景2:在指定路径创建脚本,然后执行
|
||||
- 否 → 场景3:使用临时路径(默认)
|
||||
|
||||
#### Scenario: 文档中的示例代码(三种场景)
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档提供完整的示例代码,展示:
|
||||
- 场景1示例:直接执行已存在的脚本文件
|
||||
- 场景2示例:在指定路径创建脚本并执行
|
||||
- 场景3示例:使用临时路径执行脚本(包含如何调用辅助脚本生成临时文件路径)
|
||||
- 如何检查 Bun 安装(`bun --version`)
|
||||
- 如何处理输出和错误
|
||||
|
||||
#### Scenario: 快速参考部分
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档顶部包含快速参考部分
|
||||
- **THEN** 快速参考简洁地总结三种使用场景的关键命令
|
||||
- **THEN** 快速参考帮助大模型快速选择正确的执行方式
|
||||
|
||||
### Requirement: 错误处理和诊断
|
||||
系统 MUST 提供清晰的错误信息和诊断功能,帮助用户识别和解决问题。
|
||||
|
||||
#### Scenario: 未安装 Bun 时的错误
|
||||
- **WHEN** 系统未检测到 Bun 运行时
|
||||
- **THEN** 系统输出友好的错误消息,说明需要安装 Bun
|
||||
- **THEN** 系统提供安装指令(`curl -fsSL https://bun.sh/install | bash`)
|
||||
- **THEN** 退出码指示依赖缺失
|
||||
|
||||
#### Scenario: 脚本语法错误
|
||||
- **WHEN** 脚本包含语法错误
|
||||
- **THEN** Bun 输出详细的语法错误信息(包括文件名、行号、错误描述)
|
||||
- **THEN** 系统将这些信息传递给用户
|
||||
- **THEN** 退出码指示语法错误
|
||||
|
||||
#### Scenario: 运行时错误
|
||||
- **WHEN** 脚本执行过程中抛出运行时错误
|
||||
- **THEN** 系统输出完整的错误堆栈跟踪
|
||||
- **THEN** 临时文件路径被正确映射以便用户调试
|
||||
- **THEN** 退出码指示运行时错误
|
||||
|
||||
### Requirement: 输出处理
|
||||
系统 MUST 正确处理脚本的 stdout 和 stderr 输出,将其传递给用户终端。
|
||||
|
||||
#### Scenario: 标准输出
|
||||
- **WHEN** 脚本使用 `console.log()` 或 `console.error()` 输出
|
||||
- **THEN** 系统将输出实时传递到用户终端的 stdout 或 stderr
|
||||
- **THEN** 不修改或过滤输出内容
|
||||
|
||||
#### Scenario: 退出码传递
|
||||
- **WHEN** 脚本使用 `process.exit(n)` 显式退出
|
||||
- **THEN** 系统使用该退出码
|
||||
- **WHEN** 脚本正常完成且不调用 `process.exit()`
|
||||
- **THEN** 系统使用退出码 0
|
||||
|
||||
### Requirement: 文档完整性
|
||||
系统 MUST 包含完整的 SKILL.md 文档,说明如何使用 js-runner。
|
||||
|
||||
#### Scenario: SKILL.md 包含必要的 frontmatter
|
||||
- **WHEN** 大模型阅读 SKILL.md
|
||||
- **THEN** 文档顶部包含 YAML frontmatter
|
||||
- **THEN** 包含 `name` 字段,值为 `js-runner`
|
||||
- **THEN** 包含 `description` 字段,描述技能的功能和使用场景
|
||||
- **THEN** 可选包含 `compatibility` 字段,说明 Bun 依赖
|
||||
|
||||
#### Scenario: 安装说明
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档包含 Bun 的安装说明和命令
|
||||
- **THEN** 文档说明 js-runner 的依赖要求
|
||||
|
||||
#### Scenario: 使用示例
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档提供基本使用示例(执行简单脚本)
|
||||
- **THEN** 文档提供使用外部依赖的示例(通过 import 直接引入,Bun 自动处理)
|
||||
- **THEN** 文档提供 JavaScript 和 TypeScript 执行示例(同样流程)
|
||||
- **THEN** 文档提供错误处理示例
|
||||
|
||||
#### Scenario: API 参考
|
||||
- **WHEN** 用户阅读 SKILL.md
|
||||
- **THEN** 文档列出所有可用命令行标志
|
||||
- **THEN** 文档说明每个标志的作用和用法
|
||||
- **THEN** 文档说明 `get_temp_path()` 辅助函数的用法
|
||||
- **THEN** 文档说明 Bun 的自动依赖管理机制
|
||||
166
openspec/specs/python-runner/spec.md
Normal file
166
openspec/specs/python-runner/spec.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# UV Python Runner Spec
|
||||
|
||||
## Purpose
|
||||
|
||||
Define requirements for the python-runner skill, which enables execution of Python scripts using uv with automatic dependency parsing, smart project detection, and flexible path handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### 需求:在隔离的uv环境中执行脚本
|
||||
|
||||
Skill SHALL指导LLM使用`uv run`命令执行Python脚本,并根据脚本位置和项目类型选择执行策略。Skill SHALL支持临时文件和用户指定路径两种模式。Skill SHALL使用`--with`语法传递依赖或利用项目现有依赖配置。
|
||||
|
||||
#### 场景:成功执行独立脚本(无uv项目)
|
||||
- **WHEN** LLM生成有效的Python脚本且当前目录不是uv项目
|
||||
- **THEN** skill SHALL解析脚本内容获取依赖列表
|
||||
- **THEN** skill SHALL在用户指定路径或临时目录创建/使用脚本文件
|
||||
- **THEN** skill SHALL使用`uv run --with package1 --with package2 <script_path>`执行脚本
|
||||
- **THEN** uv SHALL自动创建隔离的虚拟环境,安装依赖,并执行脚本
|
||||
- **THEN** skill SHALL捕获并返回stdout/stderr输出
|
||||
|
||||
#### 场景:成功执行uv项目内的脚本
|
||||
- **WHEN** LLM执行位于uv项目内的脚本(存在pyproject.toml)
|
||||
- **THEN** skill SHALL识别为uv项目
|
||||
- **THEN** skill SHALL使用`uv run <script_path>`执行脚本(相对路径)
|
||||
- **THEN** uv SHALL使用项目的虚拟环境和依赖配置
|
||||
- **THEN** skill SHALL捕获并返回stdout/stderr输出
|
||||
|
||||
#### 场景:脚本执行失败
|
||||
- **WHEN** 脚本执行失败(运行时错误、依赖解析失败等)
|
||||
- **THEN** skill SHALL保留脚本文件用于调试(临时或指定路径)
|
||||
- **THEN** skill SHALL显示包含traceback的完整错误消息
|
||||
- **THEN** skill SHALL显示脚本文件路径
|
||||
- **THEN** skill SHALL停止任务(严格错误处理模式)
|
||||
|
||||
### 需求:跨平台路径支持
|
||||
|
||||
Skill SHALL支持Windows、macOS、Linux三个平台的路径处理,包括临时文件和用户指定路径。Skill SHALL在需要时使用辅助脚本获取平台特定的临时目录。
|
||||
|
||||
#### 场景:创建临时脚本文件
|
||||
- **WHEN** LLM需要创建临时Python脚本且用户未指定路径
|
||||
- **THEN** skill SHALL指导LLM调用辅助脚本获取临时目录
|
||||
- **THEN** skill SHALL在临时目录创建唯一的Python文件
|
||||
- **THEN** 辅助脚本SHALL使用`tempfile.gettempdir()`自动返回平台特定的路径
|
||||
|
||||
#### 场景:使用用户指定路径
|
||||
- **WHEN** 用户明确指定脚本存储或执行路径
|
||||
- **THEN** skill SHALL使用用户指定的完整路径或相对路径
|
||||
- **THEN** skill SHALL不创建临时文件
|
||||
- **THEN** skill SHALL确保路径在不同平台(Windows/macOS/Linux)上的有效性
|
||||
|
||||
#### 场景:Windows平台路径处理
|
||||
- **WHEN** 在Windows平台创建或执行脚本
|
||||
- **THEN** skill SHALL正确处理Windows路径分隔符(`\`)
|
||||
- **THEN** 辅助脚本SHALL返回Windows临时目录(例如:`C:\Users\<username>\AppData\Local\Temp`)
|
||||
- **THEN** skill SHALL确保大模型不需要硬编码Windows路径
|
||||
|
||||
#### 场景:macOS/Linux平台路径处理
|
||||
- **WHEN** 在macOS或Linux平台创建或执行脚本
|
||||
- **THEN** skill SHALL正确处理Unix路径分隔符(`/`)
|
||||
- **THEN** 辅助脚本SHALL返回`/tmp`路径
|
||||
- **THEN** skill SHALL确保大模型不需要硬编码Unix路径
|
||||
|
||||
### 需求:自动依赖解析
|
||||
|
||||
Skill SHALL自动分析Python脚本内容以识别所需的Python包,排除Python标准库模块。
|
||||
|
||||
#### 场景:解析import语句提取依赖
|
||||
- **WHEN** Skill分析Python脚本的import语句
|
||||
- **THEN** Skill SHALL提取外部包名
|
||||
- **THEN** Skill SHALL排除Python标准库模块
|
||||
- **THEN** Skill SHALL生成用于`--with`参数的依赖列表
|
||||
|
||||
### 需求:智能项目检测
|
||||
|
||||
Skill SHALL检测当前工作目录是否为uv项目(存在pyproject.toml),并据此选择执行策略。
|
||||
|
||||
#### 场景:检测到uv项目
|
||||
- **WHEN** 当前工作目录包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL使用项目依赖配置执行脚本
|
||||
- **THEN** Skill SHALL不添加`--with`参数
|
||||
|
||||
#### 场景:非uv项目环境
|
||||
- **WHEN** 当前工作目录不包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL使用`--with`语法传递解析的依赖
|
||||
|
||||
### 需求:灵活脚本路径处理
|
||||
|
||||
Skill SHALL支持用户指定脚本路径、现有脚本文件和临时脚本三种模式。
|
||||
|
||||
#### 场景:用户指定脚本存储路径
|
||||
- **WHEN** 用户明确指定脚本存储路径
|
||||
- **THEN** Skill SHALL将脚本写入用户指定的路径
|
||||
- **THEN** Skill SHALL不使用临时文件
|
||||
|
||||
#### 场景:用户指定现有脚本路径
|
||||
- **WHEN** 用户指定要运行的现有Python脚本
|
||||
- **THEN** Skill SHALL直接在指定路径执行
|
||||
- **THEN** Skill SHALL不修改用户脚本内容
|
||||
|
||||
#### 场景:自主生成脚本且未指定路径
|
||||
- **WHEN** 大模型自主生成脚本且用户未指定存储路径
|
||||
- **THEN** Skill SHALL使用临时文件路径
|
||||
- **THEN** Skill SHALL允许系统自动清理临时文件
|
||||
|
||||
### 需求:严格错误处理
|
||||
|
||||
Skill SHALL强制执行严格错误处理模式。任何错误条件SHALL导致任务停止并显示清晰错误消息。错误条件SHALL包括:uv未安装、生成的脚本中的Python语法错误、uv的依赖解析失败、以及脚本运行时错误。
|
||||
|
||||
#### 场景:uv未安装
|
||||
- **WHEN** LLM尝试执行脚本但系统中未找到uv命令
|
||||
- **THEN** skill SHALL显示清晰错误消息:"uv not found"
|
||||
- **THEN** skill SHALL提供uv安装链接:https://docs.astral.sh/uv/getting-started/installation/
|
||||
- **THEN** skill SHALL停止任务
|
||||
|
||||
#### 场景:Python语法错误
|
||||
- **WHEN** LLM生成包含语法错误的Python代码
|
||||
- **THEN** skill SHALL在创建临时文件之前检测语法错误
|
||||
- **THEN** skill SHALL显示包含行号和Python错误描述的错误消息
|
||||
- **THEN** skill SHALL停止任务而不创建临时文件
|
||||
|
||||
#### 场景:依赖解析失败
|
||||
- **WHEN** uv无法解析或安装声明的依赖
|
||||
- **THEN** skill SHALL显示完整的uv错误输出
|
||||
- **THEN** skill SHALL显示临时文件路径,用户可手动删除调试
|
||||
- **THEN** skill SHALL停止任务
|
||||
|
||||
#### 场景:脚本运行时错误
|
||||
- **WHEN** Python脚本执行但在运行时抛出异常
|
||||
- **THEN** skill SHALL显示完整的Python traceback
|
||||
- **THEN** skill SHALL显示临时文件路径,用户可手动删除调试
|
||||
- **THEN** skill SHALL停止任务
|
||||
|
||||
### 需求:无外部输入或参数
|
||||
|
||||
Skill SHALL要求所有输入、参数和数据源都直接嵌入在Python脚本中。Skill SHALL不支持传递给脚本的命令行参数。Skill SHALL不支持从stdin读取输入。所有必要的数据SHALL硬编码或从脚本引用的文件中读取。
|
||||
|
||||
#### 场景:数据处理任务
|
||||
- **WHEN** LLM需要处理数据(例如:CSV文件分析)
|
||||
- **THEN** skill SHALL指导LLM将文件路径作为字符串字面量包含在脚本中
|
||||
- **THEN** skill SHALL不接受文件路径或参数的外部命令行参数
|
||||
- **THEN** 所有数据处理逻辑SHALL嵌入在脚本中
|
||||
|
||||
#### 场景:API交互任务
|
||||
- **WHEN** LLM需要与API交互(例如:对特定URL的GET请求)
|
||||
- **THEN** skill SHALL指导LLM将URL和参数作为字符串字面量包含在脚本中
|
||||
- **THEN** skill SHALL不接受作为命令行参数的API端点或身份验证令牌
|
||||
- **THEN** 所有API交互逻辑SHALL嵌入在脚本中
|
||||
|
||||
### 需求:通用任务适用性
|
||||
|
||||
Skill SHALL适用于任何可以用Python脚本完成的任务。Skill SHALL不限于特定领域,但SHALL为常见用例提供指导,包括数据处理(pandas、numpy)、API交互(requests、httpx)、文件操作(pathlib、shutil)、科学计算(scipy、sympy)和数据转换(json、yaml、csv)。
|
||||
|
||||
#### 场景:数据分析任务
|
||||
- **WHEN** 用户请求数据分析或统计计算
|
||||
- **THEN** skill SHALL提供生成具有适当依赖(例如:pandas)的脚本的指导
|
||||
- **THEN** skill SHALL不基于任务领域进行限制或过滤
|
||||
|
||||
#### 场景:文件操作任务
|
||||
- **WHEN** 用户请求文件操作(重命名、转换、格式化)
|
||||
- **THEN** skill SHALL提供生成具有适当标准库或外部模块的脚本的指导
|
||||
- **THEN** skill SHALL不基于任务领域进行限制或过滤
|
||||
|
||||
#### 场景:API测试任务
|
||||
- **WHEN** 用户请求API测试或数据检索
|
||||
- **THEN** skill SHALL提供生成具有适当HTTP客户端库的脚本的指导
|
||||
- **THEN** skill SHALL不基于任务领域进行限制或过滤
|
||||
111
openspec/specs/uv-run-with-syntax/spec.md
Normal file
111
openspec/specs/uv-run-with-syntax/spec.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# UV Run With Syntax Spec
|
||||
|
||||
## Purpose
|
||||
|
||||
Define requirements for using `uv run --with` syntax to execute Python scripts with automatic dependency parsing, smart project detection, and flexible path handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### 需求:自动解析脚本依赖
|
||||
|
||||
Skill SHALL自动分析Python脚本内容以识别所需的Python包。Skill SHALL解析import语句并提取外部包名,排除Python标准库模块。
|
||||
|
||||
#### 场景:解析包含外部依赖的脚本
|
||||
- **WHEN** Skill分析包含import语句的Python脚本(例如:`import pandas as pd`)
|
||||
- **THEN** Skill SHALL提取包名(pandas)
|
||||
- **THEN** Skill SHALL排除Python标准库模块(os、sys、json等)
|
||||
- **THEN** Skill SHALL生成依赖列表,用于`--with`参数
|
||||
|
||||
#### 场景:解析多个import来源
|
||||
- **WHEN** 脚本包含多种import语句(`import pandas`, `from numpy import array`, `import requests as req`)
|
||||
- **THEN** Skill SHALL正确提取所有外部包名(pandas, numpy, requests)
|
||||
- **THEN** Skill SHALL处理`import ... as`和`from ... import`语法
|
||||
- **THEN** Skill SHALL去重依赖列表
|
||||
|
||||
#### 场景:仅使用标准库的脚本
|
||||
- **WHEN** 脚本只使用Python标准库(os、sys、json、pathlib等)
|
||||
- **THEN** Skill SHALL生成空的依赖列表
|
||||
- **THEN** Skill SHALL不添加任何`--with`参数到执行命令中
|
||||
|
||||
### 需求:智能项目检测
|
||||
|
||||
Skill SHALL检测当前工作目录是否为uv项目,并据此选择执行策略。
|
||||
|
||||
#### 场景:检测到uv项目
|
||||
- **WHEN** 当前工作目录包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL识别为uv项目
|
||||
- **THEN** Skill SHALL对当前目录下的脚本使用`uv run script.py`直接执行
|
||||
- **THEN** Skill SHALL不添加`--with`参数(依赖由项目pyproject.toml管理)
|
||||
|
||||
#### 场景:非uv项目环境
|
||||
- **WHEN** 当前工作目录不包含`pyproject.toml`文件
|
||||
- **THEN** Skill SHALL识别为非uv项目
|
||||
- **THEN** Skill SHALL对脚本使用`uv run --with package1 --with package2 script.py`执行
|
||||
- **THEN** Skill SHALL根据解析的依赖列表动态构建`--with`参数
|
||||
|
||||
#### 场景:脚本位于工作目录外
|
||||
- **WHEN** 用户指定的脚本路径不在当前工作目录下
|
||||
- **THEN** Skill SHALL按独立脚本处理
|
||||
- **THEN** Skill SHALL使用`uv run --with package1 --with package2 <script_path>`执行
|
||||
- **THEN** Skill SHALL解析脚本内容以获取依赖列表
|
||||
|
||||
### 需求:灵活路径处理
|
||||
|
||||
Skill SHALL根据用户指定和上下文灵活处理脚本路径,支持用户指定路径、现有脚本文件和临时脚本。
|
||||
|
||||
#### 场景:用户指定脚本存储路径
|
||||
- **WHEN** 用户明确指定脚本存储路径(例如:"在scripts/data_processing.py中写入脚本")
|
||||
- **THEN** Skill SHALL将脚本内容写入用户指定的路径
|
||||
- **THEN** Skill SHALL不使用临时文件
|
||||
- **THEN** Skill SHALL在指定路径执行脚本
|
||||
|
||||
#### 场景:用户指定现有脚本路径
|
||||
- **WHEN** 用户指定要运行的现有Python脚本(例如:"运行scripts/my_script.py")
|
||||
- **THEN** Skill SHALL读取该脚本内容(如需解析依赖)
|
||||
- **THEN** Skill SHALL直接在指定路径执行脚本
|
||||
- **THEN** Skill SHALL不修改用户脚本内容
|
||||
|
||||
#### 场景:大模型自主生成脚本且未指定路径
|
||||
- **WHEN** 大模型自主生成Python脚本且用户未指定存储路径
|
||||
- **THEN** Skill SHALL使用辅助脚本获取临时文件路径
|
||||
- **THEN** Skill SHALL将脚本内容写入临时文件
|
||||
- **THEN** Skill SHALL在临时路径执行脚本
|
||||
- **THEN** Skill SHALL允许系统自动清理临时文件
|
||||
|
||||
### 需求:使用--with语法执行
|
||||
|
||||
Skill SHALL使用`uv run --with`语法将依赖传递给uv,替代PEP 723内联元数据方式。
|
||||
|
||||
#### 场景:有外部依赖的脚本执行
|
||||
- **WHEN** 脚本解析结果包含依赖列表[package1, package2]
|
||||
- **THEN** Skill SHALL构造命令:`uv run --with package1 --with package2 <script_path>`
|
||||
- **THEN** uv SHALL为每个`--with`指定的包创建隔离环境
|
||||
- **THEN** Skill SHALL捕获并返回脚本输出
|
||||
|
||||
#### 场景:无外部依赖的脚本执行
|
||||
- **WHEN** 脚本解析结果为空依赖列表
|
||||
- **THEN** Skill SHALL构造命令:`uv run <script_path>`
|
||||
- **THEN** Skill SHALL不包含任何`--with`参数
|
||||
- **THEN** uv SHALL使用隔离的Python标准库环境执行
|
||||
|
||||
#### 场景:uv项目内的脚本执行
|
||||
- **WHEN** 脚本位于检测到的uv项目内
|
||||
- **THEN** Skill SHALL构造命令:`uv run <script_path>`(相对路径)
|
||||
- **THEN** Skill SHALL不包含`--with`参数
|
||||
- **THEN** uv SHALL使用项目的虚拟环境和依赖配置
|
||||
|
||||
### 需求:向后兼容性
|
||||
|
||||
Skill SHALL保持对包含PEP 723元数据的旧脚本的兼容性,但不再要求使用该格式。
|
||||
|
||||
#### 场景:执行包含PEP 723元数据的脚本
|
||||
- **WHEN** 用户提供的脚本顶部包含PEP 723元数据块
|
||||
- **THEN** Skill SHALL正常执行脚本(PEP 723元数据将被uv忽略)
|
||||
- **THEN** Skill SHALL仍使用`--with`语法传递解析出的依赖
|
||||
- **THEN** 脚本SHALL成功执行
|
||||
|
||||
#### 场景:新工作流执行标准Python脚本
|
||||
- **WHEN** 用户提供的标准Python脚本无任何元数据
|
||||
- **THEN** Skill SHALL自动解析import语句
|
||||
- **THEN** Skill SHALL使用`--with`语法传递依赖
|
||||
- **THEN** 脚本SHALL在不修改的情况下成功执行
|
||||
326
skills/js-runner/SKILL.md
Normal file
326
skills/js-runner/SKILL.md
Normal file
@@ -0,0 +1,326 @@
|
||||
---
|
||||
name: js-runner
|
||||
description: Any task that requires Javascript/Typescript processing should use this skill.
|
||||
compatibility: Requires Bun runtime (https://bun.sh)
|
||||
---
|
||||
|
||||
# js-runner
|
||||
|
||||
基于 Bun 的 JavaScript/TypeScript 执行技能,提供隔离的脚本执行和自动依赖管理。
|
||||
|
||||
## 快速参考
|
||||
|
||||
根据您的需求选择使用方式:
|
||||
|
||||
| 场景 | 描述 | 命令 |
|
||||
| --------- | ------------------------ | ---------------------------------- |
|
||||
| **场景1** | 直接执行已存在的脚本文件 | `bun <script-file>` |
|
||||
| **场景2** | 在指定路径创建脚本并执行 | 使用 Write 工具创建 → `bun <path>` |
|
||||
| **场景3** | 使用临时路径执行(默认) | 生成临时路径 → `bun <temp-file>` |
|
||||
|
||||
**重要提示:** 所有场景在执行脚本前都必须先检查 Bun 环境:`bun --version`
|
||||
|
||||
## 前置条件
|
||||
|
||||
### 安装 Bun
|
||||
|
||||
js-runner 需要安装 Bun。Bun 是一个快速的 JavaScript 运行时,内置包管理器。
|
||||
|
||||
**macOS/Linux:**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
```powershell
|
||||
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||
```
|
||||
|
||||
**验证安装:**
|
||||
|
||||
```bash
|
||||
bun --version
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
**执行流程决策树:**
|
||||
|
||||
1. **步骤1:检查 Bun 环境**
|
||||
- 执行:`bun --version`
|
||||
- 失败 → 输出错误信息(包含安装说明)并停止执行
|
||||
- 成功 → 进入下一步
|
||||
- **重要:** 禁止使用 nodejs、npm、yarn、pnpm 等其他工具
|
||||
|
||||
2. **步骤2:选择执行场景**
|
||||
- 场景1:用户提供了已存在的脚本文件路径?
|
||||
- 是 → 直接执行:`bun <script-file>`
|
||||
- 否 → 进入下一步
|
||||
- 场景2:用户指定了脚本的生成路径?
|
||||
- 是 → 使用 Write 工具创建脚本,然后执行
|
||||
- 否 → 进入场景3
|
||||
|
||||
3. **场景3(默认)**:使用临时路径执行
|
||||
- 生成临时文件路径
|
||||
- 将脚本内容写入临时文件
|
||||
- 使用 Bun 运行脚本
|
||||
- 临时文件由系统自动处理
|
||||
|
||||
---
|
||||
|
||||
### 场景1:执行已存在的脚本文件
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 直接执行已存在的脚本
|
||||
bun ./scripts/my-script.js
|
||||
|
||||
# 脚本的输出将自动显示
|
||||
```
|
||||
|
||||
**关键特点:**
|
||||
- ✅ **无需生成临时文件** - 直接执行用户提供的脚本
|
||||
- ✅ **保持脚本位置** - 脚本留在原位置,不会被移动或复制
|
||||
- ✅ **简洁快速** - 跳过文件生成步骤,直接执行
|
||||
|
||||
### 场景2:在指定路径创建并执行脚本
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 使用 Write 工具在指定路径创建脚本
|
||||
# (以下步骤由大模型使用 Write 工具完成)
|
||||
# write content to "./scripts/new-script.js"
|
||||
|
||||
const greeting = "Hello from custom path!";
|
||||
console.log(greeting);
|
||||
|
||||
# 步骤 3: 执行脚本
|
||||
bun ./scripts/new-script.js
|
||||
```
|
||||
|
||||
**关键特点:**
|
||||
- ✅ **自定义路径** - 脚本创建到用户指定的位置
|
||||
- ✅ **持久化存储** - 脚本文件保存在指定位置,不会被自动清理
|
||||
- ✅ **灵活控制** - 用户可以精确控制脚本位置和命名
|
||||
|
||||
### 场景3:使用临时路径执行(默认流程)
|
||||
|
||||
当用户未提供任何路径信息时,使用临时路径执行脚本(默认流程):
|
||||
|
||||
#### 基础示例
|
||||
|
||||
```bash
|
||||
# 步骤 1: 检查 Bun 是否已安装
|
||||
bun --version
|
||||
|
||||
# 步骤 2: 生成临时文件路径
|
||||
TEMP_FILE=$(bun skills/js-runner/scripts/get_temp_path.js js)
|
||||
|
||||
# 步骤 3: 将脚本内容写入临时文件
|
||||
cat <<EOF > "$TEMP_FILE"
|
||||
const greeting = "Hello from js-runner!";
|
||||
console.log(greeting);
|
||||
EOF
|
||||
|
||||
# 步骤 4: 执行脚本
|
||||
bun "$TEMP_FILE"
|
||||
|
||||
# 步骤 5: 输出已在上面捕获
|
||||
# 临时文件将由系统自动清理
|
||||
```
|
||||
|
||||
#### TypeScript 示例
|
||||
|
||||
```bash
|
||||
# 生成 TypeScript 临时文件
|
||||
TEMP_TS=$(bun skills/js-runner/scripts/get_temp_path.js ts)
|
||||
|
||||
# 写入 TypeScript 脚本
|
||||
cat <<EOF > "$TEMP_TS"
|
||||
const message: string = 'TypeScript execution';
|
||||
console.log(message);
|
||||
EOF
|
||||
|
||||
# 执行 - Bun 会自动转译 TypeScript
|
||||
bun "$TEMP_TS"
|
||||
```
|
||||
|
||||
**Bun 自动处理:**
|
||||
- 检测 `import` 语句
|
||||
- 即时转译 TypeScript
|
||||
- 下载并缓存依赖(到 `~/.bun/install/cache`)
|
||||
- 无需 `package.json` 或手动安装
|
||||
|
||||
## 依赖管理
|
||||
|
||||
Bun 提供自动依赖管理,无需手动配置:
|
||||
|
||||
### 导入外部包
|
||||
|
||||
```javascript
|
||||
// ESM import(推荐)
|
||||
import axios from 'axios'
|
||||
import lodash from 'lodash'
|
||||
|
||||
// CommonJS import(也支持)
|
||||
const axios = require('axios')
|
||||
```
|
||||
|
||||
首次执行带有外部导入的脚本时,Bun 会:
|
||||
|
||||
1. 分析导入
|
||||
2. 从 npm 下载缺失的依赖
|
||||
3. 全局缓存到 `~/.bun/install/cache`
|
||||
4. 后续运行使用缓存版本
|
||||
|
||||
### 不需要 package.json
|
||||
|
||||
与 Node.js 不同,你无需创建 `package.json` 或单独运行 `bun install`。Bun 在运行时自动处理所有操作。
|
||||
|
||||
## 辅助函数 API
|
||||
|
||||
### `get_temp_path.js`
|
||||
|
||||
为脚本执行生成唯一的临时文件路径。
|
||||
|
||||
**CLI 使用方式:**
|
||||
|
||||
```bash
|
||||
bun skills/js-runner/scripts/get_temp_path.js <extension>
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `extension` (可选): 文件扩展名。默认为 `js`。常用值: `js`, `ts`, `mjs`, `mts`
|
||||
|
||||
**输出:** 返回类似 `/var/folders/.../js-runner-1234567890-abc123.js` 的路径
|
||||
|
||||
**路径格式:**
|
||||
|
||||
- 使用操作系统临时目录(Unix 上为 `/tmp`,Windows 上为 `%TEMP%`)
|
||||
- 前缀: `js-runner-`
|
||||
- 时间戳: 自纪元以来的毫秒数
|
||||
- 随机字符串: 7 字符字母数字
|
||||
- 扩展名: 参数中提供的值
|
||||
|
||||
**示例:**
|
||||
|
||||
```bash
|
||||
$ bun skills/js-runner/scripts/get_temp_path.js js
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/js-runner-1770257905333-na6ujx.js
|
||||
|
||||
$ bun skills/js-runner/scripts/get_temp_path.js ts
|
||||
/var/folders/8m/0hm18pdd7ts2bwp0530drz500000gn/T/js-runner-1770257905333-v8yzt.ts
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 未安装 Bun
|
||||
|
||||
**症状:** `bun --version` 失败或返回 "command not found: bun"
|
||||
|
||||
**错误处理:**
|
||||
|
||||
当检测到 Bun 未安装时,必须:
|
||||
|
||||
1. **停止执行** - 不进行任何后续操作
|
||||
2. **输出明确错误信息** - 清晰说明 "Bun 运行时未安装" 或类似提示
|
||||
3. **提供安装说明** - 参考"前置条件"章节的安装命令
|
||||
|
||||
**重要限制:**
|
||||
|
||||
- ❌ **禁止自动安装** - 不要尝试自动安装 Bun,由用户自行决定
|
||||
- ❌ **禁止使用其他工具** - 不要尝试使用 nodejs、npm、yarn、pnpm 等其他 JavaScript 运行时或包管理工具
|
||||
- ❌ **禁止格式转换** - 不要建议用户将脚本转换为其他运行时格式
|
||||
|
||||
**正确做法:**
|
||||
|
||||
- ✅ 仅输出错误信息和安装说明
|
||||
- ✅ 等待用户安装 Bun 后再继续
|
||||
- ✅ 保持使用 Bun 作为唯一运行时
|
||||
|
||||
### 脚本语法错误
|
||||
|
||||
Bun 提供详细的语法错误信息:
|
||||
|
||||
```bash
|
||||
$ bun "$TEMP_FILE"
|
||||
error: Unexpected token
|
||||
--> /var/folders/.../script.js:2:10
|
||||
|
|
||||
2 | const = 123;
|
||||
| ^
|
||||
```
|
||||
|
||||
错误信息包括:
|
||||
|
||||
- 文件路径和行号
|
||||
- 错误的确切位置
|
||||
- 问题描述
|
||||
|
||||
### 运行时错误
|
||||
|
||||
运行时错误包含完整的堆栈跟踪:
|
||||
|
||||
```bash
|
||||
$ bun "$TEMP_FILE"
|
||||
ReferenceError: foo is not defined
|
||||
at script.js:3:5
|
||||
at main (script.js:1:1)
|
||||
```
|
||||
|
||||
### 其他错误
|
||||
|
||||
其他任何形式的错误都原样输出
|
||||
|
||||
## 输出处理
|
||||
|
||||
### 标准输出
|
||||
|
||||
所有 `console.log()`, `console.info()`, `console.warn()` 输出都到 stdout:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" # stdout 由调用代码捕获
|
||||
```
|
||||
|
||||
### 错误输出
|
||||
|
||||
`console.error()` 输出到 stderr:
|
||||
|
||||
```bash
|
||||
bun "$TEMP_FILE" 2>error.log # 单独捕获 stderr
|
||||
```
|
||||
|
||||
### 退出码
|
||||
|
||||
脚本可以设置自定义退出码:
|
||||
|
||||
```javascript
|
||||
process.exit(1) // 错误
|
||||
process.exit(0) // 成功
|
||||
```
|
||||
|
||||
调用者接收这些退出码以确定执行状态。
|
||||
|
||||
## 临时文件管理
|
||||
|
||||
执行后 **不会主动删除** 临时文件。这是设计使然:
|
||||
|
||||
- 操作系统自动管理临时目录空间
|
||||
- 文件可以保留用于调试目的
|
||||
- 大多数操作系统定期清理旧的临时文件
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **始终先检查 Bun 环境** - 所有场景第一步都执行 `bun --version`
|
||||
2. **根据用户意图选择场景** - 查看快速参考选择合适的使用方式
|
||||
3. **单独处理 stdout/stderr** - 以区分输出和错误
|
||||
4. **检查退出码** - 以检测脚本失败
|
||||
5. **使用 ESM imports** - 使用 `import from` 编写现代 JavaScript
|
||||
6. **捕获并显示错误** - 以帮助用户调试问题
|
||||
14
skills/js-runner/scripts/get_temp_path.js
Normal file
14
skills/js-runner/scripts/get_temp_path.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
export function getTempPath(extension) {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
return join(tmpdir(), `js-runner-${timestamp}-${random}.${extension}`);
|
||||
}
|
||||
|
||||
// CLI interface: accepts extension as first argument
|
||||
if (import.meta.main) {
|
||||
const extension = process.argv[2] || "js";
|
||||
console.log(getTempPath(extension));
|
||||
}
|
||||
247
skills/python-runner/SKILL.md
Normal file
247
skills/python-runner/SKILL.md
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
name: python-runner
|
||||
description: Any task that requires Python processing should use this skill.
|
||||
---
|
||||
|
||||
# UV Python Runner Skill
|
||||
|
||||
指导大模型使用uv运行Python脚本,无需预安装依赖,保持环境整洁。
|
||||
|
||||
## Purpose
|
||||
|
||||
**必需依赖**: 此skill必需uv工具,不兼容其他Python运行方式。
|
||||
|
||||
利用uv隔离环境特性:
|
||||
- 自动管理虚拟环境和依赖
|
||||
- 避免系统Python污染
|
||||
- 支持用户现有脚本(无需修改)
|
||||
- 自动解析依赖并使用`--with`传递
|
||||
- 智能检测uv项目,复用项目环境
|
||||
- 跨平台兼容(Windows/macOS/Linux)
|
||||
|
||||
**重要**: 如果uv未安装,立即停止任务并引导用户安装。不要使用python、pip、poetry、venv等替代工具。
|
||||
|
||||
## When to Use
|
||||
|
||||
任何Python处理任务都应使用此skill。
|
||||
|
||||
### 典型场景
|
||||
- **数据处理**: pandas, numpy, scipy / CSV/JSON/YAML转换 / 统计分析
|
||||
- **API交互**: HTTP请求(requests, httpx) / API数据检索
|
||||
- **文件操作**: 重命名、批量处理 / pathlib, shutil操作
|
||||
- **科学计算**: numpy, scipy / matplotlib, plotly可视化
|
||||
- **系统工具**: logging / configparser / tqdm, rich进度跟踪
|
||||
|
||||
### 不适用场景
|
||||
- ✗ 需要用户交互(input())
|
||||
- ✗ 需要持久化环境(每次都是新环境)
|
||||
- ✗ 需要命令行参数
|
||||
- ✗ 需要从stdin读取
|
||||
|
||||
## Automatic Dependency Parsing
|
||||
|
||||
分析import语句,提取外部包名,排除标准库。
|
||||
|
||||
### 标准库(排除)
|
||||
**核心**: os, sys, pathlib, shutil, json, csv, re, datetime, math
|
||||
**网络**: http.client, urllib, socket, io, logging
|
||||
**高级**: itertools, functools, typing, dataclasses, enum
|
||||
|
||||
### 解析规则
|
||||
1. 提取:`import pandas` → `pandas`, `from numpy import array` → `numpy`
|
||||
2. 排除标准库
|
||||
3. 去重
|
||||
|
||||
### 示例
|
||||
```python
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import json # 标准库,排除
|
||||
from pathlib import Path # 标准库,排除
|
||||
|
||||
# 结果: [pandas, numpy]
|
||||
```
|
||||
|
||||
## Smart Project Detection
|
||||
|
||||
### 检测命令
|
||||
```bash
|
||||
uv sync --dry-run
|
||||
```
|
||||
|
||||
### 判断逻辑
|
||||
- Exit code 0 → uv项目
|
||||
- 非零退出码 → 非uv项目
|
||||
- 失败 → 回退到非uv项目模式(使用`--with`),不阻塞执行
|
||||
|
||||
## Path Handling
|
||||
|
||||
### 三层逻辑
|
||||
1. **用户指定存储路径** → 写入指定路径
|
||||
2. **用户指定现有脚本** → 直接执行
|
||||
3. **未指定** → 临时文件
|
||||
|
||||
```bash
|
||||
# 临时文件获取
|
||||
temp_file_path=$(uv run ./script/get_temp_path.py)
|
||||
```
|
||||
|
||||
## Execution Commands
|
||||
|
||||
| 场景 | 命令 |
|
||||
|------|------|
|
||||
| uv项目 | `uv run <script_path>` |
|
||||
| 非uv+有依赖 | `uv run --with pkg1 --with pkg2 <script_path>` |
|
||||
| 非uv+无依赖 | `uv run <script_path>` |
|
||||
|
||||
**特点**:
|
||||
- uv项目:使用项目环境,无`--with`
|
||||
- 非uv有依赖:每个`--with`创建隔离环境
|
||||
- 非uv无依赖:使用标准Python环境
|
||||
|
||||
## Workflow
|
||||
|
||||
**步骤1**: 解析依赖(见"Automatic Dependency Parsing")
|
||||
|
||||
**步骤2**: 检测项目(见"Smart Project Detection")
|
||||
|
||||
**步骤3**: 确定路径(见"Path Handling")
|
||||
|
||||
**步骤4**: 构造并执行命令(见"Execution Commands")
|
||||
|
||||
执行命令并捕获输出。
|
||||
|
||||
## Error Handling
|
||||
|
||||
### uv未安装
|
||||
|
||||
**检测**: `uv`命令失败
|
||||
|
||||
**错误消息**:
|
||||
```
|
||||
uv not found
|
||||
|
||||
此skill依赖uv工具运行Python脚本。
|
||||
|
||||
请安装uv: https://docs.astral.sh/uv/getting-started/installation/
|
||||
|
||||
安装命令示例:
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh # Linux/macOS
|
||||
powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
|
||||
```
|
||||
|
||||
**重要提示**:
|
||||
- **立即停止任务**,等待用户安装uv后再继续
|
||||
- **不要尝试使用**:python, pip, poetry, venv, virtualenv等
|
||||
- **不要自动安装**uv
|
||||
- 用户安装uv完成后,可以重新执行任务
|
||||
|
||||
**操作**: 立即停止所有执行,等待用户安装uv
|
||||
|
||||
### 其他错误
|
||||
|
||||
| 场景 | 错误消息 | 操作 |
|
||||
|------|---------|------|
|
||||
| 项目检测失败 | 回退到非uv模式,使用`--with` | 警告后继续 |
|
||||
| 依赖解析不准确 | 依赖可能不完整<br>Traceback: [traceback] | 停止,保留脚本调试 |
|
||||
| 语法错误 | Python语法错误: [描述]<br>文件: <path><br>行号: <line> | 停止 |
|
||||
| 路径权限问题 | 无法写入: <path><br>建议: 使用临时文件模式 | 回退到临时文件 |
|
||||
|
||||
## Examples
|
||||
|
||||
### 示例1: 数据分析
|
||||
```python
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
df = pd.read_csv('data.csv')
|
||||
print(f"形状: {df.shape}")
|
||||
print(df.describe())
|
||||
```
|
||||
**执行**: `uv run --with pandas --with numpy /tmp/script_xxx.py`
|
||||
|
||||
### 示例2: API交互
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.get('https://api.github.com/repos/python/cpython')
|
||||
data = resp.json()
|
||||
print(f"仓库: {data['full_name']}, Stars: {data['stargazers_count']}")
|
||||
```
|
||||
**执行**: `uv run --with requests /tmp/script_xxx.py`
|
||||
|
||||
### 示例3: 文件操作
|
||||
```python
|
||||
import os
|
||||
import glob
|
||||
|
||||
for i, file in enumerate(glob.glob('*.txt')):
|
||||
os.rename(file, f"file_{i:03d}.txt")
|
||||
```
|
||||
**执行**: `uv run /tmp/script_xxx.py`(无依赖)
|
||||
|
||||
### 示例4: uv项目内执行
|
||||
```python
|
||||
import pandas as pd
|
||||
from my_project import helper
|
||||
|
||||
df = pd.read_csv('data.csv')
|
||||
result = helper.process(df)
|
||||
print(result)
|
||||
```
|
||||
**执行**: `uv run scripts/data_process.py`(使用项目环境)
|
||||
|
||||
### 示例5: 用户指定路径
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.get('https://api.example.com/data')
|
||||
print(f"处理完成: {len(resp.json())} 条")
|
||||
```
|
||||
**执行**: `uv run --with requests scripts/api_analyzer.py`(写入指定路径)
|
||||
|
||||
## Notes
|
||||
|
||||
### 为什么使用uv?
|
||||
| 特性 | 优势 |
|
||||
|------|------|
|
||||
| 环境隔离 | 不污染系统Python |
|
||||
| 自动依赖 | `--with`语法,无需pip install |
|
||||
| 快速启动 | 比venv快10-100倍 |
|
||||
| 项目集成 | 自动检测uv项目 |
|
||||
| 零配置 | 开箱即用,无需PEP 723 |
|
||||
|
||||
### 最佳实践
|
||||
1. **依赖解析**: 排除标准库;失败时检查遗漏依赖;复杂项目用uv项目模式
|
||||
2. **路径**: 用户指定优先;临时文件用于自主生成;跨平台由辅助脚本保证
|
||||
3. **错误**: 预期错误脚本内处理;意外错误立即停止;检测失败自动回退
|
||||
4. **清理**: 临时文件使用系统目录,自动清理,失败时手动删除
|
||||
|
||||
### 限制
|
||||
- ✗ 不支持命令行参数、stdin输入、持久化环境
|
||||
- ✗ 使用uv默认Python版本(项目可在pyproject.toml指定)
|
||||
- ✗ 依赖解析可能不完整(动态导入、条件导入可能遗漏)
|
||||
- ✗ 项目检测可能误判(网络问题导致回退)
|
||||
|
||||
### uv工具要求
|
||||
|
||||
- **uv是此skill的必需依赖,不可替代**
|
||||
- **不支持**: python, pip, poetry, venv, virtualenv
|
||||
- 如果检测到uv未安装,必须停止任务并引导用户安装
|
||||
- 不要尝试使用替代方案或自动安装uv
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
**完整流程**:
|
||||
1. 解析import语句,提取外部包名(排除标准库)
|
||||
2. 执行`uv sync --dry-run`检测项目
|
||||
3. 确定脚本路径(用户指定/现有脚本/临时文件)
|
||||
4. 构造执行命令(根据项目类型和依赖)
|
||||
5. 执行并捕获输出
|
||||
|
||||
**关键特点**:
|
||||
- 自动依赖解析:分析import自动提取
|
||||
- 智能项目检测:自动识别uv项目
|
||||
- 灵活路径:支持指定/现有/临时三种模式
|
||||
- 跨平台:自动适配Windows/macOS/Linux
|
||||
- 环境隔离:独立虚拟环境
|
||||
@@ -1,372 +0,0 @@
|
||||
---
|
||||
name: uv-python-runner
|
||||
description: Any task that requires Python processing should use this skill.
|
||||
---
|
||||
|
||||
# UV Python Runner Skill
|
||||
|
||||
通用型工具skill,指导大模型使用uv运行Python脚本来处理各种任务,无需预安装依赖,保持系统环境整洁。
|
||||
|
||||
## Purpose
|
||||
|
||||
指导大模型在需要执行Python脚本时,利用uv的隔离环境特性来:
|
||||
|
||||
- 自动管理虚拟环境和依赖
|
||||
- 避免在系统Python环境安装依赖包
|
||||
- 使用临时文件,执行完成后立即清理
|
||||
- 保持跨平台兼容性(Windows/macOS/Linux)
|
||||
|
||||
## When to Use
|
||||
|
||||
大模型**任何需要用Python处理的任务**都应该使用此skill。
|
||||
|
||||
### 典型场景
|
||||
|
||||
**数据处理**
|
||||
|
||||
- pandas, numpy, scipy等数据分析
|
||||
- CSV, JSON, YAML文件转换和处理
|
||||
- 数据清洗、统计分析、可视化
|
||||
|
||||
**API交互**
|
||||
|
||||
- HTTP请求和测试(requests, httpx, aiohttp)
|
||||
- API数据检索和验证
|
||||
- 身份验证和会话管理
|
||||
|
||||
**文件操作**
|
||||
|
||||
- 文件重命名、批量处理
|
||||
- 路径操作(pathlib, shutil, os)
|
||||
- 文件格式转换、内容替换
|
||||
|
||||
**科学计算**
|
||||
|
||||
- 数学计算(numpy, scipy)
|
||||
- 符号计算(sympy)
|
||||
- 数据可视化(matplotlib, plotly)
|
||||
|
||||
**系统工具**
|
||||
|
||||
- 日志处理(logging)
|
||||
- 配置管理(configparser)
|
||||
- 进度跟踪(tqdm, rich)
|
||||
|
||||
### 不适用场景
|
||||
|
||||
- ✗ 需要用户交互的脚本(input(), input()等)
|
||||
- ✗ 需要持久化环境(每次都是新的隔离环境)
|
||||
- ✗ 需要传递命令行参数(所有参数嵌入脚本)
|
||||
- ✗ 需要从stdin读取输入
|
||||
|
||||
## Workflow
|
||||
|
||||
### 步骤1:生成符合PEP 723的Python脚本
|
||||
|
||||
在脚本顶部添加内联元数据块:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "package-name-1",
|
||||
# "package-name-2",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import package1
|
||||
import package2
|
||||
|
||||
# 你的代码...
|
||||
```
|
||||
|
||||
**规则:**
|
||||
|
||||
- ✓ 总是包含`# /// script`块
|
||||
- ✓ 列出所有**外部**依赖
|
||||
- ✓ 如果没有依赖:`# dependencies = []`
|
||||
- ✓ 不指定版本(让uv使用最新)
|
||||
- ✓ 不指定Python版本(使用uv默认)
|
||||
|
||||
**示例:**
|
||||
|
||||
有外部依赖:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "pandas",
|
||||
# "numpy",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
data = pd.read_csv('data.csv')
|
||||
print(data.describe())
|
||||
```
|
||||
|
||||
仅使用标准库:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
with open('data.json') as f:
|
||||
data = json.load(f)
|
||||
print(f"Keys: {list(data.keys())}")
|
||||
```
|
||||
|
||||
### 步骤2:获取临时脚本文件路径
|
||||
|
||||
调用辅助脚本创建临时Python脚本文件并获取文件路径(使用相对路径):
|
||||
|
||||
```bash
|
||||
temp_file_path=$(uv run ./script/get_temp_path.py)
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
- 临时Python脚本文件的完整路径
|
||||
- Linux/macOS: `/tmp/uv_script_xxx.py`
|
||||
- Windows: `C:\Users\<username>\AppData\Local\Temp\uv_script_xxx.py`
|
||||
|
||||
**说明:**
|
||||
|
||||
- 辅助脚本已在临时目录创建了空的Python脚本文件
|
||||
- 大模型直接得到脚本文件路径
|
||||
- 无需拼接路径,直接使用返回的文件路径
|
||||
|
||||
### 步骤3:写入PEP 723脚本内容
|
||||
|
||||
使用大模型的文件创建工具(Write等)在步骤2返回的脚本文件路径中写入PEP 723脚本内容。
|
||||
|
||||
### 步骤4:使用uv执行
|
||||
|
||||
```bash
|
||||
uv run <temp_file_path>
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### 场景1:uv未安装
|
||||
|
||||
**错误消息:**
|
||||
|
||||
```
|
||||
uv not found
|
||||
|
||||
无法找到uv命令。请先安装uv:
|
||||
https://docs.astral.sh/uv/getting-started/installation/
|
||||
```
|
||||
|
||||
**操作:** 停止任务
|
||||
|
||||
### 场景2:Python语法错误
|
||||
|
||||
**检测:** 在创建临时文件之前检测语法错误
|
||||
|
||||
**错误消息:**
|
||||
|
||||
```
|
||||
Python语法错误:[错误描述]
|
||||
文件:<script_path>
|
||||
行号:<line_number>
|
||||
错误:<python_error_message>
|
||||
|
||||
请检查Python代码的语法正确性。
|
||||
```
|
||||
|
||||
### 场景3:依赖解析失败
|
||||
|
||||
**错误消息:**
|
||||
|
||||
```
|
||||
依赖解析失败
|
||||
|
||||
uv错误输出:
|
||||
[完整的uv错误信息]
|
||||
|
||||
临时文件保留用于调试:<temp_file_path>
|
||||
```
|
||||
|
||||
### 场景4:脚本运行时错误
|
||||
|
||||
**错误消息:**
|
||||
|
||||
```
|
||||
脚本执行失败
|
||||
|
||||
Traceback (most recent call last):
|
||||
[完整的Python traceback]
|
||||
|
||||
临时文件保留用于调试:<temp_file_path>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### 示例1:数据分析
|
||||
|
||||
**场景:** 分析CSV文件的统计信息
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "pandas",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_csv('data.csv')
|
||||
print(f"数据形状: {df.shape}")
|
||||
print(f"描述统计:\n{df.describe()}")
|
||||
```
|
||||
|
||||
**执行流程:**
|
||||
|
||||
1. 调用辅助脚本获取临时目录
|
||||
2. 构造临时文件路径
|
||||
3. 创建文件并写入上述内容
|
||||
4. 执行:`uv run <temp_file_path>`
|
||||
5. 捕获输出
|
||||
|
||||
### 示例2:API交互
|
||||
|
||||
**场景:** 从GitHub API获取仓库信息
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "requests",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import requests
|
||||
|
||||
resp = requests.get('https://api.github.com/repos/python/cpython')
|
||||
data = resp.json()
|
||||
|
||||
print(f"仓库: {data['full_name']}")
|
||||
print(f"Star数: {data['stargazers_count']}")
|
||||
print(f"描述: {data['description'][:100]}...")
|
||||
```
|
||||
|
||||
### 示例3:文件操作
|
||||
|
||||
**场景:** 批量重命名文件
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
import os
|
||||
import glob
|
||||
from pathlib import Path
|
||||
|
||||
for i, file in enumerate(glob.glob('*.txt')):
|
||||
new_name = f"file_{i:03d}.txt"
|
||||
os.rename(file, new_name)
|
||||
print(f"✓ {file} → {new_name}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### 为什么使用uv?
|
||||
|
||||
| 特性 | 优势 |
|
||||
| ------------ | ------------------------------------------------ |
|
||||
| 环境隔离 | 不污染系统Python环境,每个脚本都有独立的虚拟环境 |
|
||||
| 自动依赖管理 | 无需手动pip install,uv自动解析和安装依赖 |
|
||||
| 快速启动 | 比传统venv快10-100倍,快速创建和销毁环境 |
|
||||
| 标准兼容 | 支持PEP 723格式,官方Python规范 |
|
||||
| 零配置 | 开箱即用,无需额外配置或初始化 |
|
||||
|
||||
### 最佳实践
|
||||
|
||||
1. **总使用内联元数据**
|
||||
|
||||
```python
|
||||
# 即使没有依赖也要声明
|
||||
# dependencies = []
|
||||
```
|
||||
|
||||
2. **使用最新版本**
|
||||
- 不指定版本约束
|
||||
- 让uv自动选择
|
||||
- 保持依赖更新和安全
|
||||
|
||||
3. **错误处理**
|
||||
- 脚本内部处理预期的错误(try-except)
|
||||
- 严格模式处理意外的错误(立即停止)
|
||||
|
||||
4. **清理资源**
|
||||
- 临时文件使用系统临时目录(/tmp 或 Windows Temp)
|
||||
- 系统会自动清理临时文件,无需手动管理
|
||||
- 失败时可手动删除临时文件调试
|
||||
|
||||
### 限制
|
||||
|
||||
- ✗ 不支持命令行参数
|
||||
- 所有参数必须嵌入在脚本中
|
||||
- 不支持`uv run script.py arg1 arg2`
|
||||
|
||||
- ✗ 不支持stdin输入
|
||||
- 不支持`echo "code" | uv run -`
|
||||
- 所有数据必须硬编码或从文件读取
|
||||
|
||||
- ✗ 不支持持久化环境
|
||||
- 每次执行都是新的临时环境
|
||||
- 不缓存或保留虚拟环境
|
||||
|
||||
- ✗ 不支持自定义Python版本
|
||||
- 使用uv的默认Python版本
|
||||
- 不在元数据中指定`requires-python`
|
||||
|
||||
- ✗ 不支持复杂的依赖约束
|
||||
- 只支持简单的包名
|
||||
- 不支持版本范围(`>=1.0,<2.0`)
|
||||
- 不支持Git URL或本地包
|
||||
|
||||
## Dependencies
|
||||
|
||||
### 必需依赖
|
||||
|
||||
- **uv** (https://docs.astral.sh/uv/)
|
||||
- Python包管理器和运行器
|
||||
- 支持PEP 723内联元数据格式
|
||||
- 提供环境隔离和自动依赖管理
|
||||
|
||||
### 可选依赖
|
||||
|
||||
无
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
完整的典型工作流:
|
||||
|
||||
```bash
|
||||
# 1. 获取临时脚本文件路径
|
||||
temp_file_path=$(uv run skills/uv-python-runner/script/get_temp_path.py)
|
||||
|
||||
# 2. 写入PEP 723脚本内容
|
||||
# 使用大模型的Write工具在 temp_file_path 中写入...
|
||||
|
||||
# 3. 执行脚本
|
||||
uv run $temp_file_path
|
||||
|
||||
# 4. 系统自动清理临时文件
|
||||
```
|
||||
|
||||
**关键特点:**
|
||||
|
||||
- 跨平台自动适配
|
||||
- 环境隔离
|
||||
- 自动依赖管理
|
||||
- 临时文件直接返回路径,无需手动拼接
|
||||
- 系统自动清理临时文件,无需手动管理
|
||||
331
temp/docx_parser.md
Normal file
331
temp/docx_parser.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# DOCX 解析器使用说明
|
||||
|
||||
## 简介
|
||||
|
||||
`docx_parser.py` 是一个功能强大的 DOCX 文件解析工具,支持将 Microsoft Word (.docx) 文档转换为 Markdown 格式。该脚本采用多策略解析机制,按优先级尝试以下解析方法:
|
||||
|
||||
1. **MarkItDown**(微软官方库)
|
||||
2. **python-docx**(成熟的 Python 库)
|
||||
3. **XML 原生解析**(备选方案)
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.6+
|
||||
- pip
|
||||
|
||||
## 安装依赖
|
||||
|
||||
根据你的需求安装相应的解析库:
|
||||
|
||||
```bash
|
||||
# 安装 MarkItDown(推荐)
|
||||
pip install markitdown
|
||||
|
||||
# 安装 python-docx(备选)
|
||||
pip install python-docx
|
||||
```
|
||||
|
||||
> 注意:建议至少安装一种解析库。如果未安装任何库,脚本会尝试使用 XML 原生解析,但功能可能受限。
|
||||
|
||||
## 命令行参数
|
||||
|
||||
### 基本语法
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py [options] <file_path>
|
||||
```
|
||||
|
||||
### 位置参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `file_path` | DOCX 文件的绝对路径 |
|
||||
|
||||
### 选项参数
|
||||
|
||||
| 参数 | 长参数 | 类型 | 默认值 | 说明 |
|
||||
|------|--------|------|--------|------|
|
||||
| `-n` | `--context` | 整数 | 2 | 与 `-s` 配合使用,指定每个检索结果包含的前后行数(不包含空行) |
|
||||
|
||||
### 互斥参数
|
||||
|
||||
以下参数只能使用其中一个:
|
||||
|
||||
| 参数 | 长参数 | 说明 |
|
||||
|------|--------|------|
|
||||
| `-c` | `--count` | 返回解析后的 markdown 文档的总字数 |
|
||||
| `-l` | `--lines` | 返回解析后的 markdown 文档的总行数 |
|
||||
| `-t` | `--titles` | 返回解析后的 markdown 文档的标题行(1-6级) |
|
||||
| `-tc` | `--title-content` | 指定标题名称,输出该标题及其下级内容(不包含#号) |
|
||||
| `-s` | `--search` | 使用正则表达式搜索文档,返回所有匹配结果(用---分隔) |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 输出完整 Markdown 内容
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:完整的 Markdown 格式文档内容
|
||||
|
||||
### 2. 获取文档字数
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -c /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:文档总字数(数字)
|
||||
|
||||
### 3. 获取文档行数
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -l /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:文档总行数(数字)
|
||||
|
||||
### 4. 提取所有标题
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -t /path/to/document.docx
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
# 主标题
|
||||
## 第一章
|
||||
### 1.1 简介
|
||||
### 1.2 内容
|
||||
## 第二章
|
||||
```
|
||||
|
||||
### 5. 提取指定标题内容
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -tc "第一章" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:包含所有上级标题的指定章节内容
|
||||
|
||||
**特点:**
|
||||
- 支持多个同名标题
|
||||
- 自动包含完整的上级标题链
|
||||
- 包含所有下级内容
|
||||
|
||||
示例输出:
|
||||
```
|
||||
# 主标题
|
||||
## 第一章
|
||||
这是第一章的内容
|
||||
包含所有子章节...
|
||||
|
||||
### 1.1 简介
|
||||
简介内容
|
||||
|
||||
### 1.2 内容
|
||||
详细内容
|
||||
```
|
||||
|
||||
### 6. 搜索关键词
|
||||
|
||||
#### 6.1 基本搜索
|
||||
|
||||
```bash
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出:所有匹配关键词的内容片段,默认前后各 2 行上下文,用 `---` 分隔
|
||||
|
||||
#### 6.2 自定义上下文行数
|
||||
|
||||
```bash
|
||||
# 前后各 5 行
|
||||
python3 docx_parser.py -s "关键词" -n 5 /path/to/document.docx
|
||||
|
||||
# 不包含上下文
|
||||
python3 docx_parser.py -s "关键词" -n 0 /path/to/document.docx
|
||||
```
|
||||
|
||||
#### 6.3 正则表达式搜索
|
||||
|
||||
```bash
|
||||
# 搜索包含数字的行
|
||||
python3 docx_parser.py -s r"数字\d+" /path/to/document.docx
|
||||
|
||||
# 搜索邮箱地址
|
||||
python3 docx_parser.py -s r"\b[\w.-]+@[\w.-]+\.\w+\b" /path/to/document.docx
|
||||
|
||||
# 搜索日期格式
|
||||
python3 docx_parser.py -s r"\d{4}-\d{2}-\d{2}" /path/to/document.docx
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
这是前一行
|
||||
包含匹配关键词
|
||||
这是后一行
|
||||
---
|
||||
另一个匹配
|
||||
---
|
||||
第三个匹配
|
||||
```
|
||||
|
||||
### 7. 将输出保存到文件
|
||||
|
||||
```bash
|
||||
# 保存完整 Markdown
|
||||
python3 docx_parser.py /path/to/document.docx > output.md
|
||||
|
||||
# 保存标题内容
|
||||
python3 docx_parser.py -tc "第一章" /path/to/document.docx > chapter1.md
|
||||
|
||||
# 保存搜索结果
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx > search_results.md
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 多策略解析
|
||||
|
||||
脚本自动尝试三种解析方法,确保最大的兼容性:
|
||||
|
||||
1. **MarkItDown**:微软官方库,解析效果最佳
|
||||
2. **python-docx**:功能完善的第三方库
|
||||
3. **XML 原生解析**:不依赖任何库的备选方案
|
||||
|
||||
### 智能匹配
|
||||
|
||||
#### 标题提取
|
||||
|
||||
- 支持 1-6 级标题识别
|
||||
- 自动处理不同样式的标题(Title、Heading 1-6)
|
||||
- 保留原始标题层级关系
|
||||
|
||||
#### 标题内容提取
|
||||
|
||||
- 支持同名标题提取
|
||||
- 自动构建完整上级标题链
|
||||
- 包含所有下级内容
|
||||
- 保持文档结构完整
|
||||
|
||||
#### 搜索功能
|
||||
|
||||
- 支持正则表达式
|
||||
- 智能合并相近匹配
|
||||
- 上下文行数控制(不包含空行)
|
||||
- 结果用 `---` 清晰分隔
|
||||
|
||||
### 文档处理
|
||||
|
||||
- 自动移除 Markdown 图片
|
||||
- 规范化空白行(连续多个空行合并为一个)
|
||||
- 支持表格、列表、粗体、斜体、下划线等格式
|
||||
|
||||
### 错误处理
|
||||
|
||||
- 文件存在性检查
|
||||
- DOCX 格式验证
|
||||
- 解析失败时自动尝试下一种方法
|
||||
- 详细的错误提示信息
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何选择解析库?
|
||||
|
||||
A: 建议优先安装 `markitdown`,它是微软官方库,解析效果最好。如果需要更多控制或兼容性,可以同时安装 `python-docx`。
|
||||
|
||||
### Q: 为什么某些文档解析不完整?
|
||||
|
||||
A: 可能原因:
|
||||
1. 文档使用特殊格式或样式
|
||||
2. 文档已损坏
|
||||
3. 未安装合适的解析库
|
||||
|
||||
尝试安装多个解析库,或检查文档是否损坏。
|
||||
|
||||
### Q: 如何处理大文档?
|
||||
|
||||
A: 对于非常大的文档,建议:
|
||||
1. 使用 `-tc` 参数只提取需要的章节
|
||||
2. 使用 `-s` 参数搜索特定内容
|
||||
3. 将输出重定向到文件进行处理
|
||||
|
||||
### Q: 搜索功能支持哪些正则表达式?
|
||||
|
||||
A: 支持所有 Python 标准正则表达式语法。需要注意特殊字符的转义:
|
||||
|
||||
```bash
|
||||
# 错误:括号需要转义
|
||||
python3 docx_parser.py -s "(关键词)" /path/to/document.docx
|
||||
|
||||
# 正确
|
||||
python3 docx_parser.py -s "\(关键词\)" /path/to/document.docx
|
||||
```
|
||||
|
||||
### Q: 如何获取更多上下文?
|
||||
|
||||
A: 使用 `-n` 参数调整上下文行数:
|
||||
|
||||
```bash
|
||||
# 默认 2 行(推荐)
|
||||
python3 docx_parser.py -s "关键词" /path/to/document.docx
|
||||
|
||||
# 更多上下文(5 行)
|
||||
python3 docx_parser.py -s "关键词" -n 5 /path/to/document.docx
|
||||
|
||||
# 不包含上下文
|
||||
python3 docx_parser.py -s "关键词" -n 0 /path/to/document.docx
|
||||
```
|
||||
|
||||
### Q: 多个同名标题如何处理?
|
||||
|
||||
A: `-tc` 参数会返回所有同名标题,每个标题都包含其完整的上级标题链:
|
||||
|
||||
```markdown
|
||||
# 主标题
|
||||
## 同名标题 1
|
||||
内容1
|
||||
|
||||
# 主标题
|
||||
## 同名标题 2
|
||||
内容2
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 标题识别规则
|
||||
|
||||
| 样式名称 | Markdown 标题级别 |
|
||||
|---------|-------------------|
|
||||
| Title | # |
|
||||
| Heading 1 | # |
|
||||
| Heading 2 | ## |
|
||||
| Heading 3 | ### |
|
||||
| Heading 4 | #### |
|
||||
| Heading 5 | ##### |
|
||||
| Heading 6 | ###### |
|
||||
|
||||
### 列表识别规则
|
||||
|
||||
| 样式名称 | Markdown 列表格式 |
|
||||
|---------|------------------|
|
||||
| List Bullet / Bullet | - (无序列表) |
|
||||
| List Number / Number | 1. (有序列表) |
|
||||
|
||||
### 文本格式支持
|
||||
|
||||
| 格式 | 转换结果 |
|
||||
|------|---------|
|
||||
| 粗体 | `**文本**` |
|
||||
| 斜体 | `*文本*` |
|
||||
| 下划线 | `<u>文本</u>` |
|
||||
| 表格 | Markdown 表格格式 |
|
||||
|
||||
## 许可证
|
||||
|
||||
本脚本遵循相关开源许可证。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交问题和改进建议。
|
||||
551
temp/docx_parser.py
Normal file
551
temp/docx_parser.py
Normal file
@@ -0,0 +1,551 @@
|
||||
#!/usr/bin/env python3
|
||||
"""整合的 DOCX 解析器,按优先级尝试多种解析方法:
|
||||
1. MarkItDown (微软官方库)
|
||||
2. python-docx (成熟的 Python 库)
|
||||
3. XML 原生解析 (备选方案)
|
||||
|
||||
代码风格要求:
|
||||
- Python 3.6+ 兼容
|
||||
- 遵循 PEP 8 规范
|
||||
- 所有公共 API 函数添加类型提示
|
||||
- 字符串优先内联使用,不提取为常量,除非被使用超过3次
|
||||
- 其他被多次使用的对象根据具体情况可考虑被提取为常量(如正则表达式)
|
||||
- 模块级和公共 API 函数保留文档字符串
|
||||
- 内部辅助函数不添加文档字符串(函数名足够描述)
|
||||
- 变量命名清晰,避免单字母变量名
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
IMAGE_PATTERN = re.compile(r"!\[[^\]]*\]\([^)]+\)")
|
||||
|
||||
|
||||
def normalize_markdown_whitespace(content: str) -> str:
|
||||
lines = content.split("\n")
|
||||
result = []
|
||||
empty_count = 0
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
empty_count += 1
|
||||
if empty_count == 1:
|
||||
result.append(line)
|
||||
else:
|
||||
empty_count = 0
|
||||
result.append(line)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def is_valid_docx(file_path: str) -> bool:
|
||||
try:
|
||||
with zipfile.ZipFile(file_path, "r") as zip_file:
|
||||
required_files = ["[Content_Types].xml", "_rels/.rels", "word/document.xml"]
|
||||
for required in required_files:
|
||||
if required not in zip_file.namelist():
|
||||
return False
|
||||
return True
|
||||
except (zipfile.BadZipFile, zipfile.LargeZipFile):
|
||||
return False
|
||||
|
||||
|
||||
def remove_markdown_images(markdown_text: str) -> str:
|
||||
return IMAGE_PATTERN.sub("", markdown_text)
|
||||
|
||||
|
||||
def extract_titles(markdown_text: str) -> List[str]:
|
||||
"""提取 markdown 文本中的所有标题行(1-6级)"""
|
||||
title_lines = []
|
||||
for line in markdown_text.split("\n"):
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("#"):
|
||||
level = 0
|
||||
for char in stripped:
|
||||
if char == "#":
|
||||
level += 1
|
||||
else:
|
||||
break
|
||||
if 1 <= level <= 6:
|
||||
title_lines.append(stripped)
|
||||
return title_lines
|
||||
|
||||
|
||||
def get_heading_level(line: str) -> int:
|
||||
stripped = line.lstrip()
|
||||
if not stripped.startswith("#"):
|
||||
return 0
|
||||
level = 0
|
||||
for char in stripped:
|
||||
if char == "#":
|
||||
level += 1
|
||||
else:
|
||||
break
|
||||
return level if 1 <= level <= 6 else 0
|
||||
|
||||
|
||||
def extract_title_content(markdown_text: str, title_name: str) -> Optional[str]:
|
||||
"""提取所有指定标题及其下级内容(每个包含上级标题)"""
|
||||
lines = markdown_text.split("\n")
|
||||
match_indices = []
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
level = get_heading_level(line)
|
||||
if level > 0:
|
||||
stripped = line.lstrip()
|
||||
title_text = stripped[level:].strip()
|
||||
if title_text == title_name:
|
||||
match_indices.append(i)
|
||||
|
||||
if not match_indices:
|
||||
return None
|
||||
|
||||
result_lines = []
|
||||
for idx in match_indices:
|
||||
target_level = get_heading_level(lines[idx])
|
||||
|
||||
parent_titles = []
|
||||
current_level = target_level
|
||||
for i in range(idx - 1, -1, -1):
|
||||
line_level = get_heading_level(lines[i])
|
||||
if line_level > 0 and line_level < current_level:
|
||||
parent_titles.append(lines[i])
|
||||
current_level = line_level
|
||||
if current_level == 1:
|
||||
break
|
||||
|
||||
parent_titles.reverse()
|
||||
result_lines.extend(parent_titles)
|
||||
|
||||
result_lines.append(lines[idx])
|
||||
for i in range(idx + 1, len(lines)):
|
||||
line = lines[i]
|
||||
line_level = get_heading_level(line)
|
||||
if line_level == 0 or line_level > target_level:
|
||||
result_lines.append(line)
|
||||
else:
|
||||
break
|
||||
|
||||
return "\n".join(result_lines)
|
||||
|
||||
|
||||
def search_markdown(
|
||||
content: str, pattern: str, context_lines: int = 0
|
||||
) -> Optional[str]:
|
||||
"""使用正则表达式搜索 markdown 文档,返回匹配结果及其上下文"""
|
||||
try:
|
||||
regex = re.compile(pattern)
|
||||
except re.error:
|
||||
return None
|
||||
|
||||
lines = content.split("\n")
|
||||
|
||||
non_empty_indices = []
|
||||
non_empty_to_original = {}
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip():
|
||||
non_empty_indices.append(i)
|
||||
non_empty_to_original[i] = len(non_empty_indices) - 1
|
||||
|
||||
matched_non_empty_indices = []
|
||||
for orig_idx in non_empty_indices:
|
||||
if regex.search(lines[orig_idx]):
|
||||
matched_non_empty_indices.append(non_empty_to_original[orig_idx])
|
||||
|
||||
if not matched_non_empty_indices:
|
||||
return None
|
||||
|
||||
merged_ranges = []
|
||||
current_start = matched_non_empty_indices[0]
|
||||
current_end = matched_non_empty_indices[0]
|
||||
|
||||
for idx in matched_non_empty_indices[1:]:
|
||||
if idx - current_end <= context_lines * 2:
|
||||
current_end = idx
|
||||
else:
|
||||
merged_ranges.append((current_start, current_end))
|
||||
current_start = idx
|
||||
current_end = idx
|
||||
merged_ranges.append((current_start, current_end))
|
||||
|
||||
results = []
|
||||
for start, end in merged_ranges:
|
||||
actual_start = max(0, start - context_lines)
|
||||
actual_end = min(len(non_empty_indices) - 1, end + context_lines)
|
||||
|
||||
start_line_idx = non_empty_indices[actual_start]
|
||||
end_line_idx = non_empty_indices[actual_end]
|
||||
|
||||
selected_indices = set(non_empty_indices[actual_start : actual_end + 1])
|
||||
result_lines = [
|
||||
line
|
||||
for i, line in enumerate(lines)
|
||||
if start_line_idx <= i <= end_line_idx
|
||||
and (line.strip() or i in selected_indices)
|
||||
]
|
||||
results.append("\n".join(result_lines))
|
||||
|
||||
return "\n---\n".join(results)
|
||||
|
||||
|
||||
def parse_with_markitdown(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
try:
|
||||
from markitdown import MarkItDown
|
||||
|
||||
md = MarkItDown()
|
||||
result = md.convert(file_path)
|
||||
if not result.text_content.strip():
|
||||
return None, "文档为空"
|
||||
return result.text_content, None
|
||||
except ImportError:
|
||||
return None, "MarkItDown 库未安装"
|
||||
except Exception as e:
|
||||
return None, f"MarkItDown 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def parse_with_python_docx(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
try:
|
||||
from docx import Document
|
||||
except ImportError:
|
||||
return None, "python-docx 库未安装"
|
||||
|
||||
try:
|
||||
doc = Document(file_path)
|
||||
|
||||
def get_heading_level(para) -> int:
|
||||
if para.style and para.style.name:
|
||||
style_name = para.style.name
|
||||
if "Heading 1" in style_name or "Title" in style_name:
|
||||
return 1
|
||||
elif "Heading 2" in style_name:
|
||||
return 2
|
||||
elif "Heading 3" in style_name:
|
||||
return 3
|
||||
elif "Heading 4" in style_name:
|
||||
return 4
|
||||
elif "Heading 5" in style_name:
|
||||
return 5
|
||||
elif "Heading 6" in style_name:
|
||||
return 6
|
||||
return 0
|
||||
|
||||
def get_list_style(para) -> Optional[str]:
|
||||
if not para.style or not para.style.name:
|
||||
return None
|
||||
style_name = para.style.name
|
||||
if "List Bullet" in style_name or "Bullet" in style_name:
|
||||
return "bullet"
|
||||
elif "List Number" in style_name or "Number" in style_name:
|
||||
return "number"
|
||||
return None
|
||||
|
||||
def convert_runs_to_markdown(runs) -> str:
|
||||
result = []
|
||||
for run in runs:
|
||||
text = run.text
|
||||
if not text:
|
||||
continue
|
||||
if run.bold:
|
||||
text = f"**{text}**"
|
||||
if run.italic:
|
||||
text = f"*{text}*"
|
||||
if run.underline:
|
||||
text = f"<u>{text}</u>"
|
||||
result.append(text)
|
||||
return "".join(result)
|
||||
|
||||
def convert_table_to_markdown(table) -> str:
|
||||
md_lines = []
|
||||
for i, row in enumerate(table.rows):
|
||||
cells = []
|
||||
for cell in row.cells:
|
||||
cell_text = cell.text.strip().replace("\n", " ")
|
||||
cells.append(cell_text)
|
||||
if cells:
|
||||
md_line = "| " + " | ".join(cells) + " |"
|
||||
md_lines.append(md_line)
|
||||
if i == 0:
|
||||
sep_line = "| " + " | ".join(["---"] * len(cells)) + " |"
|
||||
md_lines.append(sep_line)
|
||||
return "\n".join(md_lines)
|
||||
|
||||
markdown_lines = []
|
||||
|
||||
for para in doc.paragraphs:
|
||||
text = convert_runs_to_markdown(para.runs)
|
||||
if not text.strip():
|
||||
continue
|
||||
heading_level = get_heading_level(para)
|
||||
if heading_level > 0:
|
||||
markdown_lines.append(f"{'#' * heading_level} {text}")
|
||||
else:
|
||||
list_style = get_list_style(para)
|
||||
if list_style == "bullet":
|
||||
markdown_lines.append(f"- {text}")
|
||||
elif list_style == "number":
|
||||
markdown_lines.append(f"1. {text}")
|
||||
else:
|
||||
markdown_lines.append(text)
|
||||
markdown_lines.append("")
|
||||
|
||||
for table in doc.tables:
|
||||
table_md = convert_table_to_markdown(table)
|
||||
markdown_lines.append(table_md)
|
||||
markdown_lines.append("")
|
||||
|
||||
content = "\n".join(markdown_lines)
|
||||
if not content.strip():
|
||||
return None, "文档为空"
|
||||
return content, None
|
||||
except Exception as e:
|
||||
return None, f"python-docx 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def parse_with_xml(file_path: str) -> Optional[Tuple[str, None]]:
|
||||
word_namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
namespaces = {"w": word_namespace}
|
||||
|
||||
def safe_open_zip(zip_file: zipfile.ZipFile, name: str):
|
||||
if name.startswith("..") or "/" not in name:
|
||||
return None
|
||||
return zip_file.open(name)
|
||||
|
||||
def get_heading_level(style_id: Optional[str], style_to_level: dict) -> int:
|
||||
return style_to_level.get(style_id, 0)
|
||||
|
||||
def get_list_style(style_id: Optional[str], style_to_list: dict) -> Optional[str]:
|
||||
return style_to_list.get(style_id, None)
|
||||
|
||||
def extract_text_with_formatting(para, namespaces: dict) -> str:
|
||||
texts = []
|
||||
for run in para.findall(".//w:r", namespaces=namespaces):
|
||||
text_elem = run.find(".//w:t", namespaces=namespaces)
|
||||
if text_elem is not None and text_elem.text:
|
||||
text = text_elem.text
|
||||
bold = run.find(".//w:b", namespaces=namespaces) is not None
|
||||
italic = run.find(".//w:i", namespaces=namespaces) is not None
|
||||
if bold:
|
||||
text = f"**{text}**"
|
||||
if italic:
|
||||
text = f"*{text}*"
|
||||
texts.append(text)
|
||||
return "".join(texts).strip()
|
||||
|
||||
def convert_table_to_markdown(table_elem, namespaces: dict) -> str:
|
||||
rows = table_elem.findall(".//w:tr", namespaces=namespaces)
|
||||
if not rows:
|
||||
return ""
|
||||
md_lines = []
|
||||
for i, row in enumerate(rows):
|
||||
cells = row.findall(".//w:tc", namespaces=namespaces)
|
||||
cell_texts = []
|
||||
for cell in cells:
|
||||
cell_text = extract_text_with_formatting(cell, namespaces)
|
||||
cell_text = cell_text.replace("\n", " ").strip()
|
||||
cell_texts.append(cell_text if cell_text else "")
|
||||
if cell_texts:
|
||||
md_line = "| " + " | ".join(cell_texts) + " |"
|
||||
md_lines.append(md_line)
|
||||
if i == 0:
|
||||
sep_line = "| " + " | ".join(["---"] * len(cell_texts)) + " |"
|
||||
md_lines.append(sep_line)
|
||||
return "\n".join(md_lines)
|
||||
|
||||
try:
|
||||
style_to_level = {}
|
||||
style_to_list = {}
|
||||
markdown_lines = []
|
||||
|
||||
with zipfile.ZipFile(file_path) as zip_file:
|
||||
try:
|
||||
styles_file = safe_open_zip(zip_file, "word/styles.xml")
|
||||
if styles_file:
|
||||
styles_root = ET.parse(styles_file)
|
||||
for style in styles_root.findall(
|
||||
".//w:style", namespaces=namespaces
|
||||
):
|
||||
style_id = style.get(f"{{{word_namespace}}}styleId")
|
||||
style_name_elem = style.find("w:name", namespaces=namespaces)
|
||||
if style_id and style_name_elem is not None:
|
||||
style_name = style_name_elem.get(f"{{{word_namespace}}}val")
|
||||
if style_name:
|
||||
if style_name == "Title":
|
||||
style_to_level[style_id] = 1
|
||||
elif style_name == "heading 1":
|
||||
style_to_level[style_id] = 1
|
||||
elif style_name == "heading 2":
|
||||
style_to_level[style_id] = 2
|
||||
elif style_name == "heading 3":
|
||||
style_to_level[style_id] = 3
|
||||
elif style_name == "heading 4":
|
||||
style_to_level[style_id] = 4
|
||||
elif style_name == "heading 5":
|
||||
style_to_level[style_id] = 5
|
||||
elif style_name == "heading 6":
|
||||
style_to_level[style_id] = 6
|
||||
elif (
|
||||
"List Bullet" in style_name
|
||||
or "Bullet" in style_name
|
||||
):
|
||||
style_to_list[style_id] = "bullet"
|
||||
elif (
|
||||
"List Number" in style_name
|
||||
or "Number" in style_name
|
||||
):
|
||||
style_to_list[style_id] = "number"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
document_file = safe_open_zip(zip_file, "word/document.xml")
|
||||
if not document_file:
|
||||
return None, "document.xml 不存在或无法访问"
|
||||
|
||||
root = ET.parse(document_file)
|
||||
body = root.find(".//w:body", namespaces=namespaces)
|
||||
if body is None:
|
||||
return None, "document.xml 中未找到 w:body 元素"
|
||||
|
||||
for child in body.findall("./*", namespaces=namespaces):
|
||||
if child.tag.endswith("}p"):
|
||||
style_elem = child.find(".//w:pStyle", namespaces=namespaces)
|
||||
style_id = (
|
||||
style_elem.get(f"{{{word_namespace}}}val")
|
||||
if style_elem is not None
|
||||
else None
|
||||
)
|
||||
|
||||
heading_level = get_heading_level(style_id, style_to_level)
|
||||
list_style = get_list_style(style_id, style_to_list)
|
||||
para_text = extract_text_with_formatting(child, namespaces)
|
||||
|
||||
if para_text:
|
||||
if heading_level > 0:
|
||||
markdown_lines.append(f"{'#' * heading_level} {para_text}")
|
||||
elif list_style == "bullet":
|
||||
markdown_lines.append(f"- {para_text}")
|
||||
elif list_style == "number":
|
||||
markdown_lines.append(f"1. {para_text}")
|
||||
else:
|
||||
markdown_lines.append(para_text)
|
||||
markdown_lines.append("")
|
||||
|
||||
elif child.tag.endswith("}tbl"):
|
||||
table_md = convert_table_to_markdown(child, namespaces)
|
||||
if table_md:
|
||||
markdown_lines.append(table_md)
|
||||
markdown_lines.append("")
|
||||
|
||||
content = "\n".join(markdown_lines)
|
||||
if not content.strip():
|
||||
return None, "文档为空"
|
||||
return content, None
|
||||
except Exception as e:
|
||||
return None, f"XML 解析失败: {str(e)}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="将 DOCX 文件解析为 Markdown")
|
||||
|
||||
parser.add_argument("file_path", help="DOCX 文件的绝对路径")
|
||||
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--context",
|
||||
type=int,
|
||||
default=2,
|
||||
help="与 -s 配合使用,指定每个检索结果包含的前后行数(不包含空行)",
|
||||
)
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"-c", "--count", action="store_true", help="返回解析后的 markdown 文档的总字数"
|
||||
)
|
||||
group.add_argument(
|
||||
"-l", "--lines", action="store_true", help="返回解析后的 markdown 文档的总行数"
|
||||
)
|
||||
group.add_argument(
|
||||
"-t",
|
||||
"--titles",
|
||||
action="store_true",
|
||||
help="返回解析后的 markdown 文档的标题行(1-6级)",
|
||||
)
|
||||
group.add_argument(
|
||||
"-tc",
|
||||
"--title-content",
|
||||
help="指定标题名称,输出该标题及其下级内容(不包含#号)",
|
||||
)
|
||||
group.add_argument(
|
||||
"-s",
|
||||
"--search",
|
||||
help="使用正则表达式搜索文档,返回所有匹配结果(用---分隔)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.file_path):
|
||||
print(f"错误: 文件不存在: {args.file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not args.file_path.lower().endswith(".docx"):
|
||||
print(f"警告: 文件扩展名不是 .docx: {args.file_path}")
|
||||
|
||||
if not is_valid_docx(args.file_path):
|
||||
print(f"错误: 文件不是有效的 DOCX 格式或已损坏: {args.file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
parsers = [
|
||||
("MarkItDown", parse_with_markitdown),
|
||||
("python-docx", parse_with_python_docx),
|
||||
("XML 原生解析", parse_with_xml),
|
||||
]
|
||||
|
||||
failures = []
|
||||
content = None
|
||||
|
||||
for parser_name, parser_func in parsers:
|
||||
content, error = parser_func(args.file_path)
|
||||
if content is not None:
|
||||
content = remove_markdown_images(content)
|
||||
content = normalize_markdown_whitespace(content)
|
||||
break
|
||||
else:
|
||||
failures.append(f"- {parser_name}: {error}")
|
||||
|
||||
if content is None:
|
||||
print("所有解析方法均失败:")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
sys.exit(1)
|
||||
|
||||
if args.count:
|
||||
print(len(content.replace("\n", "")))
|
||||
elif args.lines:
|
||||
print(len(content.split("\n")))
|
||||
elif args.titles:
|
||||
titles = extract_titles(content)
|
||||
for title in titles:
|
||||
print(title)
|
||||
elif args.title_content:
|
||||
title_content = extract_title_content(content, args.title_content)
|
||||
if title_content is None:
|
||||
print(f"错误: 未找到标题 '{args.title_content}'")
|
||||
sys.exit(1)
|
||||
print(title_content, end="")
|
||||
elif args.search:
|
||||
search_result = search_markdown(content, args.search, args.context)
|
||||
if search_result is None:
|
||||
print(f"错误: 正则表达式无效或未找到匹配: '{args.search}'")
|
||||
sys.exit(1)
|
||||
print(search_result, end="")
|
||||
else:
|
||||
print(content, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user