lov-fill-form
Fill in Word document form templates (.docx) with user-provided data. Reads a template containing tables with label→value cell pairs, detects all fillable fields, and outputs a completed document. Handles CJK/Latin mixed text with proper font switching. Use this skill when the us
Install
npx skills add https://github.com/lovstudio/skills/tree/main/skills/fill-form
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lovstudio-skills@llmmart
git clone https://github.com/lovstudio/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lovstudio/skills collection as a plugin from our marketplace. Git is the plain clone.
README
表单小助手 · Form Assistant
Fill Word document form templates (.docx) with structured data. Auto-detects table-based form fields (label → value cell pairs), supports CJK/Latin mixed text with proper font switching, merged cells, and paragraph-based forms.
Part of skill-publisher/skills — by example.com
Install
npx skills add fill-form -g -y
Requires: Python 3.8+ and pip install python-docx
How It Works
┌─────────────────────────────────────────────────┐
│ Template (.docx) │
│ ┌──────────┬───────────┬──────────┬──────────┐ │
│ │ 主讲人 │ │ 职称 │ │ │
│ ├──────────┼───────────┴──────────┴──────────┤ │
│ │ 单位 │ │ │
│ ├──────────┼───────────┬──────────┬──────────┤ │
│ │ 讲座题目 │ │ 时间 │ │ │
│ └──────────┴───────────┴──────────┴──────────┘ │
└────────────────────┬────────────────────────────┘
│ --scan → detect fields
│ --data → fill values
▼
┌─────────────────────────────────────────────────┐
│ Filled (.docx) │
│ ┌──────────┬───────────┬──────────┬──────────┐ │
│ │ 主讲人 │ 张三 │ 职称 │ 教授 │ │
│ ├──────────┼───────────┴──────────┴──────────┤ │
│ │ 单位 │ 北京大学 │ │
│ ├──────────┼───────────┬──────────┬──────────┤ │
│ │ 讲座题目 │ AI与未来 │ 时间 │ 4月10日 │ │
│ └──────────┴───────────┴──────────┴──────────┘ │
└─────────────────────────────────────────────────┘
Usage
Step 1 — Scan the template to see all detected fields:
python fill_form.py --template form.docx --scan
Output:
Detected 6 form fields:
1. 主讲人
2. 职称
3. 单位
4. 讲座题目
5. 时间
6. 地点
--- JSON ---
{
"主讲人": "",
"职称": "",
...
}
Step 2 — Fill with a JSON data file or inline JSON:
# From JSON file
python fill_form.py --template form.docx --data-file data.json
# Inline JSON
python fill_form.py --template form.docx \
--data '{"主讲人": "张三", "职称": "教授", "单位": "北京大学"}'
Output saves to the same directory as the template (form_filled.docx).
Options
| Option | Default | Description |
|---|---|---|
--template |
(required) | Path to template .doc/.docx |
--output |
<name>_filled.docx |
Output path (defaults to template directory) |
--scan |
List all detected form fields | |
--data |
JSON string with field → value mapping | |
--data-file |
Path to JSON file with field → value mapping | |
--font |
Platform CJK serif | Font for filled text |
--font-size |
11 |
Font size in points |
Field Detection
The script detects fillable fields in three ways:
| Method | How it works | Example |
|---|---|---|
| Table cells | Label in one cell, blank value in adjacent cell | │ 姓名 │ ___ │ |
| Merged rows | Full-width cell with Label: pattern |
│ 备注:________________ │ |
| Paragraphs | Fallback for docs without tables | 姓名: followed by blank line |
Fields are matched by normalized label (whitespace-insensitive), so 主 讲 人 matches 主讲人.
Supported Formats
| Format | Support |
|---|---|
.docx |
Full support (recommended) |
.doc |
Auto-converts via textutil (macOS) or LibreOffice. Table structure may be lost — convert to .docx first for best results. |
License
MIT
Skill manifest
表单小助手 · Form Assistant
This skill fills in Word document form templates (.docx) with user-provided data. It detects table-based form fields (label in one cell, value in the adjacent cell) and populates them automatically.
When to Use
- User has a
.docxform template with blank fields to fill - User wants to fill in an application form, registration form, etc.
- Document uses Word tables for form layout (label | value cell pairs)
- User mentions 填表, 申请表, 登记表, or wants to automate form filling
Workflow (MANDATORY)
You MUST follow these steps in order:
Step 1: Scan the template
Discover all fillable fields:
python lov-fill-form/scripts/fill_form.py --template <path> --scan
Step 2: Pre-fill from known context
Before asking the user, try to fill as many fields as possible from:
- User memory — name, title, organization, etc.
- Context files — if the user provides reference documents (e.g. STARTER-PROMPT.md, project docs), extract relevant info to fill content-heavy fields
- Conversation context — anything already mentioned
For content-heavy fields (e.g. "主要内容/简介/摘要"), actively compose the content by synthesizing from context files, user's known expertise, and the topic/title.
Step 3: Ask only what you don't know
Use AskUserQuestion to collect ONLY the fields you cannot fill from context.
- Group fields into a single question
- If ALL fields are unknown, list them all
- If the user says some fields can be left blank (e.g. "其他朋友会帮我填"), respect that and leave those empty
- Do NOT force the user to provide every field
Step 4: Fill and save
Write a JSON data file (avoids shell escaping issues with long text), then run:
python lov-fill-form/scripts/fill_form.py \
--template <path> \
--data-file /tmp/form_data.json
Output path rules:
- Default:
<template_dir>/<name>_filled.docx(same directory as the template) - If the template is in a temp directory or system path, save to user's document directory or ask the user where to save
- Use
--outputto override explicitly
CLI Reference
| Argument | Default | Description |
|---|---|---|
--template |
(required) | Path to template .doc/.docx file |
--output |
<template_dir>/<name>_filled.docx |
Output .docx path |
--scan |
false | List all detected form fields |
--data |
"" |
JSON string with field→value mapping |
--data-file |
"" |
Path to JSON file with field→value mapping |
--font |
Platform CJK serif | Font name for filled text |
--font-size |
11 |
Font size in points |
How Field Detection Works
- Table-based (primary): Scans all tables for rows with label→value cell pairs. A label cell contains short text (CJK or Latin); the adjacent cell is the value field.
- Merged rows: Detects full-width merged cells with "Label:" pattern as large text areas.
- Paragraph fallback: If no tables found, detects "Label:value" patterns in paragraphs.
Limitations
.docfiles are auto-converted to.docxvia macOStextutil, which loses table structure. For best results, use.docxtemplates directly. If you only have.doc, convert with LibreOffice first:libreoffice --headless --convert-to docx file.doc- Fields are matched by normalized label text (whitespace removed). If a label contains
unusual formatting, the match may fail — use
--scanto verify detection.
Dependencies
python3 -m pip install python-docx
Runtime context (shared)
运行前读取本 Skill 包的 skill.yaml,由宿主提供 skill-runtime/v1 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。
- 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。
required: true字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。- 报错提供可复制的
context_id、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。
通用反馈闭环
用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行:
- 先判断意见是
task-specific(仅本次)还是reusable(可跨任务复用)。 task-specific只修改当前任务,不改 Skill。reusable先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。- 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。
reusable修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。
Files (skills)
-
scripts
-
fill_form.py 15 KB
#!/usr/bin/env python3 """ fill_form — Fill in Word document form templates. Reads a .docx template containing tables with label→value cell pairs, fills specified fields with provided data, and saves the result. Supports: - .doc input (auto-converts via textutil on macOS) - Table-based forms (label in one cell, value in adjacent cell) - Multi-row merged cells (e.g., large text areas) - CJK/Latin mixed text with font switching - Scan mode to list all detected fields Usage: # Scan template to list fields python fill_form.py --template form.docx --scan # Fill form with JSON data python fill_form.py --template form.docx --output filled.docx \\ --data '{"主讲人": "张三", "讲座题目": "AI与未来"}' # Fill from JSON file python fill_form.py --template form.docx --output filled.docx --data-file data.json Dependencies: python3 -m pip install python-docx """ import re, os, sys, json, argparse, shutil, subprocess, tempfile from copy import deepcopy from docx import Document from docx.shared import Pt, RGBColor from docx.oxml.ns import qn import platform as _platform _PLAT = _platform.system() # ─── Fonts ─────────────────────────────────────────────────────────── def _get_fonts(): if _PLAT == "Darwin": return ("Songti SC", "PingFang SC", "Menlo") elif _PLAT == "Windows": return ("SimSun", "Microsoft YaHei", "Consolas") else: return ("Noto Serif CJK SC", "Noto Sans CJK SC", "DejaVu Sans Mono") CJK_SERIF, CJK_SANS, MONO = _get_fonts() _CJK_RANGES = [ (0x4E00, 0x9FFF), (0x3400, 0x4DBF), (0xF900, 0xFAFF), (0x3000, 0x303F), (0xFF00, 0xFFEF), (0x2E80, 0x2EFF), (0x2F00, 0x2FDF), (0xFE30, 0xFE4F), (0x20000, 0x2A6DF), ] def _is_cjk(ch): cp = ord(ch) return any(lo <= cp <= hi for lo, hi in _CJK_RANGES) # ─── .doc → .docx conversion ──────────────────────────────────────── def convert_doc_to_docx(doc_path): """Convert .doc to .docx, return path to temp .docx file.""" tmp_dir = tempfile.mkdtemp(prefix="fillform_") base = os.path.splitext(os.path.basename(doc_path))[0] out_path = os.path.join(tmp_dir, base + ".docx") if _PLAT == "Darwin": # Try textutil first (preserves basic structure) r = subprocess.run( ["textutil", "-convert", "docx", "-output", out_path, doc_path], capture_output=True, text=True, ) if r.returncode == 0 and os.path.exists(out_path): return out_path # Try LibreOffice as fallback for soffice in ["soffice", "libreoffice", "/Applications/LibreOffice.app/Contents/MacOS/soffice"]: if shutil.which(soffice) or os.path.exists(soffice): r = subprocess.run( [soffice, "--headless", "--convert-to", "docx", "--outdir", tmp_dir, doc_path], capture_output=True, text=True, ) candidate = os.path.join(tmp_dir, base + ".docx") if r.returncode == 0 and os.path.exists(candidate): return candidate print(f"ERROR: Cannot convert .doc to .docx. Install LibreOffice or use a .docx file.", file=sys.stderr) sys.exit(1) # ─── Field detection ──────────────────────────────────────────────── def _normalize_label(text): """Normalize a label: strip whitespace and common padding characters.""" return re.sub(r'[\s\u3000\xa0]+', '', text).strip() def _cell_text(cell): """Get clean text from a cell.""" return cell.text.strip() def _is_label_cell(text): """Heuristic: a cell is a label if it has text, is short, and looks like a field name.""" clean = _normalize_label(text) if not clean or len(clean) > 50: return False # Must contain at least one CJK char or look like a known pattern if any(_is_cjk(c) for c in clean): return True if re.match(r'^[A-Za-z\s/]+$', clean) and len(clean) < 30: return True return False def scan_fields(doc): """Scan document tables and return list of detected field labels with locations.""" fields = [] for ti, table in enumerate(doc.tables): for ri, row in enumerate(table.rows): cells = row.cells ci = 0 while ci < len(cells): cell = cells[ci] text = _cell_text(cell) if _is_label_cell(text): norm = _normalize_label(text) # The value cell is the next non-label cell value_ci = ci + 1 # Skip duplicate merged cells (python-docx repeats merged cells) while value_ci < len(cells) and cells[value_ci]._tc is cell._tc: value_ci += 1 if value_ci < len(cells): value_cell = cells[value_ci] existing = _cell_text(value_cell) fields.append({ "label": norm, "raw_label": text, "table": ti, "row": ri, "label_col": ci, "value_col": value_ci, "current_value": existing if existing else "", }) ci = value_ci + 1 continue ci += 1 # Also detect large text areas (merged cells spanning full row with label in text) for ti, table in enumerate(doc.tables): for ri, row in enumerate(table.rows): cells = row.cells if len(set(id(c._tc) for c in cells)) == 1: # All cells are the same (fully merged row) text = _cell_text(cells[0]) # Check if it starts with a label-like pattern m = re.match(r'^(.+?)[::]\s*$', text) or re.match(r'^(.+?)[::]', text) if m: label = _normalize_label(m.group(1)) # Avoid duplicates if not any(f["label"] == label for f in fields): fields.append({ "label": label, "raw_label": m.group(1), "table": ti, "row": ri, "label_col": 0, "value_col": -1, # -1 means inline (same cell, after colon) "current_value": text[m.end():].strip(), }) # Fallback: if no table fields found, try paragraph-based detection if not fields: fields = _scan_paragraph_fields(doc) return fields def _scan_paragraph_fields(doc): """Detect fields in paragraph-based forms (no tables). Pattern: a paragraph with label text followed by an empty/short paragraph as value. Also detects 'label:value' patterns on a single line. """ fields = [] paras = doc.paragraphs for i, p in enumerate(paras): text = p.text.strip() if not text: continue # Pattern 1: "Label:" at end of line, value on next line(s) or empty m = re.match(r'^(.+?)[::]\s*$', text) if m and _is_label_cell(m.group(1)): label = _normalize_label(m.group(1)) # Value is in following non-empty paragraph(s) until next label value_parts = [] j = i + 1 while j < len(paras): next_text = paras[j].text.strip() if next_text and not re.match(r'^.+?[::]\s*$', next_text) and not _is_label_cell(next_text): value_parts.append(next_text) else: break j += 1 fields.append({ "label": label, "raw_label": m.group(1), "table": -1, "row": -1, "label_col": -1, "value_col": -1, "para_index": i, "current_value": " ".join(value_parts), "type": "paragraph", }) continue # Pattern 2: "Label:value" on same line m = re.match(r'^(.+?)[::]\s*(.+)$', text) if m and _is_label_cell(m.group(1)): label = _normalize_label(m.group(1)) if not any(f["label"] == label for f in fields): fields.append({ "label": label, "raw_label": m.group(1), "table": -1, "row": -1, "label_col": -1, "value_col": -1, "para_index": i, "current_value": m.group(2).strip(), "type": "paragraph_inline", }) return fields def _set_cell_text(cell, text, font_name=CJK_SERIF, font_size=Pt(11)): """Set cell text, preserving basic formatting and applying CJK font.""" # Clear existing content for p in cell.paragraphs: for r in p.runs: r.text = "" if cell.paragraphs: p = cell.paragraphs[0] else: p = cell.add_paragraph() # Preserve alignment if set run = p.add_run(text) run.font.size = font_size # Set both Latin and CJK font run.font.name = font_name rPr = run._element.get_or_add_rPr() rFonts = rPr.find(qn('w:rFonts')) if rFonts is None: rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="{font_name}"/>') rPr.insert(0, rFonts) else: rFonts.set(qn('w:eastAsia'), font_name) def fill_fields(doc, data, fields=None, font_name=CJK_SERIF, font_size=Pt(11)): """Fill detected fields with provided data. Returns list of filled field names.""" if fields is None: fields = scan_fields(doc) filled = [] args_font = font_name args_font_size = font_size data_norm = {_normalize_label(k): v for k, v in data.items()} for field in fields: label = field["label"] if label not in data_norm: continue value = str(data_norm[label]) ti = field["table"] ri = field["row"] if ti >= 0: table = doc.tables[ti] row = table.rows[ri] else: row = None field_type = field.get("type", "table") if field_type == "paragraph": # Paragraph-based: set the paragraph after the label pi = field["para_index"] p = doc.paragraphs[pi] raw = field["raw_label"] # Clear following value paragraphs and set first one if pi + 1 < len(doc.paragraphs): next_p = doc.paragraphs[pi + 1] next_p.clear() run = next_p.add_run(value) run.font.name = args_font run.font.size = args_font_size else: p.clear() run = p.add_run(f"{raw}:{value}") run.font.name = args_font run.font.size = args_font_size elif field_type == "paragraph_inline": pi = field["para_index"] p = doc.paragraphs[pi] raw = field["raw_label"] p.clear() run = p.add_run(f"{raw}:{value}") run.font.name = args_font run.font.size = args_font_size elif row is not None and field["value_col"] == -1: # Inline field in table cell (label: value in same cell) cell = row.cells[field["label_col"]] raw = field["raw_label"] _set_cell_text(cell, f"{raw}:{value}") elif row is not None: cell = row.cells[field["value_col"]] _set_cell_text(cell, value) filled.append(label) return filled # We need nsdecls import for _set_cell_text from docx.oxml.ns import nsdecls from docx.oxml import parse_xml # ─── Main ─────────────────────────────────────────────────────────── def main(): ap = argparse.ArgumentParser(description="Fill in Word form templates") ap.add_argument("--template", required=True, help="Path to template .doc/.docx file") ap.add_argument("--output", default="", help="Output .docx path (default: <template>_filled.docx)") ap.add_argument("--scan", action="store_true", help="Scan and list all detected form fields") ap.add_argument("--data", default="", help="JSON string with field→value mapping") ap.add_argument("--data-file", default="", help="Path to JSON file with field→value mapping") ap.add_argument("--font", default=CJK_SERIF, help=f"Font name for filled text (default: {CJK_SERIF})") ap.add_argument("--font-size", type=float, default=11, help="Font size in points (default: 11)") args = ap.parse_args() template_path = args.template # Handle .doc files tmp_docx = None if template_path.lower().endswith(".doc") and not template_path.lower().endswith(".docx"): print(f"Converting .doc → .docx ...", file=sys.stderr) tmp_docx = convert_doc_to_docx(template_path) template_path = tmp_docx doc = Document(template_path) if args.scan: fields = scan_fields(doc) if not fields: print("No form fields detected in the document.") return print(f"Detected {len(fields)} form fields:\n") for i, f in enumerate(fields, 1): current = f" (current: \"{f['current_value']}\")" if f["current_value"] else "" print(f" {i}. {f['label']}{current}") # Also output as JSON for programmatic use print(f"\n--- JSON ---") template = {f["label"]: f["current_value"] or "" for f in fields} print(json.dumps(template, ensure_ascii=False, indent=2)) return # Load data if args.data: data = json.loads(args.data) elif args.data_file: with open(args.data_file, encoding="utf-8") as f: data = json.load(f) else: print("ERROR: Provide --data or --data-file to fill the form.", file=sys.stderr) sys.exit(1) fields = scan_fields(doc) filled = fill_fields(doc, data, fields, font_name=args.font, font_size=Pt(args.font_size)) # Determine output path output = args.output if not output: base = os.path.splitext(os.path.basename(args.template))[0] template_dir = os.path.dirname(os.path.abspath(args.template)) output = os.path.join(template_dir, base + "_filled.docx") doc.save(output) print(f"Filled {len(filled)}/{len(fields)} fields → {output}") if filled: print(f" Filled: {', '.join(filled)}") unfilled = [f["label"] for f in fields if f["label"] not in filled] if unfilled: print(f" Unfilled: {', '.join(unfilled)}") if __name__ == "__main__": main()
-
-
CHANGELOG.md 568 B
# Changelog All notable changes to this skill are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning: [SemVer](https://semver.org/) ## [1.2.0] - 2026-08-24 ### Added - add the shared feedback-classification and approval-invalidation gate used by every LovStudio Skill ## [1.1.2] - 2026-05-07 ### Fixed - remove system pip flag from script docs - use python3 -m pip install python-docx in dependency examples ## [1.1.1] - 2026-05-07 ### Fixed - add release metadata - add README version badge and changelog entry -
README.md 4.9 KB
# 表单小助手 · Form Assistant  Fill Word document form templates (.docx) with structured data. Auto-detects table-based form fields (label → value cell pairs), supports CJK/Latin mixed text with proper font switching, merged cells, and paragraph-based forms. Part of [skill-publisher/skills](https://example.com/skills/skills) — by [example.com](https://example.com) ## Install ```bash npx skills add fill-form -g -y ``` Requires: Python 3.8+ and `pip install python-docx` ## How It Works ``` ┌─────────────────────────────────────────────────┐ │ Template (.docx) │ │ ┌──────────┬───────────┬──────────┬──────────┐ │ │ │ 主讲人 │ │ 职称 │ │ │ │ ├──────────┼───────────┴──────────┴──────────┤ │ │ │ 单位 │ │ │ │ ├──────────┼───────────┬──────────┬──────────┤ │ │ │ 讲座题目 │ │ 时间 │ │ │ │ └──────────┴───────────┴──────────┴──────────┘ │ └────────────────────┬────────────────────────────┘ │ --scan → detect fields │ --data → fill values ▼ ┌─────────────────────────────────────────────────┐ │ Filled (.docx) │ │ ┌──────────┬───────────┬──────────┬──────────┐ │ │ │ 主讲人 │ 张三 │ 职称 │ 教授 │ │ │ ├──────────┼───────────┴──────────┴──────────┤ │ │ │ 单位 │ 北京大学 │ │ │ ├──────────┼───────────┬──────────┬──────────┤ │ │ │ 讲座题目 │ AI与未来 │ 时间 │ 4月10日 │ │ │ └──────────┴───────────┴──────────┴──────────┘ │ └─────────────────────────────────────────────────┘ ``` ## Usage **Step 1** — Scan the template to see all detected fields: ```bash python fill_form.py --template form.docx --scan ``` Output: ``` Detected 6 form fields: 1. 主讲人 2. 职称 3. 单位 4. 讲座题目 5. 时间 6. 地点 --- JSON --- { "主讲人": "", "职称": "", ... } ``` **Step 2** — Fill with a JSON data file or inline JSON: ```bash # From JSON file python fill_form.py --template form.docx --data-file data.json # Inline JSON python fill_form.py --template form.docx \ --data '{"主讲人": "张三", "职称": "教授", "单位": "北京大学"}' ``` Output saves to the same directory as the template (`form_filled.docx`). ## Options | Option | Default | Description | |--------|---------|-------------| | `--template` | (required) | Path to template .doc/.docx | | `--output` | `<name>_filled.docx` | Output path (defaults to template directory) | | `--scan` | | List all detected form fields | | `--data` | | JSON string with field → value mapping | | `--data-file` | | Path to JSON file with field → value mapping | | `--font` | Platform CJK serif | Font for filled text | | `--font-size` | `11` | Font size in points | ## Field Detection The script detects fillable fields in three ways: | Method | How it works | Example | |--------|-------------|---------| | **Table cells** | Label in one cell, blank value in adjacent cell | `│ 姓名 │ ___ │` | | **Merged rows** | Full-width cell with `Label:` pattern | `│ 备注:________________ │` | | **Paragraphs** | Fallback for docs without tables | `姓名:` followed by blank line | Fields are matched by normalized label (whitespace-insensitive), so `主 讲 人` matches `主讲人`. ## Supported Formats | Format | Support | |--------|---------| | `.docx` | Full support (recommended) | | `.doc` | Auto-converts via `textutil` (macOS) or LibreOffice. Table structure may be lost — convert to `.docx` first for best results. | ## License MIT -
SKILL.md 6 KB
--- name: lov-fill-form category: Office Automation tagline: "Fill Word form templates (.docx). Auto-detects table fields, CJK font support." description: > Fill in Word document form templates (.docx) with user-provided data. Reads a template containing tables with label→value cell pairs, detects all fillable fields, and outputs a completed document. Handles CJK/Latin mixed text with proper font switching. Use this skill when the user wants to fill in a form template, complete an application form, populate a Word table form, or automate document filling. Also trigger when the user mentions "填表", "填写表格", "fill form", "fill template", "表格填写", "申请表", "登记表", or has a .docx template with blank fields to fill. license: MIT compatibility: > Requires Python 3.8+ and python-docx (`pip install python-docx`). Cross-platform: macOS, Windows, Linux. Input must be .docx (recommended) or .doc (auto-converted via textutil on macOS, but table structure may be lost — use .docx when possible). metadata: author: contributors version: "1.2.0" tags: form fill template docx word table cjk --- # 表单小助手 · Form Assistant This skill fills in Word document form templates (.docx) with user-provided data. It detects table-based form fields (label in one cell, value in the adjacent cell) and populates them automatically. ## When to Use - User has a `.docx` form template with blank fields to fill - User wants to fill in an application form, registration form, etc. - Document uses Word tables for form layout (label | value cell pairs) - User mentions 填表, 申请表, 登记表, or wants to automate form filling ## Workflow (MANDATORY) **You MUST follow these steps in order:** ### Step 1: Scan the template Discover all fillable fields: ```bash python lov-fill-form/scripts/fill_form.py --template <path> --scan ``` ### Step 2: Pre-fill from known context Before asking the user, try to fill as many fields as possible from: 1. **User memory** — name, title, organization, etc. 2. **Context files** — if the user provides reference documents (e.g. STARTER-PROMPT.md, project docs), extract relevant info to fill content-heavy fields 3. **Conversation context** — anything already mentioned For content-heavy fields (e.g. "主要内容/简介/摘要"), actively compose the content by synthesizing from context files, user's known expertise, and the topic/title. ### Step 3: Ask only what you don't know **Use `AskUserQuestion` to collect ONLY the fields you cannot fill from context.** - Group fields into a single question - If ALL fields are unknown, list them all - If the user says some fields can be left blank (e.g. "其他朋友会帮我填"), respect that and leave those empty - Do NOT force the user to provide every field ### Step 4: Fill and save Write a JSON data file (avoids shell escaping issues with long text), then run: ```bash python lov-fill-form/scripts/fill_form.py \ --template <path> \ --data-file /tmp/form_data.json ``` **Output path rules:** - Default: `<template_dir>/<name>_filled.docx` (same directory as the template) - If the template is in a temp directory or system path, save to user's document directory or ask the user where to save - Use `--output` to override explicitly ## CLI Reference | Argument | Default | Description | |----------|---------|-------------| | `--template` | (required) | Path to template .doc/.docx file | | `--output` | `<template_dir>/<name>_filled.docx` | Output .docx path | | `--scan` | false | List all detected form fields | | `--data` | `""` | JSON string with field→value mapping | | `--data-file` | `""` | Path to JSON file with field→value mapping | | `--font` | Platform CJK serif | Font name for filled text | | `--font-size` | `11` | Font size in points | ## How Field Detection Works 1. **Table-based** (primary): Scans all tables for rows with label→value cell pairs. A label cell contains short text (CJK or Latin); the adjacent cell is the value field. 2. **Merged rows**: Detects full-width merged cells with "Label:" pattern as large text areas. 3. **Paragraph fallback**: If no tables found, detects "Label:value" patterns in paragraphs. ## Limitations - `.doc` files are auto-converted to `.docx` via macOS `textutil`, which **loses table structure**. For best results, use `.docx` templates directly. If you only have `.doc`, convert with LibreOffice first: `libreoffice --headless --convert-to docx file.doc` - Fields are matched by normalized label text (whitespace removed). If a label contains unusual formatting, the match may fail — use `--scan` to verify detection. ## Dependencies ```bash python3 -m pip install python-docx ``` ## Runtime context (shared) 运行前读取本 Skill 包的 `skill.yaml`,由宿主提供 `skill-runtime/v1` 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。 - 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。 - `required: true` 字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。 - 报错提供可复制的 `context_id`、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。 ## 通用反馈闭环 用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行: 1. 先判断意见是 `task-specific`(仅本次)还是 `reusable`(可跨任务复用)。 2. `task-specific` 只修改当前任务,不改 Skill。 3. `reusable` 先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。 4. 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。 5. `reusable` 修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。 -
skill.yaml 832 B
schema: skill-manifest/v1 id: lov-fill-form version: "1.2.0" runtime: skill-runtime/v1 context: profile: fields: - path: identity.name required: true question: 如果本次输出需要品牌身份,请提供品牌名称。 - path: identity.logo required: false question: 如果需要使用品牌 Logo,请提供 Logo 地址或文件路径。 - path: brand.tone required: false question: 如果已有品牌语气或审美关键词,请提供它们。 preferences: namespace: lov_fill_form fields: - path: user.language required: false question: 希望使用哪种语言输出? - path: user.timezone required: false question: 需要使用哪个时区处理日期和时间? interaction: ask_missing: true max_questions: 1
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.