feat: add Chinese office productivity plugins

This commit is contained in:
CJYSEX
2026-07-23 12:15:04 +08:00
parent 9a24a2125f
commit 1b674e31da
24 changed files with 1001 additions and 2 deletions

View File

@@ -0,0 +1,45 @@
---
name: build-spreadsheets
description: 创建、读取、编辑、分析和验证 XLSX、CSV、TSV 表格文件。用户要求制作 Excel、整理 CSV、加入公式和图表、分析工作簿、修复公式或保留模板格式时使用不用于控制正在 Excel 中打开的实时工作簿。
---
# CJYSA 表格
## 路由
- 对 CSV/TSV 做纯数据清洗时可直接用 Python。
- 对 XLSX 创建和编辑,优先使用工作区依赖加载器提供的 `@oai/artifact-tool`;不可用时使用其 Python 和 `openpyxl`
- 用户要求控制当前打开的 Excel 窗口时,不使用本技能;需要 Excel/WPS 实时控制能力。
## 标准流程
1. 先运行结构检查:
```powershell
& "<bundled-python>" "<skill-dir>\scripts\xlsx_tool.py" inspect "<input.xlsx>"
```
2. 编辑已有工作簿前检查相关单元格的值、公式、数字格式、合并区域、冻结窗格、条件格式和图表。
3. 创建工作簿时把输入、计算和输出分区;派生数据使用可审计公式,不把结果硬编码。
4. 简单 CSV 转 XLSX 可运行:
```powershell
& "<bundled-python>" "<skill-dir>\scripts\xlsx_tool.py" csv-to-xlsx `
"<input.csv>" --output "<output.xlsx>"
```
5. 运行 `formula-scan` 检查明显错误,再读取关键范围核对结果。
6. 使用 artifact-tool、Excel、WPS 或可靠渲染器查看所有工作表;不可渲染时明确只完成结构和公式检查。
## 表格规则
- 数字、日期、百分比和货币使用真实类型,不写成带符号的文本。
- 公式引用保持一致,跨表引用对含空格的工作表名加单引号。
- 编辑已有表格时做最小改动,不对整张表盲目自动宽度或重设样式。
- 重要假设集中放置并标注;复杂公式拆成辅助行/列。
- 不复制 OpenAI 官方 Spreadsheets 插件、内部 API 文档或 App 集成。
- 交付前按 [quality-checks.md](references/quality-checks.md) 检查。
## 输出
最终只链接用户要求的 XLSX/CSV/TSV不主动附带 builder、预览图或检查日志。

View File

@@ -0,0 +1,7 @@
interface:
display_name: "CJYSA 表格"
short_description: "创建、编辑、分析并验证 Excel 工作簿和数据表"
default_prompt: "Use $build-spreadsheets to create or edit an auditable XLSX workbook."
policy:
allow_implicit_invocation: true

View File

@@ -0,0 +1,9 @@
# 表格交付检查
- 工作表名称、可见性、冻结窗格和筛选范围正确。
- 输入、公式和输出区域清晰,关键公式可追溯。
- 扫描 `#REF!``#DIV/0!``#VALUE!``#NAME?``#N/A`
- 日期、数字、百分比和货币使用正确的数据类型与格式。
- 合并单元格、条件格式、图表和打印区域未被意外破坏。
- 逐表查看预览,确认标题、关键数字和图表未裁切。
- 若无法重新计算公式,明确说明结果由 Excel/WPS 打开后计算。

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Original XLSX helper for inspection, error scans, and CSV conversion."""
from __future__ import annotations
import argparse
import csv
import json
from pathlib import Path
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill
from openpyxl.utils import get_column_letter
ERROR_TOKENS = ("#REF!", "#DIV/0!", "#VALUE!", "#NAME?", "#N/A", "#NUM!", "#NULL!")
def inspect(path: Path) -> None:
workbook = load_workbook(path, data_only=False, read_only=False)
sheets = []
for sheet in workbook.worksheets:
formula_count = 0
nonempty = 0
for row in sheet.iter_rows():
for cell in row:
if cell.value is not None:
nonempty += 1
if isinstance(cell.value, str) and cell.value.startswith("="):
formula_count += 1
sheets.append(
{
"name": sheet.title,
"state": sheet.sheet_state,
"max_row": sheet.max_row,
"max_column": sheet.max_column,
"nonempty_cells": nonempty,
"formulas": formula_count,
"merged_ranges": len(sheet.merged_cells.ranges),
"charts": len(sheet._charts),
"freeze_panes": str(sheet.freeze_panes) if sheet.freeze_panes else None,
}
)
print(json.dumps({"file": str(path.resolve()), "sheets": sheets}, ensure_ascii=False, indent=2))
def formula_scan(path: Path) -> None:
formula_book = load_workbook(path, data_only=False, read_only=False)
value_book = load_workbook(path, data_only=True, read_only=False)
findings = []
for formula_sheet in formula_book.worksheets:
value_sheet = value_book[formula_sheet.title]
for row in formula_sheet.iter_rows():
for cell in row:
formula = cell.value
cached = value_sheet[cell.coordinate].value
if isinstance(formula, str) and formula.startswith("="):
formula_upper = formula.upper()
if any(token in formula_upper for token in ERROR_TOKENS):
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "formula_text", "value": formula})
if isinstance(cached, str) and cached.upper() in ERROR_TOKENS:
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "cached_error", "value": cached})
print(json.dumps({"file": str(path.resolve()), "errors": findings, "count": len(findings)}, ensure_ascii=False, indent=2))
def csv_to_xlsx(source: Path, output: Path, delimiter: str | None) -> None:
chosen = delimiter
if chosen is None:
chosen = "\t" if source.suffix.lower() == ".tsv" else ","
workbook = Workbook()
sheet = workbook.active
sheet.title = "Data"
with source.open("r", encoding="utf-8-sig", newline="") as handle:
reader = csv.reader(handle, delimiter=chosen)
for row_index, row in enumerate(reader, start=1):
for column_index, value in enumerate(row, start=1):
sheet.cell(row=row_index, column=column_index, value=value)
if sheet.max_row >= 1:
for cell in sheet[1]:
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="1F4E78")
sheet.freeze_panes = "A2"
sheet.auto_filter.ref = sheet.dimensions
for column in range(1, sheet.max_column + 1):
width = max((len(str(sheet.cell(row=row, column=column).value or "")) for row in range(1, min(sheet.max_row, 200) + 1)), default=8)
sheet.column_dimensions[get_column_letter(column)].width = min(max(width + 2, 10), 45)
output.parent.mkdir(parents=True, exist_ok=True)
workbook.save(output)
inspect(output)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
inspect_cmd = sub.add_parser("inspect")
inspect_cmd.add_argument("input", type=Path)
scan_cmd = sub.add_parser("formula-scan")
scan_cmd.add_argument("input", type=Path)
convert_cmd = sub.add_parser("csv-to-xlsx")
convert_cmd.add_argument("input", type=Path)
convert_cmd.add_argument("--output", required=True, type=Path)
convert_cmd.add_argument("--delimiter")
return parser
def main() -> None:
args = build_parser().parse_args()
if args.command == "inspect":
inspect(args.input)
elif args.command == "formula-scan":
formula_scan(args.input)
elif args.command == "csv-to-xlsx":
csv_to_xlsx(args.input, args.output, args.delimiter)
if __name__ == "__main__":
main()